/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, University of Maryland
 *
 * 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., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA
 */

package edu.umd.cs.findbugs.gui2;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.tree.TreeModel;

import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.Plugin;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.cloud.CloudPlugin;
import edu.umd.cs.findbugs.filter.Filter;
import edu.umd.cs.findbugs.filter.Matcher;
import edu.umd.cs.findbugs.util.LaunchBrowser;

@SuppressWarnings("serial")
public class PreferencesFrame extends FBDialog {
    private static final Logger LOGGER = Logger.getLogger(PreferencesFrame.class.getName());

    private static final int TAB_MIN = 1;
    private static final int TAB_MAX = 20;

    private static final int FONT_MIN = 10;
    private static final int FONT_MAX = 99;

    private static PreferencesFrame instance;

    private final CheckBoxList filterCheckBoxList = new CheckBoxList();

    // Variables for Properties tab.
    private JTextField tabTextField;
    private JTextField fontTextField;

    private JTextField packagePrefixLengthTextField;

    private final Map<Plugin, EnabledSettings> pluginEnabledStatus = new HashMap<Plugin, EnabledSettings>();
    private JPanel pluginPanelCenter;

    private static class EnabledSettings {
        public boolean global;
        public Boolean project;

        private EnabledSettings(boolean global, Boolean project) {
            this.global = global;
            this.project = project;
        }
    }

    public static PreferencesFrame getInstance() {
        if (instance == null)
            instance = new PreferencesFrame();

        return instance;
    }

    private boolean pluginsAdded = false;
    private final JPanel filterPane;
    private final JTabbedPane mainTabPane;
    
    public void showFilterPane() {
        mainTabPane.setSelectedComponent(filterPane);
        
    }
    private PreferencesFrame() {
        setTitle(edu.umd.cs.findbugs.L10N.getLocalString("dlg.fil_sup_ttl", "Preferences"));
        setModal(true);

        mainTabPane = new JTabbedPane();

        mainTabPane.add("General", createPropertiesPane());

        filterPane = createFilterPane();
        mainTabPane.add(edu.umd.cs.findbugs.L10N.getLocalString("pref.filters", "Filters"), filterPane);
        mainTabPane.add("Plugins", createPluginPane());

        MainFrame.getInstance().updateStatusBar();
        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        JPanel top = new JPanel();
        top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS));
        top.add(Box.createHorizontalStrut(5));
        top.add(mainTabPane);
        top.add(Box.createHorizontalStrut(5));

        JPanel bottom = new JPanel();
        bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS));
        bottom.add(Box.createHorizontalGlue());
        bottom.add(new JButton(new AbstractAction(edu.umd.cs.findbugs.L10N.getLocalString("pref.close", "Close")) {
            public void actionPerformed(ActionEvent evt) {
                handleWindowClose();
                PreferencesFrame.this.setVisible(false);
            }
        }));
        bottom.add(Box.createHorizontalStrut(5));

        add(Box.createVerticalStrut(5));
        add(top);
        add(Box.createVerticalStrut(5));
        add(bottom);
        add(Box.createVerticalStrut(5));

        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent e) {
                resetPropertiesPane();
            }
        });
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                handleWindowClose();
            }
        });
        setDefaultCloseOperation(HIDE_ON_CLOSE);

        pack();
    }

    private void handleWindowClose() {
        TreeModel bt = (MainFrame.getInstance().getTree().getModel());
        if (bt instanceof BugTreeModel)
            ((BugTreeModel) bt).checkSorter();
        Project project = getCurrentProject();

        boolean changed = pluginsAdded;
        pluginsAdded = false;
        List<String> enabledPlugins = new ArrayList<String>();
        List<String> disabledPlugins = new ArrayList<String>();
        for (Map.Entry<Plugin, EnabledSettings> entry : pluginEnabledStatus.entrySet()) {
            Plugin plugin = entry.getKey();
            EnabledSettings enabled = entry.getValue();
            if (project != null) {
                Boolean newSetting = enabled.project;
                Boolean existingSetting = project.getPluginStatus(plugin);
                if (existingSetting != newSetting && (newSetting == null || !newSetting.equals(existingSetting))) {
                    project.setPluginStatusTrinary(plugin.getPluginId(), newSetting);
                    changed = true;
                }
            }
            if (enabled.global)
                enabledPlugins.add(plugin.getPluginId());
            else
                disabledPlugins.add(plugin.getPluginId());
            if (plugin.isGloballyEnabled() != enabled.global) {
                plugin.setGloballyEnabled(enabled.global);
                changed = true;
            }
        }

        if (changed && project != null) {
            MainFrame.getInstance().updateBugTree();
            MainFrame.getInstance().setProjectChanged(true);
        }
        if (project == null) {
            GUISaveState.getInstance().setPluginsEnabled(enabledPlugins, disabledPlugins);
            GUISaveState.getInstance().save();
        }
    }

    private Project getCurrentProject() {
        BugCollection bugCollection = MainFrame.getInstance().getBugCollection();
        return bugCollection == null ? null : bugCollection.getProject();
    }

    private JPanel createPluginPane() {
        final JPanel pluginPanel = new JPanel();
        pluginPanel.setLayout(new BorderLayout());
        pluginPanelCenter = new JPanel();

        pluginPanel.add(pluginPanelCenter, BorderLayout.CENTER);

        pluginPanelCenter.setBorder(new EmptyBorder(10,10,10,10));
        pluginPanelCenter.setLayout(new GridBagLayout());

        JButton addButton = new JButton("Install new plugin...");
        JPanel south = new JPanel();

        south.add(addButton);
        addButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser();
                chooser.addChoosableFileFilter(new FileFilter() {

                    @Override
                    public String getDescription() {
                        return "FindBugs Plugin (*.jar)";
                    }

                    @SuppressWarnings({"RedundantIfStatement"})
                    @Override
                    public boolean accept(File f) {
                        if (f.isDirectory())
                            return true;
                        if (!f.canRead())
                            return false;
                        if (f.getName().endsWith(".jar"))
                            return true;
                        return false;
                    }
                });
                chooser.setDialogTitle("Select a FindBugs plugin jar");
                int retvalue = chooser.showDialog(PreferencesFrame.this, "Install");

                if (retvalue == JFileChooser.APPROVE_OPTION) {
                    File f = chooser.getSelectedFile();
                    try {
                        // load and enable for project (if loaded)
                        Plugin plugin = Plugin.loadCustomPlugin(f, PreferencesFrame.this.getCurrentProject());

                        GUISaveState guiSaveState = GUISaveState.getInstance();
                        URL url = f.toURI().toURL();
                        // add to FBGUI custom plugins list
                        guiSaveState.addCustomPlugin(url);
                        // add to list of enabled plugins
                        guiSaveState.setPluginEnabled(plugin.getPluginId());
                        plugin.setGloballyEnabled(true);
                        guiSaveState.save();
                        pluginsAdded = true;
                        rebuildPluginCheckboxes();

                    } catch (Exception e1) {
                        LOGGER.log(Level.WARNING, "Could not load " + f.getPath(), e1);
                        JOptionPane.showMessageDialog(PreferencesFrame.this, "Could not load " + f.getPath()
                                + "\n\n"
                                + e1.getClass().getSimpleName() + ": " + e1.getMessage(),
                                "Error Loading Plugin", JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        });

        pluginPanel.add(south, BorderLayout.SOUTH);

        return pluginPanel;
    }

    EnabledSettings isEnabled(@CheckForNull Project project, Plugin plugin) {
        return new EnabledSettings(plugin.isGloballyEnabled(), project == null ? null : project.getPluginStatus(plugin));
    }
    
    private void rebuildPluginCheckboxes() {
        Project currentProject = getCurrentProject();
        pluginPanelCenter.removeAll();
        if (currentProject != null) {
            GridBagConstraints g = new GridBagConstraints();
            g.fill = GridBagConstraints.NONE;
            g.insets = new Insets(5,5,5,5);
            g.gridy = 0;
    //        g.anchor = GridBagConstraints.WEST;
    //        g.gridx = 1;
    //        pluginPanelCenter.add(new JLabel("Global Setting"), g);
            g.anchor = GridBagConstraints.CENTER;
            g.gridx = 2;
            pluginPanelCenter.add(new JLabel("Project Setting"), g);
        }
        Collection<Plugin> plugins = Plugin.getAllPlugins();
        int added = 0;
        for (final Plugin plugin : plugins) {
            if (plugin.isCorePlugin())
                continue;
            String text = plugin.getShortDescription();
            String id = plugin.getPluginId();
            if (text == null)
                text = id;
            final URL url = plugin.getPluginLoader().getURL();
            String pluginUrlStr = url.toExternalForm();
            if ("file".equals(url.getProtocol())) {
                try {
                    pluginUrlStr = new File(URLDecoder.decode(url.getPath(), "UTF-8")).getAbsolutePath();
                } catch (UnsupportedEncodingException e) {

                }
            }
            text = String.format("<html>%s<br><font style='font-weight:normal;font-style:italic'>%s",
                    text, pluginUrlStr);

            EnabledSettings enabled = isEnabled(currentProject, plugin);
            final JCheckBox checkGlobal = new JCheckBox(text, enabled.global);
            final boolean cannotDisable = plugin.isEnabledByDefault() && plugin.cannotDisable();
            if (cannotDisable) {
                if (!enabled.global)
                    throw new IllegalStateException(
                            plugin.getPluginId() + " is enabled by default and cannot be disabled, but is disabled");
                checkGlobal.setEnabled(false);
            } else {
                checkGlobal.addMouseListener(new MouseAdapter() {
            
                @Override
                public void mousePressed(MouseEvent e) {
                    if (SwingUtilities.isRightMouseButton(e)) {
                        JPopupMenu menu = new JPopupMenu();
                        JMenuItem item = new JMenuItem("Uninstall " + plugin.getShortDescription() + "...");
                        item.addActionListener(new UninstallClickListener(plugin, url));
                        menu.add(item);
                        menu.show(checkGlobal, e.getX(), e.getY());
                    }
                }
            });
                checkGlobal.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        pluginEnabledStatus.get(plugin).global = checkGlobal.isSelected();
                    }
                });
            }
            checkGlobal.setVerticalTextPosition(SwingConstants.TOP);
            String longText = plugin.getDetailedDescription();
            if (longText != null)
                checkGlobal.setToolTipText("<html>" + longText + "</html>");
            pluginEnabledStatus.put(plugin, enabled);
           
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.fill = GridBagConstraints.BOTH;
            gbc.weightx = 1;
            gbc.gridx = 1;
            gbc.insets = new Insets(0,5,0,5);
            gbc.gridy = added+1;
            gbc.anchor = GridBagConstraints.NORTHWEST;
            pluginPanelCenter.add(checkGlobal, gbc);

            if (currentProject != null && !cannotDisable) {
                final JComboBox combo = new WideComboBox(new Object[]{"DEFAULT", "DISABLED", "ENABLED"});
                if (enabled.project == null) combo.setSelectedIndex(0);
                else combo.setSelectedIndex(enabled.project ? 2 : 1);
                combo.setRenderer(new DefaultListCellRenderer() {
                    @Override
                    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                        if (index == -1) {
                            if (value.equals("DEFAULT")) value = "Default";
                            if (value.equals("DISABLED")) value = "Disabled";
                            if (value.equals("ENABLED")) value = "Enabled";
                        } else {
                            if (value.equals("DEFAULT")) value = "Default (use global setting)";
                            if (value.equals("DISABLED")) value = "Disabled for this project";
                            if (value.equals("ENABLED")) value = "Enabled for this project";
                        }
                        return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                    }
                });
                combo.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        Boolean[] array = { null, false, true };
                        int i = combo.getSelectedIndex();
                        if (i < 0 || i > 2)
                            return; // ??
                        pluginEnabledStatus.get(plugin).project = array[i];
                    }
                });
                gbc.gridx = 2;
                gbc.fill = GridBagConstraints.NONE;
                gbc.weightx = 0;
                pluginPanelCenter.add(combo, gbc);
            }
            added++;
        }
        if (added == 0) {
            JLabel label = new JLabel("<html>No plugins are loaded.<br> " +
                    "Try installing <u><font color=blue>fb-contrib</font></u> - or write your own<br>" +
                    "plugin for your project's needs!");
            label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            label.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    try {
                        LaunchBrowser.showDocument(new URL("https://sourceforge.net/projects/fb-contrib/"));
                    } catch (MalformedURLException e1) {
                        throw new IllegalStateException(e1);
                    }
                }
            });
            label.setBorder(new EmptyBorder(10,10,10,10));
            pluginPanelCenter.add(label);
        }
        PreferencesFrame.this.pack();
    }

    private void addField(JPanel p, GridBagConstraints c, int y, String lbl, JComponent field) {
        c.gridy = y;
         JLabel l = new JLabel(lbl, JLabel.TRAILING);
        l.setLabelFor(field);
        c.anchor = GridBagConstraints.LINE_END;
        c.gridx = 0;
        p.add(l, c);
        c.anchor = GridBagConstraints.LINE_START;
        c.gridx = 1;
        p.add(field, c);
    }
    private JPanel createPropertiesPane() {
        JPanel contentPanel = new JPanel(new BorderLayout());
        JPanel mainPanel = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.ipadx = c.ipady = 5;
        

        float currFS = Driver.getFontSize();

        tabTextField = new JTextField(Integer.toString(GUISaveState.getInstance().getTabSize()));
        tabTextField.setPreferredSize(new Dimension((int) (currFS * 4), (int) (currFS * 2)));
        addField(mainPanel, c, 0, "Tab size", tabTextField);
        
        fontTextField = new JTextField(Float.toString(GUISaveState.getInstance().getFontSize()));
        fontTextField.setPreferredSize(new Dimension((int) (currFS * 6), (int) (currFS * 2)));
        addField(mainPanel, c, 1, "Font size", fontTextField);

        packagePrefixLengthTextField = new JTextField(Integer.toString(GUISaveState.getInstance().getPackagePrefixSegments()));
        packagePrefixLengthTextField.setPreferredSize(new Dimension((int) (currFS * 4), (int) (currFS * 2)));
        addField(mainPanel, c, 2, "Package prefix length", packagePrefixLengthTextField);

        contentPanel.add(mainPanel, BorderLayout.CENTER);

        JPanel bottomPanel = new JPanel();
        bottomPanel.add(new JButton(new AbstractAction("Apply") {
            public void actionPerformed(ActionEvent evt) {
                changeTabSize();
                changeFontSize();
                changePackagePrefixLength();
            }
        }));

        bottomPanel.add(new JButton(new AbstractAction("Reset") {
            public void actionPerformed(ActionEvent evt) {
                resetPropertiesPane();
            }
        }));

        contentPanel.add(bottomPanel, BorderLayout.SOUTH);

        return contentPanel;
    }

    static class PluginWithDescription {

        public PluginWithDescription(String description, CloudPlugin plugin) {
            this.description = description;
            this.plugin = plugin;
        }
        final String description;
        final CloudPlugin plugin;
        @Override
        public String toString() {
            return description;
        }
        
    }
    private void changeTabSize() {
        int tabSize;

        try {
            tabSize = Integer.decode(tabTextField.getText());
        } catch (NumberFormatException exc) {
            JOptionPane.showMessageDialog(instance, "Error in tab size field.", "Tab Size Error",
                            JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (tabSize < TAB_MIN || tabSize > TAB_MAX) {
            JOptionPane.showMessageDialog(instance, "Tab size exceedes range (" + TAB_MIN + " - " + TAB_MAX + ").",
                    "Tab Size Excedes Range", JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (tabSize != GUISaveState.getInstance().getTabSize()) {
            GUISaveState.getInstance().setTabSize(tabSize);
            MainFrame.getInstance().getSourceCodeDisplayer().clearCache();
            // This causes the GUI to redisplay the current code
            MainFrame.getInstance().syncBugInformation();
        }
    }

    private void changeFontSize() {
        float fontSize;

        try {
            fontSize = Float.parseFloat(fontTextField.getText());
        } catch (NumberFormatException exc) {
            JOptionPane.showMessageDialog(instance, "Error in font size field.", "Font Size Error",
                    JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (fontSize < FONT_MIN || fontSize > FONT_MAX) {
            JOptionPane.showMessageDialog(instance, "Font size exceedes range (" + FONT_MIN + " - " + FONT_MAX + ").",
                    "Font Size Exceedes Range", JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (fontSize != GUISaveState.getInstance().getFontSize()) {
            GUISaveState.getInstance().setFontSize(fontSize);
            JOptionPane.showMessageDialog(instance, "To implement the new font size please restart FindBugs.",
                    "Changing Font",
                    JOptionPane.INFORMATION_MESSAGE);
        }
    }

    private void changePackagePrefixLength() {
        int value;

        try {
            value = Integer.parseInt(packagePrefixLengthTextField.getText());
        } catch (NumberFormatException exc) {
            JOptionPane.showMessageDialog(instance, "Error in package prefix length field.", "Package Prefix Length Error",
                    JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (value < 1 || value > 12) {
            JOptionPane.showMessageDialog(instance, "package prefix length exceedes range (" + 1 + " - " + 12 + ").",
                    "package prefix lengthe exceedes range", JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        if (value != GUISaveState.getInstance().getPackagePrefixSegments()) {
            GUISaveState.getInstance().setPackagePrefixSegments(value);
            BugTreeModel bt = (BugTreeModel) (MainFrame.getInstance().getTree().getModel());
            bt.needToRebuild();
            bt.checkSorter();
        }

    }

    private void resetPropertiesPane() {
        tabTextField.setText(Integer.toString(GUISaveState.getInstance().getTabSize()));
        fontTextField.setText(Float.toString(GUISaveState.getInstance().getFontSize()));
        rebuildPluginCheckboxes();
    }

    /**
     * Create a JPanel to display the filtering controls.
     */
    private JPanel createFilterPane() {
        JButton addButton = new JButton(edu.umd.cs.findbugs.L10N.getLocalString("dlg.add_dot_btn", "Add..."));
        JButton removeButton = new JButton(edu.umd.cs.findbugs.L10N.getLocalString("dlg.remove_btn", "Remove"));
        JButton removeAllButton = new JButton(edu.umd.cs.findbugs.L10N.getLocalString("dlg.remove_all_btn", "Remove All"));
        JPanel filterPanel = new JPanel();
        filterPanel.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridheight = 1;
        gbc.gridwidth = 1;
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weightx = 1;
        gbc.weighty = 1;
        filterPanel.add(new JLabel("<HTML>These rules control which bugs are shown and which are hidden in this project"), gbc);

        gbc.gridheight = 4;
        gbc.gridwidth = 1;
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weightx = 1;
        gbc.weighty = 1;
        filterPanel.add(new JScrollPane(filterCheckBoxList), gbc);
        updateFilterPanel();

        gbc.gridheight = 1;
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 0;
        gbc.weighty = 0;
        filterPanel.add(addButton, gbc);
        addButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                NewFilterFrame.open();
            }
        });

        gbc.gridy = 2;
        gbc.insets = new Insets(5, 0, 0, 0);
        filterPanel.add(removeButton, gbc);
        removeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                Object[] selected = filterCheckBoxList.getSelectedValues();
                if (selected.length == 0)
                    return;
                for (Object o : selected) {
                    MatchBox box = (MatchBox) o;
                    MainFrame.getInstance().getProject().getSuppressionFilter().removeChild(box.getMatcher());
                }
                FilterActivity.notifyListeners(FilterListener.Action.UNFILTERING, null);
                MainFrame.getInstance().setProjectChanged(true);
                updateFilterPanel();
            }
        });
        gbc.gridy = 3;
        gbc.weighty = 0;
        gbc.insets = new Insets(5, 0, 0, 0);
        filterPanel.add(removeAllButton, gbc);
        removeAllButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                boolean needsRebuild = false;
                Filter suppressionFilter = MainFrame.getInstance().getProject().getSuppressionFilter();
                if (!suppressionFilter.isEmpty())
                    needsRebuild = true;
                suppressionFilter.clear();

                if (needsRebuild)// TODO This will rebuild even if all the
                                 // filters being cleared were disabled
                    FilterActivity.notifyListeners(FilterListener.Action.UNFILTERING, null);
                MainFrame.getInstance().setProjectChanged(true);
                updateFilterPanel();
            }
        });
        gbc.gridy = 4;
        gbc.weighty = 1;
        gbc.insets = new Insets(0, 0, 0, 0);
        filterPanel.add(Box.createGlue(), gbc);

        return filterPanel;
    }

    private static class MatchBox extends JCheckBox {
        Matcher m;

        MatchBox(String text, Matcher m) {
            super(text);
            this.m = m;
        }

        Matcher getMatcher() {
            return m;
        }
    }

    void updateFilterPanel() {
        ArrayList<MatchBox> boxes = new ArrayList<MatchBox>();
        final Filter f = MainFrame.getInstance().getProject().getSuppressionFilter();

        for (final Matcher m : f.getChildren()) {
            MatchBox box = new MatchBox(m.toString(), m);
            box.addItemListener(new ItemListener() {
                public void itemStateChanged(ItemEvent evt) {
                    boolean isSelected = ((JCheckBox) evt.getSource()).isSelected();
                    boolean wasSelected = f.isEnabled(m);
                    if (isSelected == wasSelected)
                        return;
                    f.setEnabled(m, isSelected);
                    updateFilters(isSelected);

                }
            });
            box.setSelected(f.isEnabled(m));
            boxes.add(box);
        }

        filterCheckBoxList.setListData(boxes.toArray(new MatchBox[boxes.size()]));
    }


    public static void updateFilters(boolean addedFilter) {
        FilterActivity.notifyListeners(addedFilter ? FilterListener.Action.FILTERING
                : FilterListener.Action.UNFILTERING, null);
        MainFrame.getInstance().setProjectChanged(true);
    }

    private class UninstallClickListener implements ActionListener {
        private final Plugin plugin;
        private final URL url;

        public UninstallClickListener(Plugin plugin, URL url) {
            this.plugin = plugin;
            this.url = url;
        }

        public void actionPerformed(ActionEvent e) {
            int result = JOptionPane.showOptionDialog(PreferencesFrame.this,
                    "Are you sure you want to uninstall " + plugin.getShortDescription() + "?" +
                            "\n\nNo files will be deleted from your computer.", "",
                    JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                    new Object[]{"Uninstall", "Cancel"}, "Cancel");
            if (result == 0) {
                if (!GUISaveState.getInstance().removeCustomPlugin(url)) {
                    if ("file".equals(url.getProtocol())) {
                        String path = url.toExternalForm();
                        try {
                            path = new File(URLDecoder.decode(url.getPath(), "UTF-8")).getAbsolutePath();
                        } catch (UnsupportedEncodingException ex) {
                        }
                        try {
                            StringSelection contents = new StringSelection(path);
                            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(contents, contents);
                        } catch (Exception e1) {
                        }
                        JOptionPane.showMessageDialog(PreferencesFrame.this,
                                "The plugin could not be uninstalled automatically.\n\n" +
                                        "You can try to delete this plugin manually: \n"
                                        + path + "\n\n(This path has been copied to your clipboard)",
                                "Error", JOptionPane.ERROR_MESSAGE);
                    } else {
                        JOptionPane.showMessageDialog(PreferencesFrame.this,
                                "This plugin is not actually in the list of plugins...\n" +
                                        "Not sure what to do...\n "
                                        + url.toExternalForm()
                                        + "\n\nPlugin URL's:\n" +
                                        GUISaveState.getInstance().getCustomPlugins(),
                                "Error", JOptionPane.ERROR_MESSAGE);
                    }
                } else {
                    JOptionPane.showMessageDialog(PreferencesFrame.this,
                            "Changes will take effect after you restart FindBugs.");
                }
            }
        }
    }
}
