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