001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.event.ActionEvent; 025import java.awt.event.MouseEvent; 026import java.util.EventObject; 027import java.util.List; 028 029import javax.swing.AbstractAction; 030import javax.swing.Action; 031import javax.swing.JTable; 032import javax.swing.JTextArea; 033import javax.swing.JTree; 034import javax.swing.KeyStroke; 035import javax.swing.LookAndFeel; 036import javax.swing.table.TableCellEditor; 037import javax.swing.tree.TreePath; 038 039import com.google.common.collect.ImmutableList; 040import com.puppycrawl.tools.checkstyle.api.DetailAST; 041 042/** 043 * This example shows how to create a simple JTreeTable component, 044 * by using a JTree as a renderer (and editor) for the cells in a 045 * particular column in the JTable. 046 * 047 * <a href= 048 * "https://docs.oracle.com/cd/E48246_01/apirefs.1111/e13403/oracle/ide/controls/TreeTableModel.html"> 049 * Original Source Location</a> 050 * 051 * @author Philip Milne 052 * @author Scott Violet 053 * @author Lars Kühne 054 */ 055public class JTreeTable extends JTable { 056 /** For Serialisation that will never happen. */ 057 private static final long serialVersionUID = -8493693409423365387L; 058 /** A subclass of JTree. */ 059 private final TreeTableCellRenderer tree; 060 /** JTextArea editor. */ 061 private JTextArea editor; 062 /** Line position map. */ 063 private List<Integer> linePositionMap; 064 065 /** 066 * Creates JTreeTable base on TreeTableModel. 067 * @param treeTableModel Tree table model 068 */ 069 public JTreeTable(TreeTableModel treeTableModel) { 070 071 // Create the tree. It will be used as a renderer and editor. 072 tree = new TreeTableCellRenderer(this, treeTableModel); 073 074 // Install a tableModel representing the visible rows in the tree. 075 setModel(new TreeTableModelAdapter(treeTableModel, tree)); 076 077 // Force the JTable and JTree to share their row selection models. 078 final ListToTreeSelectionModelWrapper selectionWrapper = new 079 ListToTreeSelectionModelWrapper(this); 080 tree.setSelectionModel(selectionWrapper); 081 setSelectionModel(selectionWrapper.getListSelectionModel()); 082 083 // Install the tree editor renderer and editor. 084 setDefaultRenderer(TreeTableModel.class, tree); 085 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); 086 087 // No grid. 088 setShowGrid(false); 089 090 // No intercell spacing 091 setIntercellSpacing(new Dimension(0, 0)); 092 093 // And update the height of the trees row to match that of 094 // the table. 095 if (tree.getRowHeight() < 1) { 096 // Metal looks better like this. 097 setRowHeight(getRowHeight()); 098 } 099 100 final Action expand = new AbstractAction() { 101 private static final long serialVersionUID = -5859674518660156121L; 102 103 @Override 104 public void actionPerformed(ActionEvent e) { 105 final TreePath selected = tree.getSelectionPath(); 106 final DetailAST ast = (DetailAST) selected.getLastPathComponent(); 107 new CodeSelector(ast, editor, linePositionMap).select(); 108 109 if (tree.isExpanded(selected)) { 110 tree.collapsePath(selected); 111 } 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 @Override 130 public void updateUI() { 131 super.updateUI(); 132 if (tree != null) { 133 tree.updateUI(); 134 } 135 // Use the tree's default foreground and background colors in the 136 // table. 137 LookAndFeel.installColorsAndFont(this, "Tree.background", 138 "Tree.foreground", "Tree.font"); 139 } 140 141 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to 142 * paint the editor. The UI currently uses different techniques to 143 * paint the renderers and editors and overriding setBounds() below 144 * is not the right thing to do for an editor. Returning -1 for the 145 * editing row in this case, ensures the editor is never painted. 146 */ 147 @Override 148 public int getEditingRow() { 149 final Class<?> editingClass = getColumnClass(editingColumn); 150 151 if (editingClass == TreeTableModel.class) { 152 return -1; 153 } 154 else { 155 return editingRow; 156 } 157 } 158 159 /** 160 * Overridden to pass the new rowHeight to the tree. 161 */ 162 @Override 163 public final void setRowHeight(int newRowHeight) { 164 super.setRowHeight(newRowHeight); 165 if (tree != null && tree.getRowHeight() != newRowHeight) { 166 tree.setRowHeight(getRowHeight()); 167 } 168 } 169 170 /** 171 * @return the tree that is being shared between the model. 172 */ 173 public JTree getTree() { 174 return tree; 175 } 176 177 /** 178 * Sets text area editor. 179 * @param textArea JTextArea component. 180 */ 181 public void setEditor(JTextArea textArea) { 182 editor = textArea; 183 } 184 185 /** 186 * Sets line position map. 187 * @param linePositionMap Line position map. 188 */ 189 public void setLinePositionMap(List<Integer> linePositionMap) { 190 this.linePositionMap = ImmutableList.copyOf(linePositionMap); 191 } 192 193 /** 194 * TreeTableCellEditor implementation. Component returned is the 195 * JTree. 196 */ 197 private class TreeTableCellEditor extends BaseCellEditor implements 198 TableCellEditor { 199 @Override 200 public Component getTableCellEditorComponent(JTable table, 201 Object value, 202 boolean isSelected, 203 int row, int column) { 204 return tree; 205 } 206 207 /** 208 * Overridden to return false, and if the event is a mouse event 209 * it is forwarded to the tree. 210 * 211 * <p>The behavior for this is debatable, and should really be offered 212 * as a property. By returning false, all keyboard actions are 213 * implemented in terms of the table. By returning true, the 214 * tree would get a chance to do something with the keyboard 215 * events. For the most part this is ok. But for certain keys, 216 * such as left/right, the tree will expand/collapse where as 217 * the table focus should really move to a different column. Page 218 * up/down should also be implemented in terms of the table. 219 * By returning false this also has the added benefit that clicking 220 * outside of the bounds of the tree node, but still in the tree 221 * column will select the row, whereas if this returned true 222 * that wouldn't be the case. 223 * 224 * <p>By returning false we are also enforcing the policy that 225 * the tree will never be editable (at least by a key sequence). 226 * 227 * @see TableCellEditor 228 */ 229 @Override 230 public boolean isCellEditable(EventObject e) { 231 if (e instanceof MouseEvent) { 232 for (int counter = getColumnCount() - 1; counter >= 0; 233 counter--) { 234 if (getColumnClass(counter) == TreeTableModel.class) { 235 final MouseEvent me = (MouseEvent) e; 236 final MouseEvent newME = new MouseEvent(tree, me.getID(), 237 me.getWhen(), me.getModifiers(), 238 me.getX() - getCellRect(0, counter, true).x, 239 me.getY(), me.getClickCount(), 240 me.isPopupTrigger()); 241 tree.dispatchEvent(newME); 242 break; 243 } 244 } 245 } 246 247 return false; 248 } 249 } 250}