/* * Copyright 2000-2017 JetBrains s.r.o. * * 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.intellij.codeInsight.intention.impl.config; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.ui.search.SearchUtil; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.util.Ref; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.packageDependencies.ui.TreeExpansionMonitor; import com.intellij.ui.*; import com.intellij.util.ArrayUtil; import com.intellij.util.TimeoutUtil; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.*; import java.util.*; import java.util.List; public abstract class IntentionSettingsTree { private JComponent myComponent; private CheckboxTree myTree; private FilterComponent myFilter; private final Map<IntentionActionMetaData, Boolean> myIntentionToCheckStatus = new HashMap<>(); private JPanel myNorthPanel; protected IntentionSettingsTree() { initTree(); } public JTree getTree(){ return myTree; } public JComponent getComponent() { return myComponent; } private void initTree() { myTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer(true) { @Override public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (!(value instanceof CheckedTreeNode)) return; CheckedTreeNode node = (CheckedTreeNode)value; SimpleTextAttributes attributes = node.getUserObject() instanceof IntentionActionMetaData ? SimpleTextAttributes.REGULAR_ATTRIBUTES : SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; final String text = getNodeText(node); final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); UIUtil.changeBackGround(this, background); if (text != null) { SearchUtil.appendFragments(myFilter != null ? myFilter.getFilter() : null, text, attributes.getStyle(), attributes.getFgColor(), background, getTextRenderer()); } } }, new CheckedTreeNode(null)); myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getPath(); Object userObject = ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObject(); selectionChanged(userObject); } }); myFilter = new MyFilterComponent(); myComponent = new JPanel(new BorderLayout()); JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); myNorthPanel = new JPanel(new BorderLayout()); myNorthPanel.add(myFilter, BorderLayout.CENTER); final DefaultActionGroup group = new DefaultActionGroup(); final CommonActionsManager actionManager = CommonActionsManager.getInstance(); final DefaultTreeExpander treeExpander = new DefaultTreeExpander(myTree); group.add(actionManager.createExpandAllAction(treeExpander, myTree)); group.add(actionManager.createCollapseAllAction(treeExpander, myTree)); myNorthPanel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(), BorderLayout.WEST); myComponent.add(myNorthPanel, BorderLayout.NORTH); myComponent.add(scrollPane, BorderLayout.CENTER); myFilter.reset(); } protected abstract void selectionChanged(Object selected); protected abstract List<IntentionActionMetaData> filterModel(String filter, final boolean force); public void filter(List<IntentionActionMetaData> intentionsToShow) { refreshCheckStatus((CheckedTreeNode)myTree.getModel().getRoot()); reset(intentionsToShow); } public void reset(){ while (((IntentionManagerImpl)IntentionManager.getInstance()).hasActiveRequests()) { TimeoutUtil.sleep(100); } resetCheckStatus(); reset(IntentionManagerSettings.getInstance().getMetaData()); } private void resetCheckStatus() { myIntentionToCheckStatus.clear(); IntentionManagerSettings manager = IntentionManagerSettings.getInstance(); for (IntentionActionMetaData metaData : manager.getMetaData()) { myIntentionToCheckStatus.put(metaData, manager.isEnabled(metaData)); } } private void reset(List<IntentionActionMetaData> intentionsToShow) { CheckedTreeNode root = new CheckedTreeNode(null); final DefaultTreeModel treeModel = (DefaultTreeModel)myTree.getModel(); intentionsToShow = sort(intentionsToShow); for (final IntentionActionMetaData metaData : intentionsToShow) { String[] category = metaData.myCategory; CheckedTreeNode node = root; for (final String name : category) { CheckedTreeNode child = findChild(node, name); if (child == null) { CheckedTreeNode newChild = new CheckedTreeNode(name); treeModel.insertNodeInto(newChild, node, node.getChildCount()); child = newChild; } node = child; } CheckedTreeNode newChild = new CheckedTreeNode(metaData); treeModel.insertNodeInto(newChild, node, node.getChildCount()); } resetCheckMark(root); treeModel.setRoot(root); treeModel.nodeChanged(root); TreeUtil.expandAll(myTree); myTree.setSelectionRow(0); } public void selectIntention(String familyName) { final CheckedTreeNode child = findChildRecursively(getRoot(), familyName); if (child != null) { final TreePath path = new TreePath(child.getPath()); TreeUtil.selectPath(myTree, path); } } private static List<IntentionActionMetaData> sort(final List<IntentionActionMetaData> intentionsToShow) { List<IntentionActionMetaData> copy = new ArrayList<>(intentionsToShow); Collections.sort(copy, (data1, data2) -> { String[] category1 = data1.myCategory; String[] category2 = data2.myCategory; int result = ArrayUtil.lexicographicCompare(category1, category2); if (result!= 0) { return result; } return data1.getFamily().compareTo(data2.getFamily()); }); return copy; } private CheckedTreeNode getRoot() { return (CheckedTreeNode)myTree.getModel().getRoot(); } private boolean resetCheckMark(final CheckedTreeNode root) { Object userObject = root.getUserObject(); if (userObject instanceof IntentionActionMetaData) { IntentionActionMetaData metaData = (IntentionActionMetaData)userObject; Boolean b = myIntentionToCheckStatus.get(metaData); boolean enabled = b == null ? false : b.booleanValue(); root.setChecked(enabled); return enabled; } else { root.setChecked(false); visitChildren(root, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { if (resetCheckMark(node)) { root.setChecked(true); } } }); return root.isChecked(); } } private static CheckedTreeNode findChild(CheckedTreeNode node, final String name) { final Ref<CheckedTreeNode> found = new Ref<>(); visitChildren(node, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { String text = getNodeText(node); if (name.equals(text)) { found.set(node); } } }); return found.get(); } private static CheckedTreeNode findChildRecursively(CheckedTreeNode node, final String name) { final Ref<CheckedTreeNode> found = new Ref<>(); visitChildren(node, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { if (found.get() != null) return; final Object userObject = node.getUserObject(); if (userObject instanceof IntentionActionMetaData) { String text = getNodeText(node); if (name.equals(text)) { found.set(node); } } else { final CheckedTreeNode child = findChildRecursively(node, name); if (child != null) { found.set(child); } } } }); return found.get(); } private static String getNodeText(CheckedTreeNode node) { final Object userObject = node.getUserObject(); String text; if (userObject instanceof String) { text = (String)userObject; } else if (userObject instanceof IntentionActionMetaData) { text = ((IntentionActionMetaData)userObject).getFamily(); } else { text = "???"; } return text; } public void apply() { CheckedTreeNode root = getRoot(); apply(root); } private void refreshCheckStatus(final CheckedTreeNode root) { Object userObject = root.getUserObject(); if (userObject instanceof IntentionActionMetaData) { IntentionActionMetaData actionMetaData = (IntentionActionMetaData)userObject; myIntentionToCheckStatus.put(actionMetaData, root.isChecked()); } else { visitChildren(root, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { refreshCheckStatus(node); } }); } } private static void apply(CheckedTreeNode root) { Object userObject = root.getUserObject(); if (userObject instanceof IntentionActionMetaData) { IntentionActionMetaData actionMetaData = (IntentionActionMetaData)userObject; IntentionManagerSettings.getInstance().setEnabled(actionMetaData, root.isChecked()); } else { visitChildren(root, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { apply(node); } }); } } public boolean isModified() { return isModified(getRoot()); } private static boolean isModified(CheckedTreeNode root) { Object userObject = root.getUserObject(); if (userObject instanceof IntentionActionMetaData) { IntentionActionMetaData actionMetaData = (IntentionActionMetaData)userObject; boolean enabled = IntentionManagerSettings.getInstance().isEnabled(actionMetaData); return enabled != root.isChecked(); } else { final boolean[] modified = new boolean[] { false }; visitChildren(root, new CheckedNodeVisitor() { @Override public void visit(CheckedTreeNode node) { modified[0] |= isModified(node); } }); return modified[0]; } } public void dispose() { myFilter.dispose(); } public void setFilter(String filter){ myFilter.setFilter(filter); } public String getFilter() { return myFilter.getFilter(); } interface CheckedNodeVisitor { void visit(CheckedTreeNode node); } private static void visitChildren(CheckedTreeNode node, CheckedNodeVisitor visitor) { Enumeration children = node.children(); while (children.hasMoreElements()) { final CheckedTreeNode child = (CheckedTreeNode)children.nextElement(); visitor.visit(child); } } private class MyFilterComponent extends FilterComponent { private final TreeExpansionMonitor<DefaultMutableTreeNode> myExpansionMonitor = TreeExpansionMonitor.install(myTree); public MyFilterComponent() { super("INTENTION_FILTER_HISTORY", 10); } @Override public void filter() { final String filter = getFilter(); if (filter != null && filter.length() > 0) { if (!myExpansionMonitor.isFreeze()) { myExpansionMonitor.freeze(); } } IntentionSettingsTree.this.filter(filterModel(filter, true)); if (myTree != null) { List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(myTree); ((DefaultTreeModel)myTree.getModel()).reload(); TreeUtil.restoreExpandedPaths(myTree, expandedPaths); } SwingUtilities.invokeLater(() -> { myTree.setSelectionRow(0); IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); }); TreeUtil.expandAll(myTree); if (filter == null || filter.length() == 0) { TreeUtil.collapseAll(myTree, 0); myExpansionMonitor.restore(); } } @Override protected void onlineFilter() { final String filter = getFilter(); if (filter != null && filter.length() > 0) { if (!myExpansionMonitor.isFreeze()) { myExpansionMonitor.freeze(); } } IntentionSettingsTree.this.filter(filterModel(filter, true)); TreeUtil.expandAll(myTree); if (filter == null || filter.length() == 0) { TreeUtil.collapseAll(myTree, 0); myExpansionMonitor.restore(); } } } public JPanel getToolbarPanel() { return myNorthPanel; } }