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}