| /* |
| * Copyright 2000-2012 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. |
| */ |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: max |
| * Date: Nov 4, 2001 |
| * Time: 5:19:35 PM |
| */ |
| package com.intellij.codeInspection.ui; |
| |
| import com.intellij.codeInspection.CommonProblemDescriptor; |
| import com.intellij.codeInspection.InspectionsBundle; |
| import com.intellij.codeInspection.ProblemDescriptor; |
| import com.intellij.codeInspection.ex.*; |
| import com.intellij.codeInspection.reference.RefElement; |
| import com.intellij.codeInspection.reference.RefEntity; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.vcs.FileStatus; |
| import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator; |
| import com.intellij.ui.ColoredTreeCellRenderer; |
| import com.intellij.ui.JBColor; |
| import com.intellij.ui.SimpleTextAttributes; |
| import com.intellij.ui.TreeSpeedSearch; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.TreeExpansionEvent; |
| import javax.swing.event.TreeSelectionEvent; |
| import javax.swing.event.TreeSelectionListener; |
| import javax.swing.event.TreeWillExpandListener; |
| import javax.swing.tree.DefaultTreeModel; |
| import javax.swing.tree.ExpandVetoException; |
| import javax.swing.tree.TreeNode; |
| import javax.swing.tree.TreePath; |
| import java.util.*; |
| |
| public class InspectionTree extends Tree { |
| private final HashSet<Object> myExpandedUserObjects; |
| @NotNull private final GlobalInspectionContextImpl myContext; |
| private SelectionPath mySelectionPath; |
| private static final ProblemDescriptor[] EMPTY_DESCRIPTORS = new ProblemDescriptor[0]; |
| |
| public InspectionTree(@NotNull Project project, @NotNull GlobalInspectionContextImpl context) { |
| super(new InspectionRootNode(project)); |
| myContext = context; |
| |
| setCellRenderer(new CellRenderer()); |
| setShowsRootHandles(true); |
| UIUtil.setLineStyleAngled(this); |
| addTreeWillExpandListener(new ExpandListener()); |
| |
| myExpandedUserObjects = new HashSet<Object>(); |
| myExpandedUserObjects.add(project); |
| |
| TreeUtil.installActions(this); |
| new TreeSpeedSearch(this, new Convertor<TreePath, String>() { |
| @Override |
| public String convert(TreePath o) { |
| return InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString()); |
| } |
| }); |
| |
| addTreeSelectionListener(new TreeSelectionListener() { |
| @Override |
| public void valueChanged(TreeSelectionEvent e) { |
| TreePath newSelection = e.getNewLeadSelectionPath(); |
| if (newSelection != null) { |
| mySelectionPath = new SelectionPath(newSelection); |
| } |
| } |
| }); |
| } |
| |
| public void removeAllNodes() { |
| getRoot().removeAllChildren(); |
| nodeStructureChanged(getRoot()); |
| } |
| |
| public InspectionTreeNode getRoot() { |
| return (InspectionTreeNode)getModel().getRoot(); |
| } |
| |
| @Nullable |
| public InspectionToolWrapper getSelectedToolWrapper() { |
| final TreePath[] paths = getSelectionPaths(); |
| if (paths == null) return null; |
| InspectionToolWrapper toolWrapper = null; |
| for (TreePath path : paths) { |
| Object[] nodes = path.getPath(); |
| for (int j = nodes.length - 1; j >= 0; j--) { |
| Object node = nodes[j]; |
| if (node instanceof InspectionNode) { |
| InspectionToolWrapper wrapper = ((InspectionNode)node).getToolWrapper(); |
| if (toolWrapper == null) { |
| toolWrapper = wrapper; |
| } |
| else if (toolWrapper != wrapper) { |
| return null; |
| } |
| break; |
| } |
| } |
| } |
| |
| return toolWrapper; |
| } |
| |
| @NotNull |
| public RefEntity[] getSelectedElements() { |
| TreePath[] selectionPaths = getSelectionPaths(); |
| if (selectionPaths != null) { |
| InspectionToolWrapper toolWrapper = getSelectedToolWrapper(); |
| if (toolWrapper == null) return RefEntity.EMPTY_ELEMENTS_ARRAY; |
| |
| List<RefEntity> result = new ArrayList<RefEntity>(); |
| for (TreePath selectionPath : selectionPaths) { |
| final InspectionTreeNode node = (InspectionTreeNode)selectionPath.getLastPathComponent(); |
| addElementsInNode(node, result); |
| } |
| return result.toArray(new RefEntity[result.size()]); |
| } |
| return RefEntity.EMPTY_ELEMENTS_ARRAY; |
| } |
| |
| private static void addElementsInNode(InspectionTreeNode node, List<RefEntity> out) { |
| if (!node.isValid()) return; |
| if (node instanceof RefElementNode) { |
| final RefEntity element = ((RefElementNode)node).getElement(); |
| if (!out.contains(element)) { |
| out.add(0, element); |
| } |
| } |
| if (node instanceof ProblemDescriptionNode) { |
| final RefEntity element = ((ProblemDescriptionNode)node).getElement(); |
| if (!out.contains(element)) { |
| out.add(0, element); |
| } |
| } |
| final Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| InspectionTreeNode child = (InspectionTreeNode)children.nextElement(); |
| addElementsInNode(child, out); |
| } |
| } |
| |
| public CommonProblemDescriptor[] getSelectedDescriptors() { |
| final InspectionToolWrapper toolWrapper = getSelectedToolWrapper(); |
| if (getSelectionCount() == 0) return EMPTY_DESCRIPTORS; |
| final TreePath[] paths = getSelectionPaths(); |
| final LinkedHashSet<CommonProblemDescriptor> descriptors = new LinkedHashSet<CommonProblemDescriptor>(); |
| for (TreePath path : paths) { |
| Object node = path.getLastPathComponent(); |
| traverseDescriptors((InspectionTreeNode)node, descriptors); |
| } |
| return descriptors.toArray(new CommonProblemDescriptor[descriptors.size()]); |
| } |
| |
| private static void traverseDescriptors(InspectionTreeNode node, LinkedHashSet<CommonProblemDescriptor> descriptors){ |
| if (node instanceof ProblemDescriptionNode) { |
| descriptors.add(((ProblemDescriptionNode)node).getDescriptor()); |
| } |
| for(int i = node.getChildCount() - 1; i >= 0; i--){ |
| traverseDescriptors((InspectionTreeNode)node.getChildAt(i), descriptors); |
| } |
| } |
| |
| private void nodeStructureChanged(InspectionTreeNode node) { |
| ((DefaultTreeModel)getModel()).nodeStructureChanged(node); |
| } |
| |
| private class ExpandListener implements TreeWillExpandListener { |
| @Override |
| public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { |
| final InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent(); |
| final Object userObject = node.getUserObject(); |
| //TODO: never re-sort |
| if (node.isValid() && !myExpandedUserObjects.contains(userObject)) { |
| sortChildren(node); |
| nodeStructureChanged(node); |
| } |
| myExpandedUserObjects.add(userObject); |
| // Smart expand |
| if (node.getChildCount() == 1) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| expandPath(new TreePath(node.getPath())); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { |
| InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent(); |
| myExpandedUserObjects.remove(node.getUserObject()); |
| } |
| } |
| |
| public void restoreExpansionAndSelection() { |
| restoreExpansionStatus((InspectionTreeNode)getModel().getRoot()); |
| if (mySelectionPath != null) { |
| mySelectionPath.restore(); |
| } |
| } |
| |
| private void restoreExpansionStatus(InspectionTreeNode node) { |
| if (myExpandedUserObjects.contains(node.getUserObject())) { |
| sortChildren(node); |
| TreeNode[] pathToNode = node.getPath(); |
| expandPath(new TreePath(pathToNode)); |
| Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| InspectionTreeNode childNode = (InspectionTreeNode)children.nextElement(); |
| restoreExpansionStatus(childNode); |
| } |
| } |
| } |
| |
| private static class CellRenderer extends ColoredTreeCellRenderer { |
| /* private Project myProject; |
| InspectionManagerEx myManager; |
| public CellRenderer(Project project) { |
| myProject = project; |
| myManager = (InspectionManagerEx)InspectionManager.getInstance(myProject); |
| }*/ |
| |
| @Override |
| public void customizeCellRenderer(JTree tree, |
| Object value, |
| boolean selected, |
| boolean expanded, |
| boolean leaf, |
| int row, |
| boolean hasFocus) { |
| InspectionTreeNode node = (InspectionTreeNode)value; |
| |
| append(node.toString(), |
| patchAttr(node, appearsBold(node) ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : getMainForegroundAttributes(node))); |
| |
| int problemCount = node.getProblemCount(); |
| if (!leaf) { |
| append(" " + InspectionsBundle.message("inspection.problem.descriptor.count", problemCount), patchAttr(node, SimpleTextAttributes.GRAYED_ATTRIBUTES)); |
| } |
| |
| if (!node.isValid()) { |
| append(" " + InspectionsBundle.message("inspection.invalid.node.text"), patchAttr(node, SimpleTextAttributes.ERROR_ATTRIBUTES)); |
| } else { |
| setIcon(node.getIcon(expanded)); |
| } |
| } |
| |
| public static SimpleTextAttributes patchAttr(InspectionTreeNode node, SimpleTextAttributes attributes) { |
| if (node.isResolved()) { |
| return new SimpleTextAttributes(attributes.getBgColor(), attributes.getFgColor(), attributes.getWaveColor(), attributes.getStyle() | SimpleTextAttributes.STYLE_STRIKEOUT); |
| } |
| return attributes; |
| } |
| |
| private static SimpleTextAttributes getMainForegroundAttributes(InspectionTreeNode node) { |
| SimpleTextAttributes foreground = SimpleTextAttributes.REGULAR_ATTRIBUTES; |
| if (node instanceof RefElementNode) { |
| RefEntity refElement = ((RefElementNode)node).getElement(); |
| |
| if (refElement instanceof RefElement) { |
| refElement = ((RefElement)refElement).getContainingEntry(); |
| if (((RefElement)refElement).isEntry() && ((RefElement)refElement).isPermanentEntry()) { |
| foreground = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.blue); |
| } |
| } |
| |
| } |
| final FileStatus nodeStatus = node.getNodeStatus(); |
| if (nodeStatus != FileStatus.NOT_CHANGED){ |
| foreground = new SimpleTextAttributes(foreground.getBgColor(), nodeStatus.getColor(), foreground.getWaveColor(), foreground.getStyle()); |
| } |
| return foreground; |
| } |
| |
| private static boolean appearsBold(Object node) { |
| return ((InspectionTreeNode)node).appearsBold(); |
| } |
| } |
| |
| private void sortChildren(InspectionTreeNode node) { |
| final List<TreeNode> children = TreeUtil.childrenToArray(node); |
| Collections.sort(children, InspectionResultsViewComparator.getInstance()); |
| node.removeAllChildren(); |
| TreeUtil.addChildrenTo(node, children); |
| ((DefaultTreeModel)getModel()).reload(node); |
| } |
| |
| private class SelectionPath { |
| private final Object[] myPath; |
| private final int[] myIndicies; |
| |
| public SelectionPath(TreePath path) { |
| myPath = path.getPath(); |
| myIndicies = new int[myPath.length]; |
| for (int i = 0; i < myPath.length - 1; i++) { |
| InspectionTreeNode node = (InspectionTreeNode)myPath[i]; |
| myIndicies[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); |
| } |
| } |
| |
| private int getChildIndex(InspectionTreeNode node, InspectionTreeNode child) { |
| int idx = 0; |
| Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| InspectionTreeNode ch = (InspectionTreeNode)children.nextElement(); |
| if (ch == child) break; |
| idx++; |
| } |
| return idx; |
| } |
| |
| public void restore() { |
| getSelectionModel().removeSelectionPaths(getSelectionModel().getSelectionPaths()); |
| TreeUtil.selectPath(InspectionTree.this, restorePath()); |
| } |
| |
| private TreePath restorePath() { |
| ArrayList<Object> newPath = new ArrayList<Object>(); |
| |
| newPath.add(getModel().getRoot()); |
| restorePath(newPath, 1); |
| |
| return new TreePath(newPath.toArray(new InspectionTreeNode[newPath.size()])); |
| } |
| |
| private void restorePath(ArrayList<Object> newPath, int idx) { |
| if (idx >= myPath.length) return; |
| InspectionTreeNode oldNode = (InspectionTreeNode)myPath[idx]; |
| |
| InspectionTreeNode newRoot = (InspectionTreeNode)newPath.get(idx - 1); |
| |
| |
| InspectionResultsViewComparator comparator = InspectionResultsViewComparator.getInstance(); |
| Enumeration children = newRoot.children(); |
| while (children.hasMoreElements()) { |
| InspectionTreeNode child = (InspectionTreeNode)children.nextElement(); |
| if (comparator.compare(child, oldNode) == 0) { |
| newPath.add(child); |
| restorePath(newPath, idx + 1); |
| return; |
| } |
| } |
| |
| // Exactly same element not found. Trying to select somewhat near. |
| int count = newRoot.getChildCount(); |
| if (count > 0) { |
| if (myIndicies[idx] < count) { |
| newPath.add(newRoot.getChildAt(myIndicies[idx])); |
| } |
| else { |
| newPath.add(newRoot.getChildAt(count - 1)); |
| } |
| } |
| } |
| } |
| |
| @NotNull |
| public GlobalInspectionContextImpl getContext() { |
| return myContext; |
| } |
| } |