org.languagetool.gui.ConfigurationDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.languagetool.gui.ConfigurationDialog.java

Source

/* LanguageTool, a natural language style checker 
 * Copyright (C) 2005 Daniel Naber (http://www.danielnaber.de)
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */
package org.languagetool.gui;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.languagetool.JLanguageTool;
import org.languagetool.Language;
import org.languagetool.Languages;
import org.languagetool.languagemodel.LuceneLanguageModel;
import org.languagetool.rules.Rule;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.*;
import java.util.List;

/**
 * Dialog that offers the available rules so they can be turned on/off
 * individually.
 * 
 * @author Daniel Naber
 */
public class ConfigurationDialog implements ActionListener {

    private static final String NO_MOTHER_TONGUE = "---";
    private static final String ACTION_COMMAND_OK = "OK";
    private static final String ACTION_COMMAND_CANCEL = "CANCEL";
    private static final int MAX_PORT = 65536;

    private final ResourceBundle messages;
    private final Configuration original;
    private final Configuration config;
    private final Frame owner;
    private final boolean insideOffice;
    private boolean configChanged = false;

    private JDialog dialog;
    private JCheckBox serverCheckbox;
    private JTextField serverPortField;
    private JTree configTree[];
    private JCheckBox serverSettingsCheckbox;
    private final List<JPanel> extraPanels = new ArrayList<>();
    private final List<Rule> configurableRules = new ArrayList<>();

    public ConfigurationDialog(Frame owner, boolean insideOffice, Configuration config) {
        this.owner = owner;
        this.insideOffice = insideOffice;
        this.original = config;
        this.config = original.copy(original);
        messages = JLanguageTool.getMessageBundle();
    }

    /**
     * Add extra JPanel to this dialog.
     * 
     * If the panel implements {@see SavablePanel}, this dialog will call
     * {@link SavablePanel#save} after the user clicks OK.
     * 
     * @param panel the JPanel to be added to this dialog
     * @since 3.4
     */
    void addExtraPanel(JPanel panel) {
        extraPanels.add(panel);
    }

    private DefaultMutableTreeNode createTree(List<Rule> rules, boolean isStyle, String tabName) {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Rules");
        String lastRuleId = null;
        Map<String, DefaultMutableTreeNode> parents = new TreeMap<>();
        for (Rule rule : rules) {
            if ((tabName == null && !config.isSpecialTabCategory(rule.getCategory().getName())
                    && ((isStyle && config.isStyleCategory(rule.getCategory().getName()))
                            || (!isStyle && !config.isStyleCategory(rule.getCategory().getName()))))
                    || (tabName != null && config.isInSpecialTab(rule.getCategory().getName(), tabName))) {
                if (rule.hasConfigurableValue()) {
                    configurableRules.add(rule);
                } else {
                    if (!parents.containsKey(rule.getCategory().getName())) {
                        boolean enabled = true;
                        if (config.getDisabledCategoryNames() != null
                                && config.getDisabledCategoryNames().contains(rule.getCategory().getName())) {
                            enabled = false;
                        }
                        if (rule.getCategory().isDefaultOff() && (config.getEnabledCategoryNames() == null
                                || !config.getEnabledCategoryNames().contains(rule.getCategory().getName()))) {
                            enabled = false;
                        }
                        DefaultMutableTreeNode categoryNode = new CategoryNode(rule.getCategory(), enabled);
                        root.add(categoryNode);
                        parents.put(rule.getCategory().getName(), categoryNode);
                    }
                    if (!rule.getId().equals(lastRuleId)) {
                        RuleNode ruleNode = new RuleNode(rule, getEnabledState(rule));
                        parents.get(rule.getCategory().getName()).add(ruleNode);
                    }
                    lastRuleId = rule.getId();
                }
            }
        }
        return root;
    }

    private boolean getEnabledState(Rule rule) {
        boolean ret = true;
        if (config.getDisabledRuleIds().contains(rule.getId())) {
            ret = false;
        }
        if (config.getDisabledCategoryNames().contains(rule.getCategory().getName())) {
            ret = false;
        }
        if ((rule.isDefaultOff() || rule.getCategory().isDefaultOff())
                && !config.getEnabledRuleIds().contains(rule.getId())) {
            ret = false;
        }
        if (rule.isDefaultOff() && rule.getCategory().isDefaultOff()
                && config.getEnabledRuleIds().contains(rule.getId())) {
            config.getDisabledCategoryNames().remove(rule.getCategory().getName());
        }
        return ret;
    }

    public boolean show(List<Rule> rules) {
        configChanged = false;
        if (original != null) {
            config.restoreState(original);
        }
        dialog = new JDialog(owner, true);
        dialog.setTitle(messages.getString("guiConfigWindowTitle"));
        // close dialog when user presses Escape key:
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        ActionListener actionListener = new ActionListener() {
            @Override
            public void actionPerformed(@SuppressWarnings("unused") ActionEvent actionEvent) {
                dialog.setVisible(false);
            }
        };
        JRootPane rootPane = dialog.getRootPane();
        rootPane.registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);

        configurableRules.clear();

        Language lang = config.getLanguage();
        if (lang == null) {
            lang = Languages.getLanguageForLocale(Locale.getDefault());
        }

        String specialTabNames[] = config.getSpecialTabNames();
        int numConfigTrees = 2 + specialTabNames.length;
        configTree = new JTree[numConfigTrees];
        JPanel checkBoxPanel[] = new JPanel[numConfigTrees];
        DefaultMutableTreeNode rootNode;
        GridBagConstraints cons;

        for (int i = 0; i < numConfigTrees; i++) {
            checkBoxPanel[i] = new JPanel();
            cons = new GridBagConstraints();
            checkBoxPanel[i].setLayout(new GridBagLayout());
            cons.anchor = GridBagConstraints.NORTHWEST;
            cons.gridx = 0;
            cons.weightx = 1.0;
            cons.weighty = 1.0;
            cons.fill = GridBagConstraints.HORIZONTAL;
            Collections.sort(rules, new CategoryComparator());
            if (i == 0) {
                rootNode = createTree(rules, false, null); //  grammar options
            } else if (i == 1) {
                rootNode = createTree(rules, true, null); //  Style options
            } else {
                rootNode = createTree(rules, true, specialTabNames[i - 2]); //  Special tab options
            }
            configTree[i] = new JTree(getTreeModel(rootNode));

            configTree[i].applyComponentOrientation(ComponentOrientation.getOrientation(lang.getLocale()));

            configTree[i].setRootVisible(false);
            configTree[i].setEditable(false);
            configTree[i].setCellRenderer(new CheckBoxTreeCellRenderer());
            TreeListener.install(configTree[i]);
            checkBoxPanel[i].add(configTree[i], cons);
            configTree[i].addMouseListener(getMouseAdapter());
        }

        JPanel portPanel = new JPanel();
        portPanel.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(0, 4, 0, 0);
        cons.gridx = 0;
        cons.gridy = 0;
        cons.anchor = GridBagConstraints.WEST;
        cons.fill = GridBagConstraints.NONE;
        cons.weightx = 0.0f;
        if (!insideOffice) {
            createNonOfficeElements(cons, portPanel);
        } else {
            createOfficeElements(cons, portPanel);
        }

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridBagLayout());
        JButton okButton = new JButton(Tools.getLabel(messages.getString("guiOKButton")));
        okButton.setMnemonic(Tools.getMnemonic(messages.getString("guiOKButton")));
        okButton.setActionCommand(ACTION_COMMAND_OK);
        okButton.addActionListener(this);
        JButton cancelButton = new JButton(Tools.getLabel(messages.getString("guiCancelButton")));
        cancelButton.setMnemonic(Tools.getMnemonic(messages.getString("guiCancelButton")));
        cancelButton.setActionCommand(ACTION_COMMAND_CANCEL);
        cancelButton.addActionListener(this);
        cons = new GridBagConstraints();
        cons.insets = new Insets(0, 4, 0, 0);
        buttonPanel.add(okButton, cons);
        buttonPanel.add(cancelButton, cons);

        JTabbedPane tabpane = new JTabbedPane();

        JPanel jPane = new JPanel();
        jPane.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(4, 4, 4, 4);

        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 10.0f;
        cons.weighty = 0.0f;
        cons.fill = GridBagConstraints.NONE;
        cons.anchor = GridBagConstraints.NORTHWEST;
        cons.gridy++;
        cons.anchor = GridBagConstraints.WEST;
        jPane.add(getMotherTonguePanel(cons), cons);

        if (insideOffice) {
            cons.gridy += 3;
        } else {
            cons.gridy++;
        }
        cons.anchor = GridBagConstraints.WEST;
        jPane.add(getNgramPanel(cons), cons);

        cons.gridy++;
        cons.anchor = GridBagConstraints.WEST;
        jPane.add(getWord2VecPanel(cons), cons);

        cons.gridy++;
        cons.anchor = GridBagConstraints.WEST;
        jPane.add(portPanel, cons);
        cons.fill = GridBagConstraints.HORIZONTAL;
        cons.anchor = GridBagConstraints.WEST;
        for (JPanel extra : extraPanels) {
            //in case it wasn't in a containment hierarchy when user changed L&F
            SwingUtilities.updateComponentTreeUI(extra);
            cons.gridy++;
            jPane.add(extra, cons);
        }

        cons.gridy++;
        cons.fill = GridBagConstraints.BOTH;
        cons.weighty = 1.0f;
        jPane.add(new JPanel(), cons);

        tabpane.addTab(messages.getString("guiGeneral"), jPane);

        jPane = new JPanel();
        jPane.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(4, 4, 4, 4);
        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 10.0f;
        cons.weighty = 10.0f;
        cons.fill = GridBagConstraints.BOTH;
        jPane.add(new JScrollPane(checkBoxPanel[0]), cons);
        cons.weightx = 0.0f;
        cons.weighty = 0.0f;

        cons.gridx = 0;
        cons.gridy++;
        cons.fill = GridBagConstraints.NONE;
        cons.anchor = GridBagConstraints.LINE_END;
        jPane.add(getTreeButtonPanel(0), cons);

        tabpane.addTab(messages.getString("guiGrammarRules"), jPane);

        jPane = new JPanel();
        jPane.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(4, 4, 4, 4);
        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 10.0f;
        cons.weighty = 10.0f;
        cons.fill = GridBagConstraints.BOTH;
        jPane.add(new JScrollPane(checkBoxPanel[1]), cons);
        cons.weightx = 0.0f;
        cons.weighty = 0.0f;

        cons.gridx = 0;
        cons.gridy++;
        cons.fill = GridBagConstraints.NONE;
        cons.anchor = GridBagConstraints.LINE_END;
        jPane.add(getTreeButtonPanel(1), cons);

        cons.gridx = 0;
        cons.gridy++;
        cons.weightx = 5.0f;
        cons.weighty = 5.0f;
        cons.fill = GridBagConstraints.BOTH;
        cons.anchor = GridBagConstraints.WEST;
        jPane.add(new JScrollPane(getSpecialRuleValuePanel()), cons);

        tabpane.addTab(messages.getString("guiStyleRules"), jPane);

        for (int i = 0; i < specialTabNames.length; i++) {
            jPane = new JPanel();
            jPane.setLayout(new GridBagLayout());
            cons = new GridBagConstraints();
            cons.insets = new Insets(4, 4, 4, 4);
            cons.gridx = 0;
            cons.gridy = 0;
            cons.weightx = 10.0f;
            cons.weighty = 10.0f;
            cons.fill = GridBagConstraints.BOTH;
            jPane.add(new JScrollPane(checkBoxPanel[i + 2]), cons);
            cons.weightx = 0.0f;
            cons.weighty = 0.0f;

            cons.gridx = 0;
            cons.gridy++;
            cons.fill = GridBagConstraints.NONE;
            cons.anchor = GridBagConstraints.LINE_END;
            jPane.add(getTreeButtonPanel(i + 2), cons);

            tabpane.addTab(specialTabNames[i], jPane);
        }

        jPane = new JPanel();
        jPane.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(4, 4, 4, 4);
        cons.gridx = 0;
        cons.gridy = 0;
        if (insideOffice) {
            JLabel versionText = new JLabel(messages.getString("guiUColorHint"));
            versionText.setForeground(Color.blue);
            jPane.add(versionText, cons);
            cons.gridy++;
        }

        cons.weightx = 2.0f;
        cons.weighty = 2.0f;
        cons.fill = GridBagConstraints.BOTH;

        jPane.add(new JScrollPane(getUnderlineColorPanel(rules)), cons);

        Container contentPane = dialog.getContentPane();
        contentPane.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.insets = new Insets(4, 4, 4, 4);
        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 10.0f;
        cons.weighty = 10.0f;
        cons.fill = GridBagConstraints.BOTH;
        cons.anchor = GridBagConstraints.NORTHWEST;
        contentPane.add(tabpane, cons);
        cons.weightx = 0.0f;
        cons.weighty = 0.0f;
        cons.gridy++;
        cons.fill = GridBagConstraints.NONE;
        cons.anchor = GridBagConstraints.EAST;
        contentPane.add(buttonPanel, cons);

        dialog.pack();
        // center on screen:
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = dialog.getSize();
        dialog.setLocation(screenSize.width / 2 - frameSize.width / 2,
                screenSize.height / 2 - frameSize.height / 2);
        dialog.setLocationByPlatform(true);
        //  add Color tab after dimension was set
        tabpane.addTab(messages.getString("guiUnderlineColor"), jPane);

        for (JPanel extra : this.extraPanels) {
            if (extra instanceof SavablePanel) {
                ((SavablePanel) extra).componentShowing();
            }
        }
        dialog.setVisible(true);
        return configChanged;
    }

    private void createNonOfficeElements(GridBagConstraints cons, JPanel portPanel) {
        serverCheckbox = new JCheckBox(Tools.getLabel(messages.getString("guiRunOnPort")));
        serverCheckbox.setMnemonic(Tools.getMnemonic(messages.getString("guiRunOnPort")));
        serverCheckbox.setSelected(config.getRunServer());
        portPanel.add(serverCheckbox, cons);
        serverCheckbox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) {
                serverPortField.setEnabled(serverCheckbox.isSelected());
                serverSettingsCheckbox.setEnabled(serverCheckbox.isSelected());
            }
        });
        serverCheckbox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setRunServer(serverCheckbox.isSelected());
            }
        });

        serverPortField = new JTextField(Integer.toString(config.getServerPort()));
        serverPortField.setEnabled(serverCheckbox.isSelected());
        serverSettingsCheckbox = new JCheckBox(Tools.getLabel(messages.getString("useGUIConfig")));
        serverPortField.setMinimumSize(new Dimension(100, 25)); // without this the box is just a few pixels small, but why?
        cons.gridx = 1;
        portPanel.add(serverPortField, cons);
        serverPortField.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                try {
                    int serverPort = Integer.parseInt(serverPortField.getText());
                    if (serverPort > -1 && serverPort < MAX_PORT) {
                        serverPortField.setForeground(null);
                        config.setServerPort(serverPort);
                    } else {
                        serverPortField.setForeground(Color.RED);
                    }
                } catch (NumberFormatException ex) {
                    serverPortField.setForeground(Color.RED);
                }
            }
        });

        cons.gridx = 0;
        cons.gridy = 10;
        serverSettingsCheckbox.setMnemonic(Tools.getMnemonic(messages.getString("useGUIConfig")));
        serverSettingsCheckbox.setSelected(config.getUseGUIConfig());
        serverSettingsCheckbox.setEnabled(config.getRunServer());
        serverSettingsCheckbox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setUseGUIConfig(serverSettingsCheckbox.isSelected());
            }
        });
        portPanel.add(serverSettingsCheckbox, cons);
    }

    private void createOfficeElements(GridBagConstraints cons, JPanel portPanel) {
        int numParaCheck = config.getNumParasToCheck();
        JRadioButton[] radioButtons = new JRadioButton[3];
        ButtonGroup numParaGroup = new ButtonGroup();
        radioButtons[0] = new JRadioButton(Tools.getLabel(messages.getString("guiCheckOnlyParagraph")));
        radioButtons[0].setActionCommand("ParagraphCheck");

        radioButtons[1] = new JRadioButton(Tools.getLabel(messages.getString("guiCheckFullText")));
        radioButtons[1].setActionCommand("FullTextCheck");

        radioButtons[2] = new JRadioButton(Tools.getLabel(messages.getString("guiCheckNumParagraphs")));
        radioButtons[2].setActionCommand("NParagraphCheck");
        radioButtons[2].setSelected(true);

        JTextField numParaField = new JTextField(Integer.toString(5), 2);
        numParaField.setEnabled(radioButtons[2].isSelected());
        numParaField.setMinimumSize(new Dimension(30, 25));

        for (int i = 0; i < 3; i++) {
            numParaGroup.add(radioButtons[i]);
        }

        if (numParaCheck == 0) {
            radioButtons[0].setSelected(true);
            numParaField.setEnabled(false);
        } else if (numParaCheck < 0) {
            radioButtons[1].setSelected(true);
            numParaField.setEnabled(false);
        } else {
            radioButtons[2].setSelected(true);
            numParaField.setText(Integer.toString(numParaCheck));
            numParaField.setEnabled(true);
        }

        radioButtons[0].addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                numParaField.setEnabled(false);
                config.setNumParasToCheck(0);
            }
        });

        radioButtons[1].addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                numParaField.setEnabled(false);
                config.setNumParasToCheck(-1);
            }
        });

        radioButtons[2].addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int numParaCheck = Integer.parseInt(numParaField.getText());
                if (numParaCheck < 1)
                    numParaCheck = 1;
                else if (numParaCheck > 99)
                    numParaCheck = 99;
                config.setNumParasToCheck(numParaCheck);
                numParaField.setForeground(Color.BLACK);
                numParaField.setText(Integer.toString(numParaCheck));
                numParaField.setEnabled(true);
            }
        });

        numParaField.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                changedUpdate(e);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                try {
                    int numParaCheck = Integer.parseInt(numParaField.getText());
                    if (numParaCheck > 0 && numParaCheck < 99) {
                        numParaField.setForeground(Color.BLACK);
                        config.setNumParasToCheck(numParaCheck);
                    } else {
                        numParaField.setForeground(Color.RED);
                    }
                } catch (NumberFormatException ex) {
                    numParaField.setForeground(Color.RED);
                }
            }
        });

        JLabel textChangedLabel = new JLabel(Tools.getLabel(messages.getString("guiTextChangeLabel")));
        cons.gridy++;
        portPanel.add(textChangedLabel, cons);

        cons.gridy++;
        cons.insets = new Insets(0, 30, 0, 0);
        for (int i = 0; i < 3; i++) {
            portPanel.add(radioButtons[i], cons);
            if (i < 2)
                cons.gridy++;
        }
        cons.gridx = 1;
        portPanel.add(numParaField, cons);

        JCheckBox noMultiResetbox = new JCheckBox(Tools.getLabel(messages.getString("guiNoMultiReset")));
        noMultiResetbox.setSelected(config.isNoMultiReset());
        noMultiResetbox.setEnabled(config.isResetCheck());
        noMultiResetbox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setNoMultiReset(noMultiResetbox.isSelected());
            }
        });

        JCheckBox resetCheckbox = new JCheckBox(Tools.getLabel(messages.getString("guiDoResetCheck")));
        resetCheckbox.setSelected(config.isResetCheck());
        resetCheckbox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setDoResetCheck(resetCheckbox.isSelected());
                noMultiResetbox.setEnabled(resetCheckbox.isSelected());
            }
        });
        cons.insets = new Insets(0, 4, 0, 0);
        cons.gridx = 0;
        //    JLabel dummyLabel = new JLabel(" ");
        //    cons.gridy++;
        //    portPanel.add(dummyLabel, cons);
        cons.gridy++;
        portPanel.add(resetCheckbox, cons);

        cons.insets = new Insets(0, 30, 0, 0);
        cons.gridx = 0;
        cons.gridy++;
        portPanel.add(noMultiResetbox, cons);

        JCheckBox fullTextCheckAtFirstBox = new JCheckBox(
                Tools.getLabel(messages.getString("guiCheckFullTextAtFirst")));
        fullTextCheckAtFirstBox.setSelected(config.doFullCheckAtFirst());
        fullTextCheckAtFirstBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setFullCheckAtFirst(fullTextCheckAtFirstBox.isSelected());
            }
        });
        cons.insets = new Insets(0, 4, 0, 0);
        cons.gridx = 0;
        //    cons.gridy++;
        //    JLabel dummyLabel2 = new JLabel(" ");
        //    portPanel.add(dummyLabel2, cons);
        cons.gridy++;
        portPanel.add(fullTextCheckAtFirstBox, cons);

        JCheckBox isMultiThreadBox = new JCheckBox(Tools.getLabel(messages.getString("guiIsMultiThread")));
        isMultiThreadBox.setSelected(config.isMultiThread());
        isMultiThreadBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                config.setMultiThreadLO(isMultiThreadBox.isSelected());
            }
        });
        cons.gridy++;
        JLabel dummyLabel3 = new JLabel(" ");
        portPanel.add(dummyLabel3, cons);
        cons.gridy++;
        portPanel.add(isMultiThreadBox, cons);

    }

    @NotNull
    private DefaultTreeModel getTreeModel(DefaultMutableTreeNode rootNode) {
        DefaultTreeModel treeModel = new DefaultTreeModel(rootNode);
        treeModel.addTreeModelListener(new TreeModelListener() {
            @Override
            public void treeNodesChanged(TreeModelEvent e) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getTreePath().getLastPathComponent();
                int index = e.getChildIndices()[0];
                node = (DefaultMutableTreeNode) node.getChildAt(index);
                if (node instanceof RuleNode) {
                    RuleNode o = (RuleNode) node;
                    if (o.getRule().isDefaultOff() || o.getRule().getCategory().isDefaultOff()) {
                        if (o.isEnabled()) {
                            config.getEnabledRuleIds().add(o.getRule().getId());
                            config.getDisabledRuleIds().remove(o.getRule().getId());
                        } else {
                            config.getEnabledRuleIds().remove(o.getRule().getId());
                            config.getDisabledRuleIds().add(o.getRule().getId());
                        }
                    } else {
                        if (o.isEnabled()) {
                            config.getDisabledRuleIds().remove(o.getRule().getId());
                        } else {
                            config.getDisabledRuleIds().add(o.getRule().getId());
                        }
                    }
                }
                if (node instanceof CategoryNode) {
                    CategoryNode o = (CategoryNode) node;
                    if (o.getCategory().isDefaultOff()) {
                        if (o.isEnabled()) {
                            config.getDisabledCategoryNames().remove(o.getCategory().getName());
                            config.getEnabledCategoryNames().add(o.getCategory().getName());
                        } else {
                            config.getDisabledCategoryNames().add(o.getCategory().getName());
                            config.getEnabledCategoryNames().remove(o.getCategory().getName());
                        }
                    } else {
                        if (o.isEnabled()) {
                            config.getDisabledCategoryNames().remove(o.getCategory().getName());
                        } else {
                            config.getDisabledCategoryNames().add(o.getCategory().getName());
                        }
                    }
                }
            }

            @Override
            public void treeNodesInserted(TreeModelEvent e) {
            }

            @Override
            public void treeNodesRemoved(TreeModelEvent e) {
            }

            @Override
            public void treeStructureChanged(TreeModelEvent e) {
            }
        });
        return treeModel;
    }

    @NotNull
    private MouseAdapter getMouseAdapter() {
        return new MouseAdapter() {
            private void handlePopupEvent(MouseEvent e) {
                JTree tree = (JTree) e.getSource();
                TreePath path = tree.getPathForLocation(e.getX(), e.getY());
                if (path == null) {
                    return;
                }
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
                TreePath[] paths = tree.getSelectionPaths();
                boolean isSelected = false;
                if (paths != null) {
                    for (TreePath selectionPath : paths) {
                        if (selectionPath.equals(path)) {
                            isSelected = true;
                        }
                    }
                }
                if (!isSelected) {
                    tree.setSelectionPath(path);
                }
                if (node.isLeaf()) {
                    JPopupMenu popup = new JPopupMenu();
                    JMenuItem aboutRuleMenuItem = new JMenuItem(messages.getString("guiAboutRuleMenu"));
                    aboutRuleMenuItem.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent actionEvent) {
                            RuleNode node = (RuleNode) tree.getSelectionPath().getLastPathComponent();
                            Rule rule = node.getRule();
                            Language lang = config.getLanguage();
                            if (lang == null) {
                                lang = Languages.getLanguageForLocale(Locale.getDefault());
                            }
                            Tools.showRuleInfoDialog(tree, messages.getString("guiAboutRuleTitle"),
                                    rule.getDescription(), rule, rule.getUrl(), messages,
                                    lang.getShortCodeWithCountryAndVariant());
                        }
                    });
                    popup.add(aboutRuleMenuItem);
                    popup.show(tree, e.getX(), e.getY());
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    handlePopupEvent(e);
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    handlePopupEvent(e);
                }
            }
        };
    }

    @NotNull
    private JPanel getTreeButtonPanel(int num) {
        GridBagConstraints cons;
        JPanel treeButtonPanel = new JPanel();
        cons = new GridBagConstraints();
        cons.gridx = 0;
        cons.gridy = 0;
        JButton expandAllButton = new JButton(messages.getString("guiExpandAll"));
        treeButtonPanel.add(expandAllButton, cons);
        expandAllButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                TreeNode root = (TreeNode) configTree[num].getModel().getRoot();
                TreePath parent = new TreePath(root);
                for (Enumeration cat = root.children(); cat.hasMoreElements();) {
                    TreeNode n = (TreeNode) cat.nextElement();
                    TreePath child = parent.pathByAddingChild(n);
                    configTree[num].expandPath(child);
                }
            }
        });

        cons.gridx = 1;
        cons.gridy = 0;
        JButton collapseAllButton = new JButton(messages.getString("guiCollapseAll"));
        treeButtonPanel.add(collapseAllButton, cons);
        collapseAllButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                TreeNode root = (TreeNode) configTree[num].getModel().getRoot();
                TreePath parent = new TreePath(root);
                for (Enumeration categ = root.children(); categ.hasMoreElements();) {
                    TreeNode n = (TreeNode) categ.nextElement();
                    TreePath child = parent.pathByAddingChild(n);
                    configTree[num].collapsePath(child);
                }
            }
        });
        return treeButtonPanel;
    }

    @NotNull
    private JPanel getMotherTonguePanel(GridBagConstraints cons) {
        JPanel motherTonguePanel = new JPanel();
        if (insideOffice) {
            motherTonguePanel.setLayout(new GridBagLayout());
            GridBagConstraints cons1 = new GridBagConstraints();
            cons1.insets = new Insets(0, 0, 0, 0);
            cons1.gridx = 0;
            cons1.gridy = 0;
            cons1.anchor = GridBagConstraints.WEST;
            cons1.fill = GridBagConstraints.NONE;
            cons1.weightx = 0.0f;
            JRadioButton[] radioButtons = new JRadioButton[2];
            ButtonGroup numParaGroup = new ButtonGroup();
            radioButtons[0] = new JRadioButton(Tools.getLabel(messages.getString("guiUseDocumentLanguage")));
            radioButtons[0].setActionCommand("DocLang");
            radioButtons[0].setSelected(true);

            radioButtons[1] = new JRadioButton(Tools.getLabel(messages.getString("guiSetLanguageTo")));
            radioButtons[1].setActionCommand("SelectLang");

            JComboBox<String> motherTongueBox = new JComboBox<>(getPossibleMotherTongues());
            if (config.getMotherTongue() != null) {
                motherTongueBox.setSelectedItem(config.getMotherTongue().getTranslatedName(messages));
            }
            motherTongueBox.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                        Language motherTongue;
                        if (motherTongueBox.getSelectedItem() instanceof String) {
                            motherTongue = getLanguageForLocalizedName(
                                    motherTongueBox.getSelectedItem().toString());
                        } else {
                            motherTongue = (Language) motherTongueBox.getSelectedItem();
                        }
                        config.setMotherTongue(motherTongue);
                        config.setUseDocLanguage(false);
                        radioButtons[1].setSelected(true);
                    }
                }
            });

            for (int i = 0; i < 2; i++) {
                numParaGroup.add(radioButtons[i]);
            }

            if (config.getUseDocLanguage()) {
                radioButtons[0].setSelected(true);
            } else {
                radioButtons[1].setSelected(true);
            }

            radioButtons[0].addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    config.setUseDocLanguage(true);
                }
            });

            radioButtons[1].addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    config.setUseDocLanguage(false);
                    Language motherTongue;
                    if (motherTongueBox.getSelectedItem() instanceof String) {
                        motherTongue = getLanguageForLocalizedName(motherTongueBox.getSelectedItem().toString());
                    } else {
                        motherTongue = (Language) motherTongueBox.getSelectedItem();
                    }
                    config.setMotherTongue(motherTongue);
                }
            });
            motherTonguePanel.add(radioButtons[0], cons1);
            cons1.gridy++;
            motherTonguePanel.add(radioButtons[1], cons1);
            cons1.gridx = 1;
            motherTonguePanel.add(motherTongueBox, cons1);
        } else {
            motherTonguePanel.add(new JLabel(messages.getString("guiMotherTongue")), cons);
            JComboBox<String> motherTongueBox = new JComboBox<>(getPossibleMotherTongues());
            if (config.getMotherTongue() != null) {
                motherTongueBox.setSelectedItem(config.getMotherTongue().getTranslatedName(messages));
            }
            motherTongueBox.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                        Language motherTongue;
                        if (motherTongueBox.getSelectedItem() instanceof String) {
                            motherTongue = getLanguageForLocalizedName(
                                    motherTongueBox.getSelectedItem().toString());
                        } else {
                            motherTongue = (Language) motherTongueBox.getSelectedItem();
                        }
                        config.setMotherTongue(motherTongue);
                    }
                }
            });
            motherTonguePanel.add(motherTongueBox, cons);
        }
        return motherTonguePanel;
    }

    private JPanel getNgramPanel(GridBagConstraints cons) {
        JPanel panel = new JPanel();
        panel.add(new JLabel(messages.getString("guiNgramDir")), cons);
        File dir = config.getNgramDirectory();
        int maxDirDisplayLength = 45;
        String buttonText = dir != null ? StringUtils.abbreviate(dir.getAbsolutePath(), maxDirDisplayLength)
                : messages.getString("guiNgramDirSelect");
        JButton ngramDirButton = new JButton(buttonText);
        ngramDirButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                File newDir = Tools.openDirectoryDialog(owner, dir);
                if (newDir != null) {
                    try {
                        if (config.getLanguage() != null) { // may happen in office context
                            File checkDir = new File(newDir, config.getLanguage().getShortCode());
                            LuceneLanguageModel.validateDirectory(checkDir);
                        }
                        config.setNgramDirectory(newDir);
                        ngramDirButton
                                .setText(StringUtils.abbreviate(newDir.getAbsolutePath(), maxDirDisplayLength));
                    } catch (Exception ex) {
                        Tools.showErrorMessage(ex);
                    }
                } else {
                    // not the best UI, but this way user can turn off ngram feature without another checkbox
                    config.setNgramDirectory(null);
                    ngramDirButton.setText(
                            StringUtils.abbreviate(messages.getString("guiNgramDirSelect"), maxDirDisplayLength));
                }
            }
        });
        panel.add(ngramDirButton, cons);
        JButton helpButton = new JButton(messages.getString("guiNgramHelp"));
        helpButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Tools.openURL("http://wiki.languagetool.org/finding-errors-using-n-gram-data");
            }
        });
        panel.add(helpButton, cons);
        return panel;
    }

    private JPanel getWord2VecPanel(GridBagConstraints cons) {
        JPanel panel = new JPanel();
        panel.add(new JLabel(messages.getString("guiWord2VecDir")), cons);
        File dir = config.getWord2VecDirectory();
        int maxDirDisplayLength = 45;
        String buttonText = dir != null ? StringUtils.abbreviate(dir.getAbsolutePath(), maxDirDisplayLength)
                : messages.getString("guiWord2VecDirSelect");
        JButton word2vecDirButton = new JButton(buttonText);
        word2vecDirButton.addActionListener(e -> {
            File newDir = Tools.openDirectoryDialog(owner, dir);
            if (newDir != null) {
                try {
                    config.setWord2VecDirectory(newDir);
                    word2vecDirButton
                            .setText(StringUtils.abbreviate(newDir.getAbsolutePath(), maxDirDisplayLength));
                } catch (Exception ex) {
                    Tools.showErrorMessage(ex);
                }
            } else {
                // not the best UI, but this way user can turn off word2vec feature without another checkbox
                config.setWord2VecDirectory(null);
                word2vecDirButton.setText(
                        StringUtils.abbreviate(messages.getString("guiWord2VecDirSelect"), maxDirDisplayLength));
            }
        });
        panel.add(word2vecDirButton, cons);
        JButton helpButton = new JButton(messages.getString("guiWord2VecHelp"));
        helpButton.addActionListener(e -> {
            Tools.openURL("https://github.com/gulp21/languagetool-neural-network");
        });
        panel.add(helpButton, cons);
        return panel;
    }

    private String[] getPossibleMotherTongues() {
        List<String> motherTongues = new ArrayList<>();
        if (!insideOffice) {
            motherTongues.add(NO_MOTHER_TONGUE);
        }
        for (Language lang : Languages.get()) {
            motherTongues.add(lang.getTranslatedName(messages));
        }
        return motherTongues.toArray(new String[motherTongues.size()]);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (ACTION_COMMAND_OK.equals(e.getActionCommand())) {
            if (original != null) {
                original.restoreState(config);
            }
            for (JPanel extra : extraPanels) {
                if (extra instanceof SavablePanel) {
                    ((SavablePanel) extra).save();
                }
            }
            configChanged = true;
            dialog.setVisible(false);
        } else if (ACTION_COMMAND_CANCEL.equals(e.getActionCommand())) {
            dialog.setVisible(false);
        }
    }

    /**
     * Get the Language object for the given localized language name.
     * 
     * @param languageName e.g. <code>English</code> or <code>German</code> (case is significant)
     * @return a Language object or <code>null</code> if the language could not be found
     */
    @Nullable
    private Language getLanguageForLocalizedName(String languageName) {
        for (Language element : Languages.get()) {
            if (languageName.equals(element.getTranslatedName(messages))) {
                return element;
            }
        }
        return null;
    }

    static class CategoryComparator implements Comparator<Rule> {

        @Override
        public int compare(Rule r1, Rule r2) {
            boolean hasCat = r1.getCategory() != null && r2.getCategory() != null;
            if (hasCat) {
                int res = r1.getCategory().getName().compareTo(r2.getCategory().getName());
                if (res == 0) {
                    return r1.getDescription().compareToIgnoreCase(r2.getDescription());
                }
                return res;
            }
            return r1.getDescription().compareToIgnoreCase(r2.getDescription());
        }

    }

    /* Panel to set Values for special rules like LongSentenceRule
     * @since 4.1
     */
    private JPanel getSpecialRuleValuePanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());
        GridBagConstraints cons = new GridBagConstraints();
        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 0.0f;
        cons.anchor = GridBagConstraints.WEST;

        List<JCheckBox> ruleCheckboxes = new ArrayList<JCheckBox>();
        List<JLabel> ruleLabels = new ArrayList<JLabel>();
        List<JTextField> ruleValueFields = new ArrayList<JTextField>();

        for (int i = 0; i < configurableRules.size(); i++) {
            Rule rule = configurableRules.get(i);
            JCheckBox ruleCheckbox = new JCheckBox(rule.getDescription());
            ruleCheckboxes.add(ruleCheckbox);
            ruleCheckbox.setSelected(getEnabledState(rule));
            cons.insets = new Insets(3, 0, 0, 0);
            panel.add(ruleCheckbox, cons);

            cons.insets = new Insets(0, 24, 0, 0);
            cons.gridy++;
            JLabel ruleLabel = new JLabel(rule.getConfigureText());
            ruleLabels.add(ruleLabel);
            ruleLabel.setEnabled(ruleCheckbox.isSelected());
            panel.add(ruleLabel, cons);

            cons.gridx++;
            int value = config.getConfigurableValue(rule.getId());
            if (config.getConfigurableValue(rule.getId()) < 0) {
                value = rule.getDefaultValue();
            }
            JTextField ruleValueField = new JTextField(Integer.toString(value), 2);
            ruleValueFields.add(ruleValueField);
            ruleValueField.setEnabled(ruleCheckbox.isSelected());
            ruleValueField.setMinimumSize(new Dimension(35, 25)); // without this the box is just a few pixels small, but why?
            panel.add(ruleValueField, cons);

            ruleCheckbox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) {
                    ruleValueField.setEnabled(ruleCheckbox.isSelected());
                    ruleLabel.setEnabled(ruleCheckbox.isSelected());
                    if (ruleCheckbox.isSelected()) {
                        config.getEnabledRuleIds().add(rule.getId());
                        config.getDisabledRuleIds().remove(rule.getId());
                    } else {
                        config.getEnabledRuleIds().remove(rule.getId());
                        config.getDisabledRuleIds().add(rule.getId());
                    }
                }
            });

            ruleValueField.getDocument().addDocumentListener(new DocumentListener() {
                @Override
                public void insertUpdate(DocumentEvent e) {
                    changedUpdate(e);
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    changedUpdate(e);
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    try {
                        int num = Integer.parseInt(ruleValueField.getText());
                        if (num < rule.getMinConfigurableValue()) {
                            num = rule.getMinConfigurableValue();
                            ruleValueField.setForeground(Color.RED);
                        } else if (num > rule.getMaxConfigurableValue()) {
                            num = rule.getMaxConfigurableValue();
                            ruleValueField.setForeground(Color.RED);
                        } else {
                            ruleValueField.setForeground(null);
                        }
                        config.setConfigurableValue(rule.getId(), num);
                    } catch (Exception ex) {
                        ruleValueField.setForeground(Color.RED);
                    }
                }
            });

            cons.gridx = 0;
            cons.gridy++;

        }
        return panel;
    }

    /*  Panel to choose underline Colors  
     *  @since 4.2
     */

    JPanel getUnderlineColorPanel(List<Rule> rules) {
        JPanel panel = new JPanel();

        panel.setLayout(new GridBagLayout());
        GridBagConstraints cons = new GridBagConstraints();
        cons.gridx = 0;
        cons.gridy = 0;
        cons.weightx = 0.0f;
        cons.fill = GridBagConstraints.NONE;
        cons.anchor = GridBagConstraints.NORTHWEST;

        List<String> categories = new ArrayList<String>();
        for (Rule rule : rules) {
            String category = rule.getCategory().getName();
            boolean contain = false;
            for (String c : categories) {
                if (c.equals(category)) {
                    contain = true;
                    break;
                }
            }
            if (!contain) {
                categories.add(category);
            }
        }
        List<JLabel> categorieLabel = new ArrayList<JLabel>();
        List<JLabel> underlineLabel = new ArrayList<JLabel>();
        List<JButton> changeButton = new ArrayList<JButton>();
        List<JButton> defaultButton = new ArrayList<JButton>();
        for (int nCat = 0; nCat < categories.size(); nCat++) {
            categorieLabel.add(new JLabel(categories.get(nCat) + " "));
            underlineLabel.add(new JLabel(" \u2588\u2588\u2588 ")); // \u2587 is smaller
            underlineLabel.get(nCat).setForeground(config.getUnderlineColor(categories.get(nCat)));
            underlineLabel.get(nCat).setBackground(config.getUnderlineColor(categories.get(nCat)));
            JLabel uLabel = underlineLabel.get(nCat);
            String cLabel = categories.get(nCat);
            panel.add(categorieLabel.get(nCat), cons);
            cons.gridx++;
            panel.add(underlineLabel.get(nCat), cons);

            changeButton.add(new JButton(messages.getString("guiUColorChange")));
            changeButton.get(nCat).addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Color oldColor = uLabel.getForeground();
                    Color newColor = JColorChooser.showDialog(null, messages.getString("guiUColorDialogHeader"),
                            oldColor);
                    if (newColor != null && newColor != oldColor) {
                        uLabel.setForeground(newColor);
                        config.setUnderlineColor(cLabel, newColor);
                    }
                }
            });
            cons.gridx++;
            panel.add(changeButton.get(nCat), cons);

            defaultButton.add(new JButton(messages.getString("guiUColorDefault")));
            defaultButton.get(nCat).addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    config.setDefaultUnderlineColor(cLabel);
                    uLabel.setForeground(config.getUnderlineColor(cLabel));
                }
            });
            cons.gridx++;
            panel.add(defaultButton.get(nCat), cons);
            cons.gridx = 0;
            cons.gridy++;
        }
        return panel;
    }

}