001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.BorderLayout; 023import java.awt.FlowLayout; 024import java.awt.GridLayout; 025import java.awt.Toolkit; 026import java.awt.event.ActionEvent; 027import java.awt.event.KeyEvent; 028import java.io.File; 029import java.io.Serial; 030 031import javax.swing.AbstractAction; 032import javax.swing.BorderFactory; 033import javax.swing.JButton; 034import javax.swing.JComboBox; 035import javax.swing.JFileChooser; 036import javax.swing.JFrame; 037import javax.swing.JLabel; 038import javax.swing.JOptionPane; 039import javax.swing.JPanel; 040import javax.swing.JScrollPane; 041import javax.swing.JSplitPane; 042import javax.swing.JTextArea; 043import javax.swing.SwingConstants; 044import javax.swing.border.Border; 045import javax.swing.filechooser.FileFilter; 046 047import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 048import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode; 049 050/** 051 * Displays information about a parse tree. 052 * The user can change the file that is parsed and displayed using a JFileChooser. 053 * 054 * @noinspection MagicNumber 055 * @noinspectionreason MagicNumber - "magic numbers" are required to set GUI elements 056 */ 057public final class MainFrame extends JFrame { 058 059 /** A unique serial version identifier. */ 060 @Serial 061 private static final long serialVersionUID = 7970053543351871890L; 062 063 /** The icon to show in the OS task panel. */ 064 private static final String ICON = "icon.png"; 065 066 /** Checkstyle frame model. */ 067 private final transient MainFrameModel model = new MainFrameModel(); 068 /** Reload action. */ 069 private final ReloadAction reloadAction = new ReloadAction(); 070 /** Code text area. */ 071 private JTextArea textArea; 072 /** Xpath text area. */ 073 private JTextArea xpathTextArea; 074 /** Tree table. */ 075 private TreeTable treeTable; 076 077 /** Create a new MainFrame. */ 078 public MainFrame() { 079 createContent(); 080 } 081 082 /** Create content of this MainFrame. */ 083 private void createContent() { 084 setLayout(new BorderLayout()); 085 setIconImage(Toolkit.getDefaultToolkit().getImage(MainFrame.class.getResource(ICON))); 086 087 textArea = new JTextArea(20, 15); 088 textArea.setEditable(false); 089 final JScrollPane textAreaScrollPane = new JScrollPane(textArea); 090 final JPanel textAreaPanel = new JPanel(); 091 textAreaPanel.setLayout(new BorderLayout()); 092 textAreaPanel.add(textAreaScrollPane); 093 textAreaPanel.add(createButtonsPanel(), BorderLayout.PAGE_END); 094 095 treeTable = new TreeTable(model.getParseTreeTableModel()); 096 treeTable.setEditor(textArea); 097 treeTable.setLinePositionList(model.getLinesToPosition()); 098 final JScrollPane treeTableScrollPane = new JScrollPane(treeTable); 099 100 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 101 treeTableScrollPane, textAreaPanel); 102 103 add(splitPane, BorderLayout.CENTER); 104 splitPane.setResizeWeight(0.7); 105 106 xpathTextArea = new JTextArea("Xpath", 7, 0); 107 xpathTextArea.setName("xpathTextArea"); 108 xpathTextArea.setVisible(false); 109 final JPanel xpathAreaPanel = new JPanel(); 110 xpathAreaPanel.setLayout(new BorderLayout()); 111 xpathAreaPanel.add(xpathTextArea); 112 xpathAreaPanel.add(createXpathButtonsPanel(), BorderLayout.PAGE_END); 113 114 treeTable.setXpathEditor(xpathTextArea); 115 116 final Border title = BorderFactory.createTitledBorder("Xpath Query"); 117 xpathAreaPanel.setBorder(title); 118 119 add(xpathAreaPanel, BorderLayout.PAGE_END); 120 } 121 122 /** 123 * Create buttons panel. 124 * 125 * @return buttons panel. 126 */ 127 private JPanel createButtonsPanel() { 128 final JButton openFileButton = new JButton(new FileSelectionAction()); 129 openFileButton.setName("openFileButton"); 130 openFileButton.setMnemonic(KeyEvent.VK_O); 131 openFileButton.setText("Open File"); 132 133 reloadAction.setEnabled(false); 134 final JButton reloadFileButton = new JButton(reloadAction); 135 reloadFileButton.setMnemonic(KeyEvent.VK_R); 136 reloadFileButton.setText("Reload File"); 137 138 final JComboBox<ParseMode> modesCombobox = new JComboBox<>(ParseMode.values()); 139 modesCombobox.setName("modesCombobox"); 140 modesCombobox.setSelectedIndex(0); 141 modesCombobox.addActionListener(event -> { 142 model.setParseMode((ParseMode) modesCombobox.getSelectedItem()); 143 reloadAction.actionPerformed(null); 144 }); 145 146 final JLabel modesLabel = new JLabel("Modes:", SwingConstants.RIGHT); 147 final int leftIndentation = 10; 148 modesLabel.setBorder(BorderFactory.createEmptyBorder(0, leftIndentation, 0, 0)); 149 modesLabel.setDisplayedMnemonic(KeyEvent.VK_M); 150 modesLabel.setLabelFor(modesCombobox); 151 152 final JPanel buttonPanel = new JPanel(); 153 buttonPanel.setLayout(new GridLayout(1, 2)); 154 buttonPanel.add(openFileButton); 155 buttonPanel.add(reloadFileButton); 156 157 final JPanel modesPanel = new JPanel(); 158 modesPanel.add(modesLabel); 159 modesPanel.add(modesCombobox); 160 161 final JPanel mainPanel = new JPanel(); 162 mainPanel.setLayout(new BorderLayout()); 163 mainPanel.add(buttonPanel); 164 mainPanel.add(modesPanel, BorderLayout.LINE_END); 165 166 return mainPanel; 167 } 168 169 /** 170 * Create xpath buttons panel. 171 * 172 * @return xpath buttons panel. 173 */ 174 private JPanel createXpathButtonsPanel() { 175 final JButton expandButton = new JButton(new ExpandCollapseAction()); 176 expandButton.setName("expandButton"); 177 expandButton.setText("Expand/Collapse"); 178 179 final JButton findNodeButton = new JButton(new FindNodeByXpathAction()); 180 findNodeButton.setName("findNodeButton"); 181 findNodeButton.setText("Find node by Xpath"); 182 183 final JPanel xpathButtonsPanel = new JPanel(); 184 xpathButtonsPanel.setLayout(new FlowLayout()); 185 xpathButtonsPanel.add(expandButton); 186 xpathButtonsPanel.add(findNodeButton); 187 188 final JPanel mainPanel = new JPanel(); 189 mainPanel.setLayout(new BorderLayout()); 190 mainPanel.add(xpathButtonsPanel, BorderLayout.LINE_START); 191 192 return mainPanel; 193 } 194 195 /** 196 * Open file and load it. 197 * 198 * @param sourceFile the file to open. 199 */ 200 public void openFile(File sourceFile) { 201 try { 202 model.openFile(sourceFile); 203 setTitle(model.getTitle()); 204 reloadAction.setEnabled(model.isReloadActionEnabled()); 205 textArea.setText(model.getText()); 206 treeTable.setLinePositionList(model.getLinesToPosition()); 207 } 208 catch (final CheckstyleException exc) { 209 JOptionPane.showMessageDialog(this, exc.getMessage()); 210 } 211 } 212 213 /** 214 * Handler for file selection action events. 215 */ 216 private final class FileSelectionAction extends AbstractAction { 217 218 /** A unique serial version identifier. */ 219 @Serial 220 private static final long serialVersionUID = 1762396148873280589L; 221 222 /** 223 * Creates a new {@code FileSelectionAction} instance. 224 */ 225 private FileSelectionAction() { 226 // no code by default 227 } 228 229 @Override 230 public void actionPerformed(ActionEvent event) { 231 final JFileChooser fileChooser = new JFileChooser(model.getLastDirectory()); 232 final FileFilter filter = new JavaFileFilter(); 233 fileChooser.setFileFilter(filter); 234 235 final int returnCode = fileChooser.showOpenDialog(MainFrame.this); 236 if (returnCode == JFileChooser.APPROVE_OPTION) { 237 final File file = fileChooser.getSelectedFile(); 238 openFile(file); 239 } 240 } 241 242 } 243 244 /** 245 * Handler for reload action events. 246 */ 247 private final class ReloadAction extends AbstractAction { 248 249 /** A unique serial version identifier. */ 250 @Serial 251 private static final long serialVersionUID = -890320994114628011L; 252 253 /** 254 * Creates a new {@code ReloadAction} instance. 255 */ 256 private ReloadAction() { 257 // no code by default 258 } 259 260 @Override 261 public void actionPerformed(ActionEvent event) { 262 openFile(model.getCurrentFile()); 263 } 264 265 } 266 267 /** 268 * Handler for Expand and Collapse events. 269 */ 270 private final class ExpandCollapseAction extends AbstractAction { 271 272 /** A unique serial version identifier. */ 273 @Serial 274 private static final long serialVersionUID = -890320994114628011L; 275 276 /** 277 * Creates a new {@code ExpandCollapseAction} instance. 278 */ 279 private ExpandCollapseAction() { 280 // no code by default 281 } 282 283 @Override 284 public void actionPerformed(ActionEvent event) { 285 xpathTextArea.setVisible(!xpathTextArea.isVisible()); 286 } 287 288 } 289 290 /** 291 * Handler for Find Node by Xpath Event. 292 */ 293 private final class FindNodeByXpathAction extends AbstractAction { 294 295 /** A unique serial version identifier. */ 296 @Serial 297 private static final long serialVersionUID = -890320994114628011L; 298 299 /** 300 * Creates a new {@code FindNodeByXpathAction} instance. 301 */ 302 private FindNodeByXpathAction() { 303 // no code by default 304 } 305 306 @Override 307 public void actionPerformed(ActionEvent event) { 308 treeTable.selectNodeByXpath(); 309 } 310 311 } 312 313 /** 314 * Filter for Java files. 315 */ 316 private static final class JavaFileFilter extends FileFilter { 317 318 /** 319 * Creates a new {@code JavaFileFilter} instance. 320 */ 321 private JavaFileFilter() { 322 // no code by default 323 } 324 325 @Override 326 public boolean accept(File file) { 327 return MainFrameModel.shouldAcceptFile(file); 328 } 329 330 @Override 331 public String getDescription() { 332 return "Java Source File"; 333 } 334 335 } 336 337}