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