com.sixrr.metrics.ui.dialogs.MetricsConfigurationDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.sixrr.metrics.ui.dialogs.MetricsConfigurationDialog.java

Source

/*
 * Copyright 2005-2016 Sixth and Red River Software, Bas Leijdekkers
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.sixrr.metrics.ui.dialogs;

import com.intellij.icons.AllIcons;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.FilterComponent;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.containers.Convertor;
import com.sixrr.metrics.Metric;
import com.sixrr.metrics.MetricCategory;
import com.sixrr.metrics.profile.MetricInstance;
import com.sixrr.metrics.utils.MetricsCategoryNameUtil;
import com.sixrr.metrics.profile.MetricsProfile;
import com.sixrr.metrics.profile.MetricsProfileRepository;
import com.sixrr.metrics.ui.SearchUtil;
import com.sixrr.metrics.utils.MetricsReloadedBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.Document;
import javax.swing.text.NumberFormatter;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * todo if ok or apply is not pressed do not add or remove profiles!
 * todo use inspection like tree/renderer
 * todo pretty highlighting when filtering
 * todo resizeability/splitter
 */
public class MetricsConfigurationDialog extends DialogWrapper implements TreeSelectionListener {
    private static final Logger logger = Logger.getInstance("MetricsReloaded");

    private JComboBox profilesDropdown;
    private JTextPane descriptionTextArea;
    private JButton deleteButton;
    private JButton saveAsButton;
    private JPanel contentPanel;
    private JFormattedTextField upperThresholdField;
    private JCheckBox upperThresholdEnabledCheckbox;
    private JFormattedTextField lowerThresholdField;
    private JCheckBox lowerThresholdEnabledCheckbox;
    @NonNls
    private JLabel urlLabel;
    private JButton resetButton;
    private ActionToolbarImpl treeToolbar;
    private MetricInstance selectedMetricInstance = null;

    private final MetricsProfileRepository repository;
    @Nullable
    private MetricsProfile profile;
    private boolean currentProfileIsModified = false;
    private JBScrollPane treeScrollPane;
    private FilterComponent filterComponent;
    private Tree metricsTree;

    private final Action applyAction = new ApplyAction();

    public MetricsConfigurationDialog(Project project, MetricsProfileRepository repository) {
        super(project, true);
        this.repository = repository;
        profile = repository.getCurrentProfile();
        setupMetricsTree();

        setDescriptionFromResource("/metricsDescriptions/Blank.html");
        setupProfilesDropdown();
        setupDeleteButton();
        setupAddButton();
        setupResetButton();
        setupLowerThresholdEnabledButton();
        setupLowerThresholdField();
        setupUpperThresholdEnabledButton();
        setupUpperThresholdField();
        setupURLLabel();
        toggleDeleteButton();
        applyAction.setEnabled(false);
        resetButton.setEnabled(false);
        lowerThresholdField.setEnabled(false);
        upperThresholdField.setEnabled(false);
        lowerThresholdEnabledCheckbox.setEnabled(false);
        upperThresholdEnabledCheckbox.setEnabled(false);
        urlLabel.setText("");
        init();
        setTitle(MetricsReloadedBundle.message("metrics.profiles"));
    }

    private void markProfileClean() {
        currentProfileIsModified = false;
        resetButton.setEnabled(false);
        applyAction.setEnabled(false);
    }

    private void markProfileDirty() {
        currentProfileIsModified = true;
        resetButton.setEnabled(true);
        applyAction.setEnabled(true);
    }

    private void setupLowerThresholdField() {
        final NumberFormat format = NumberFormat.getIntegerInstance();
        format.setParseIntegerOnly(true);
        final DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(new NumberFormatter(format));
        lowerThresholdField.setFormatterFactory(formatterFactory);

        final DocumentListener listener = new DocumentListener() {
            @Override
            public void changedUpdate(DocumentEvent e) {
                textChanged();
            }

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

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

            private void textChanged() {
                try {
                    lowerThresholdField.commitEdit();
                } catch (ParseException ignore) {
                    return;
                }
                final Number value = (Number) lowerThresholdField.getValue();
                if (value == null) {
                    return;
                }
                final double newValue = value.doubleValue();
                final double currentValue = selectedMetricInstance.getLowerThreshold();
                if (Math.abs(currentValue - newValue) <= 0.001) {
                    return;
                }
                selectedMetricInstance.setLowerThreshold(newValue);
                markProfileDirty();
            }
        };
        final Document thresholdDocument = lowerThresholdField.getDocument();
        thresholdDocument.addDocumentListener(listener);
    }

    private void setupUpperThresholdField() {
        final NumberFormat format = NumberFormat.getIntegerInstance();
        format.setParseIntegerOnly(true);
        final DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(new NumberFormatter(format));
        upperThresholdField.setFormatterFactory(formatterFactory);

        final DocumentListener listener = new DocumentListener() {
            @Override
            public void changedUpdate(DocumentEvent e) {
                textChanged();
            }

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

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

            private void textChanged() {
                try {
                    upperThresholdField.commitEdit();
                } catch (ParseException ignore) {
                    return;
                }
                final Number value = (Number) upperThresholdField.getValue();
                if (value == null) {
                    return;
                }
                final double threshold = value.doubleValue();
                if (Math.abs(selectedMetricInstance.getUpperThreshold() - threshold) <= 0.001) {
                    return;
                }
                selectedMetricInstance.setUpperThreshold(threshold);
                markProfileDirty();
            }
        };
        final Document thresholdDocument = upperThresholdField.getDocument();
        thresholdDocument.addDocumentListener(listener);
    }

    private void setupLowerThresholdEnabledButton() {
        final ButtonModel checkboxModel = lowerThresholdEnabledCheckbox.getModel();
        checkboxModel.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                if (selectedMetricInstance != null) {
                    final boolean selected = checkboxModel.isSelected();
                    if (selectedMetricInstance.isLowerThresholdEnabled() != selected) {
                        selectedMetricInstance.setLowerThresholdEnabled(selected);
                        markProfileDirty();
                    }
                    lowerThresholdField.setEnabled(selected && selectedMetricInstance.isEnabled());
                }
            }
        });
    }

    private void setupUpperThresholdEnabledButton() {
        final ButtonModel checkboxModel = upperThresholdEnabledCheckbox.getModel();
        checkboxModel.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                if (selectedMetricInstance != null) {
                    final boolean selected = checkboxModel.isSelected();
                    if (selectedMetricInstance.isUpperThresholdEnabled() != selected) {
                        selectedMetricInstance.setUpperThresholdEnabled(selected);
                        markProfileDirty();
                    }
                    upperThresholdField.setEnabled(selected && selectedMetricInstance.isEnabled());
                }
            }
        });
    }

    private void setupMetricsTree() {
        metricsTree = new MetricsTree();
        treeScrollPane.setViewportView(metricsTree);
        populateTree("");
        final MyTreeCellRenderer renderer = new MyTreeCellRenderer();
        metricsTree.setCellRenderer(renderer);
        metricsTree.setRootVisible(true);
        metricsTree.setShowsRootHandles(false);
        metricsTree.putClientProperty("JTree.lineStyle", "Angled");

        metricsTree.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {
                    final TreePath treePath = metricsTree.getLeadSelectionPath();
                    final MetricTreeNode node = (MetricTreeNode) treePath.getLastPathComponent();
                    toggleNode(metricsTree, node);
                    e.consume();
                }
            }
        });
        //noinspection ResultOfObjectAllocationIgnored
        new TreeSpeedSearch(metricsTree, new Convertor<TreePath, String>() {
            @Override
            public String convert(TreePath treePath) {
                final DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                final Object userObject = node.getUserObject();
                if (userObject instanceof MetricInstance) {
                    final MetricInstance metricInstance = (MetricInstance) userObject;
                    return metricInstance.getMetric().getDisplayName();
                } else {
                    return userObject.toString();
                }
            }
        });
        metricsTree.setSelectionRow(0);
    }

    private void populateTree(String filter) {
        final MetricTreeNode root = new MetricTreeNode(MetricsReloadedBundle.message("metrics"), true);
        final Map<MetricCategory, MetricTreeNode> categoryNodes = new EnumMap<MetricCategory, MetricTreeNode>(
                MetricCategory.class);

        if (profile != null) {
            final List<MetricInstance> metrics = profile.getMetricInstances();
            final List<String> filterTokens = SearchUtil.tokenizeFilter(filter);
            for (final MetricInstance metricInstance : metrics) {
                final Metric metric = metricInstance.getMetric();
                if (!isMetricAccepted(metric, filterTokens)) {
                    continue;
                }
                final MetricCategory category = metric.getCategory();
                MetricTreeNode categoryNode = categoryNodes.get(category);
                if (categoryNode == null) {
                    categoryNode = new MetricTreeNode(MetricsCategoryNameUtil.getLongNameForCategory(category),
                            true);
                    root.add(categoryNode);
                    categoryNodes.put(category, categoryNode);
                }
                final MetricTreeNode metricNode = new MetricTreeNode(metricInstance, metricInstance.isEnabled());
                categoryNode.add(metricNode);
            }
        }

        final DefaultTreeModel treeModel = new DefaultTreeModel(root);
        metricsTree.setModel(treeModel);
        metricsTree.addTreeSelectionListener(this);
        final TreeSelectionModel selectionModel = metricsTree.getSelectionModel();
        selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

        for (int j = root.getChildCount() - 1; j >= 0; j--) {
            final MetricTreeNode categoryNode = (MetricTreeNode) root.getChildAt(j);
            if (categoryNode.getChildCount() == 0) {
                root.remove(categoryNode);
            }
        }
        final TreePath rootPath = new TreePath(root);
        metricsTree.expandPath(rootPath);
        for (MetricTreeNode categoryNode : categoryNodes.values()) {
            metricsTree.expandPath(rootPath.pathByAddingChild(categoryNode));
        }
    }

    private void rebindMetricsTree() {
        final TreeModel model = metricsTree.getModel();
        final MetricTreeNode root = (MetricTreeNode) model.getRoot();
        final int numCategories = root.getChildCount();
        for (int i = 0; i < numCategories; i++) {
            final MetricTreeNode category = (MetricTreeNode) root.getChildAt(i);
            final int numMetrics = category.getChildCount();
            for (int j = 0; j < numMetrics; j++) {
                final MetricTreeNode metricNode = (MetricTreeNode) category.getChildAt(j);
                final MetricInstance currentMetricInstance = (MetricInstance) metricNode.getUserObject();
                final MetricInstance newMetric = profile.getMetricInstance(currentMetricInstance.getMetric());
                metricNode.setUserObject(newMetric);
                assert newMetric != null;
                metricNode.enabled = newMetric.isEnabled();
            }
        }
        metricsTree.treeDidChange();
    }

    private void setupProfilesDropdown() {
        final String[] profiles = repository.getProfileNames();
        final MutableComboBoxModel profilesModel = new DefaultComboBoxModel(profiles);
        profilesDropdown.setModel(profilesModel);
        final MetricsProfile currentProfile = repository.getCurrentProfile();
        profilesDropdown.setSelectedItem(currentProfile.getName());
        toggleDeleteButton();
        profilesDropdown.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.DESELECTED) {
                    return;
                }
                final String selectedProfile = (String) profilesDropdown.getSelectedItem();
                final String currentProfileName = profile.getName();
                if (!selectedProfile.equals(currentProfileName)) {
                    repository.setSelectedProfile(selectedProfile);
                    profile = repository.getCurrentProfile();
                    markProfileClean();
                }
                rebindMetricsTree();
                toggleDeleteButton();
            }
        });
    }

    @Override
    protected void doOKAction() {
        super.doOKAction();
        if (currentProfileIsModified) {
            MetricsProfileRepository.persistProfile(profile);
        }
    }

    protected class ApplyAction extends DialogWrapperAction {

        private ApplyAction() {
            super(MetricsReloadedBundle.message("apply"));
        }

        @Override
        protected void doAction(ActionEvent e) {
            doApplyAction();
        }
    }

    protected void doApplyAction() {
        processDoNotAskOnOk(NEXT_USER_EXIT_CODE);
        MetricsProfileRepository.persistProfile(profile);
        markProfileClean();
    }

    @Override
    public void doCancelAction() {
        super.doCancelAction();
        repository.reloadProfileFromStorage(profile);
    }

    private void setupAddButton() {
        saveAsButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent event) {
                super.mousePressed(event);
                final JPopupMenu popup = new JPopupMenu();
                popup.add(new JMenuItem(new CopyProfileAction(repository)));
                popup.add(new JMenuItem(new NewProfileAction(repository)));
                popup.show(saveAsButton, 0, saveAsButton.getHeight());
            }
        });
    }

    public void updateSelection(String newProfileName) {
        markProfileClean();
        profile = repository.getCurrentProfile();
        profilesDropdown.addItem(newProfileName);
        profilesDropdown.setSelectedItem(newProfileName);
        rebindMetricsTree();
        toggleDeleteButton();
    }

    private void setupResetButton() {
        resetButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                repository.reloadProfileFromStorage(profile);
                markProfileClean();
                populateTree(filterComponent.getFilter());
            }
        });
    }

    private void setupURLLabel() {
        urlLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent event) {
                final String helpURL = selectedMetricInstance.getMetric().getHelpURL();
                if (helpURL != null) {
                    BrowserUtil.launchBrowser("http://" + helpURL);
                }
            }
        });
    }

    private void toggleDeleteButton() {
        //        deleteButton.setEnabled(repository.getProfileNames().length != 0);
        //        final boolean anyProfilesLeft = repository.getProfileNames().length != 0;
        //        if (!anyProfilesLeft) {
        //            deleteButton.setEnabled(anyProfilesLeft);
        //            return;
        //        }
        deleteButton.setEnabled(profile != null && !profile.isBuiltIn());
    }

    private void setupDeleteButton() {
        deleteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final String currentProfileName = profile.getName();
                repository.deleteProfile(profile);
                profile = repository.getCurrentProfile();
                markProfileClean();
                profilesDropdown.removeItem(currentProfileName);
                profilesDropdown.setSelectedItem(profile.getName());
                toggleDeleteButton();
                rebindMetricsTree();
            }
        });
    }

    private void selectMetric(MetricInstance metricInstance) {
        selectedMetricInstance = metricInstance;
        final Metric metric = metricInstance.getMetric();
        final String url = metric.getHelpURL();
        final String displayString = metric.getHelpDisplayString();
        if (url != null) {
            urlLabel.setText("<html><a href = \'" + url + "\'>" + displayString + "</a></html>");
        } else {
            urlLabel.setText("");
        }
        final boolean metricInstanceEnabled = metricInstance.isEnabled();

        final double upperThreshold = metricInstance.getUpperThreshold();
        final boolean upperThresholdEnabled = metricInstance.isUpperThresholdEnabled();
        upperThresholdEnabledCheckbox.setSelected(upperThresholdEnabled);
        upperThresholdEnabledCheckbox.setEnabled(metricInstanceEnabled);
        upperThresholdField.setValue(Double.valueOf(upperThreshold));
        upperThresholdField.setEnabled(upperThresholdEnabled && metricInstanceEnabled);

        final double lowerThreshold = metricInstance.getLowerThreshold();
        final boolean lowerThresholdEnabled = metricInstance.isLowerThresholdEnabled();
        lowerThresholdEnabledCheckbox.setSelected(lowerThresholdEnabled);
        lowerThresholdEnabledCheckbox.setEnabled(metricInstanceEnabled);
        lowerThresholdField.setValue(Double.valueOf(lowerThreshold));
        lowerThresholdField.setEnabled(lowerThresholdEnabled && metricInstanceEnabled);

        @NonNls
        final String descriptionName = "/metricsDescriptions/" + metric.getID() + ".html";
        setDescriptionFromResource(descriptionName, metric);
    }

    private void setDescriptionFromResource(@NonNls String resourceName) {
        try {
            final URL resourceURL = getClass().getResource(resourceName);
            descriptionTextArea.setPage(resourceURL);
        } catch (Exception e) {
            logger.error(e);
        }
    }

    private void setDescriptionFromResource(String resourceName, Metric metric) {
        try {
            final URL resourceURL = metric.getClass().getResource(resourceName);
            descriptionTextArea.setPage(resourceURL);
        } catch (Exception ignore) {
            setDescriptionFromResource("/metricsDescriptions/UnderConstruction.html");
        }
    }

    private void clearSelection() {
        selectedMetricInstance = null;
        lowerThresholdField.setEnabled(false);
        lowerThresholdField.setText("");
        lowerThresholdEnabledCheckbox.setEnabled(false);
        upperThresholdField.setText("");
        upperThresholdField.setEnabled(false);
        upperThresholdEnabledCheckbox.setEnabled(false);
        setDescriptionFromResource("/metricsDescriptions/Blank.html");
    }

    @Override
    public void valueChanged(TreeSelectionEvent e) {
        final TreePath selectionPath = e.getPath();
        final MetricTreeNode lastPathComponent = (MetricTreeNode) selectionPath.getLastPathComponent();
        final Object userObject = lastPathComponent.getUserObject();
        if (userObject instanceof MetricInstance) {
            selectMetric((MetricInstance) userObject);
        } else {
            clearSelection();
        }
    }

    @NotNull
    @Override
    public Action[] createActions() {
        if (SystemInfo.isMac) {
            return new Action[] { getCancelAction(), applyAction, getOKAction() };
        } else {
            return new Action[] { getOKAction(), applyAction, getCancelAction() };
        }
    }

    @Override
    public String getTitle() {
        return MetricsReloadedBundle.message("metrics.configuration.panel.title");
    }

    @Override
    @Nullable
    protected JComponent createCenterPanel() {
        return contentPanel;
    }

    @Override
    @NonNls
    protected String getDimensionServiceKey() {
        return "MetricsReloaded.MetricsConfigurationDialog";
    }

    private void toggleNode(JTree tree, MetricTreeNode node) {
        final Object userObject = node.getUserObject();
        if (userObject instanceof MetricInstance) {
            final MetricInstance tool = (MetricInstance) userObject;
            node.enabled = !node.enabled;
            if (node.enabled) {
                final MetricTreeNode parent = (MetricTreeNode) node.getParent();
                if (!parent.equals(tree.getModel().getRoot())) {
                    parent.enabled = true;
                }
                tool.setEnabled(true);
                upperThresholdEnabledCheckbox.setEnabled(true);
                lowerThresholdEnabledCheckbox.setEnabled(true);
            } else {
                tool.setEnabled(false);
                upperThresholdEnabledCheckbox.setEnabled(false);
                lowerThresholdEnabledCheckbox.setEnabled(false);
            }
            markProfileDirty();
        } else {
            node.enabled = !node.enabled;
            final Enumeration children = node.children();
            while (children.hasMoreElements()) {
                final MetricTreeNode child = (MetricTreeNode) children.nextElement();
                child.enabled = node.enabled;
                if (child.getUserObject() instanceof MetricInstance) {
                    ((MetricInstance) child.getUserObject()).setEnabled(node.enabled);
                } else {
                    final Enumeration grandchildren = child.children();
                    while (grandchildren.hasMoreElements()) {
                        final MetricTreeNode grandChild = (MetricTreeNode) grandchildren.nextElement();
                        grandChild.enabled = node.enabled;
                        if (grandChild.getUserObject() instanceof MetricInstance) {
                            ((MetricInstance) grandChild.getUserObject()).setEnabled(node.enabled);
                        }
                    }
                }
            }
        }
        tree.repaint();
    }

    public void createUIComponents() {
        filterComponent = new MyFilterComponent();
        final AnAction expandActon = new AnAction(MetricsReloadedBundle.message("expand.all.action"),
                MetricsReloadedBundle.message("expand.all.description"), AllIcons.Actions.Expandall) {
            @Override
            public void actionPerformed(AnActionEvent anActionEvent) {
                final MetricTreeNode root = (MetricTreeNode) metricsTree.getModel().getRoot();
                final TreePath rootPath = new TreePath(root);
                for (Enumeration e = root.children(); e.hasMoreElements();) {
                    final MetricTreeNode childNode = (MetricTreeNode) e.nextElement();
                    final TreePath path = rootPath.pathByAddingChild(childNode);
                    metricsTree.expandPath(path);
                }
            }
        };
        final AnAction collapseAction = new AnAction(MetricsReloadedBundle.message("collapse.all.action"),
                MetricsReloadedBundle.message("collapse.all.description"), AllIcons.Actions.Collapseall) {
            @Override
            public void actionPerformed(AnActionEvent anActionEvent) {
                final MetricTreeNode root = (MetricTreeNode) metricsTree.getModel().getRoot();
                final TreePath rootPath = new TreePath(root);
                for (Enumeration e = root.children(); e.hasMoreElements();) {
                    final MetricTreeNode childNode = (MetricTreeNode) e.nextElement();
                    final TreePath path = rootPath.pathByAddingChild(childNode);
                    metricsTree.collapsePath(path);
                }
            }
        };
        final ActionManager actionManager = ActionManager.getInstance();

        final DefaultActionGroup expandCollapseGroup = new DefaultActionGroup();
        expandCollapseGroup.add(expandActon);
        expandCollapseGroup.add(collapseAction);

        treeToolbar = (ActionToolbarImpl) actionManager.createActionToolbar("EXPAND_COLLAPSE_GROUP",
                expandCollapseGroup, true);
    }

    private class MyFilterComponent extends FilterComponent {

        private MyFilterComponent() {
            super("METRICS_FILTER_HISTORY", 10);
        }

        @Override
        public void filter() {
            populateTree(getFilter());
        }
    }

    private static class MyTreeCellRenderer extends JPanel implements TreeCellRenderer {

        private final JLabel myLabel;
        private final JCheckBox myCheckbox;

        @SuppressWarnings("OverridableMethodCallInConstructor")
        MyTreeCellRenderer() {
            super(new BorderLayout());
            myCheckbox = new JCheckBox();
            myLabel = new JLabel();
            add(myCheckbox, BorderLayout.WEST);
            add(myLabel, BorderLayout.CENTER);
        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
                boolean leaf, int row, boolean hasFocus) {
            final MetricTreeNode node = (MetricTreeNode) value;
            final Object object = node.getUserObject();

            myCheckbox.setSelected(node.enabled);

            myCheckbox.setBackground(UIManager.getColor("Tree.textBackground"));
            setBackground(UIManager.getColor(selected ? "Tree.selectionBackground" : "Tree.textBackground"));
            final Color foreground = UIManager
                    .getColor(selected ? "Tree.selectionForeground" : "Tree.textForeground");
            setForeground(foreground);
            myCheckbox.setForeground(foreground);
            myLabel.setForeground(foreground);
            myCheckbox.setEnabled(true);

            if (object instanceof MetricInstance) {
                final MetricInstance tool = (MetricInstance) object;
                myLabel.setFont(tree.getFont());
                myLabel.setText(tool.getMetric().getDisplayName());
            } else {
                final Font font = tree.getFont();
                final Font boldFont = new Font(font.getName(), Font.BOLD, font.getSize());
                myLabel.setFont(boldFont);
                myLabel.setText((String) object);
            }

            return this;
        }
    }

    private class MetricsTree extends Tree {

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            Dimension size = super.getPreferredScrollableViewportSize();
            size = new Dimension(size.width + 10, size.height);
            return size;
        }

        @Override
        protected void processMouseEvent(MouseEvent e) {
            if (e.getID() == MouseEvent.MOUSE_PRESSED) {
                final int row = getRowForLocation(e.getX(), e.getY());
                if (row >= 0) {
                    final Rectangle rowBounds = getRowBounds(row);
                    final MyTreeCellRenderer renderer = (MyTreeCellRenderer) getCellRenderer();
                    renderer.setBounds(rowBounds);
                    final Rectangle checkBounds = renderer.myCheckbox.getBounds();

                    checkBounds.setLocation(rowBounds.getLocation());

                    if (checkBounds.contains(e.getPoint())) {
                        final MetricTreeNode node = (MetricTreeNode) getPathForRow(row).getLastPathComponent();
                        toggleNode(this, node);
                        e.consume();
                        setSelectionRow(row);
                    }
                }
            }

            if (!e.isConsumed()) {
                super.processMouseEvent(e);
            }
        }
    }

    private static class MetricTreeNode extends DefaultMutableTreeNode {
        private boolean enabled;

        private MetricTreeNode(Object userObject, boolean enabled) {
            super(userObject);
            this.enabled = enabled;
        }
    }

    private static boolean isMetricAccepted(Metric metric, List<String> filterTokens) {
        String description = null;
        for (String filterToken : filterTokens) {
            if (StringUtil.containsIgnoreCase(metric.getAbbreviation(), filterToken)) {
                continue;
            }
            if (StringUtil.containsIgnoreCase(metric.getDisplayName(), filterToken)) {
                continue;
            }
            if (StringUtil.containsIgnoreCase(MetricsCategoryNameUtil.getLongNameForCategory(metric.getCategory()),
                    filterToken)) {
                continue;
            }
            if (description == null) {
                @NonNls
                final String descriptionName = "/metricsDescriptions/" + metric.getID() + ".html";
                try {
                    final InputStream resourceStream = metric.getClass().getResourceAsStream(descriptionName);
                    try {
                        if (resourceStream == null) {
                            description = "";
                        } else {
                            description = StringUtil.stripHtml(StreamUtil.readText(resourceStream, "UTF-8"), false);
                        }
                    } finally {
                        if (resourceStream != null) {
                            resourceStream.close();
                        } else {
                            logger.warn("no description found for " + metric.getID());
                        }
                    }
                } catch (IOException e) {
                    logger.warn("problem reading metric description", e);
                    return false;
                }
            }
            if (StringUtil.containsIgnoreCase(description, filterToken)) {
                continue;
            }
            return false;
        }
        return true;
    }

    private class CopyProfileAction extends AbstractAction {

        private final MetricsProfileRepository repository;

        CopyProfileAction(MetricsProfileRepository repository) {
            super(MetricsReloadedBundle.message("copy.profile.action"));
            this.repository = repository;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            final String newProfileName = Messages.showInputDialog(saveAsButton,
                    MetricsReloadedBundle.message("enter.new.profile.name"),
                    MetricsReloadedBundle.message("create.new.metrics.profile"), Messages.getQuestionIcon(),
                    repository.generateNewProfileName(), null);
            if (newProfileName == null) {
                return;
            }
            if (repository.profileExists(newProfileName)) {
                Messages.showErrorDialog(
                        MetricsReloadedBundle.message("unable.to.create.profile.dialog.message", newProfileName),
                        MetricsReloadedBundle.message("unable.to.create.profile.dialog.title"));
            } else {
                repository.duplicateCurrentProfile(newProfileName);
                updateSelection(newProfileName);
            }
        }
    }

    private class NewProfileAction extends AbstractAction {

        private final MetricsProfileRepository repository;

        NewProfileAction(MetricsProfileRepository repository) {
            super(MetricsReloadedBundle.message("new.profile.action"));
            this.repository = repository;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            final String newProfileName = Messages.showInputDialog(saveAsButton,
                    MetricsReloadedBundle.message("enter.new.profile.name"),
                    MetricsReloadedBundle.message("create.new.metrics.profile"), Messages.getQuestionIcon(),
                    repository.generateNewProfileName("Metrics"), null);
            if (newProfileName == null) {
                return;
            }
            if (repository.profileExists(newProfileName)) {
                Messages.showErrorDialog(
                        MetricsReloadedBundle.message("unable.to.create.profile.dialog.message", newProfileName),
                        MetricsReloadedBundle.message("unable.to.create.profile.dialog.title"));
            } else {
                repository.createEmptyProfile(newProfileName);
                updateSelection(newProfileName);
            }
        }
    }
}