/* * Copyright 2000-2016 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.usages.impl; import com.intellij.icons.AllIcons; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FileStatus; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.DarculaColors; import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.speedSearch.SpeedSearchUtil; import com.intellij.usageView.UsageTreeColors; import com.intellij.usageView.UsageTreeColorsScheme; import com.intellij.usageView.UsageViewBundle; import com.intellij.usages.*; import com.intellij.util.FontUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import java.awt.*; /** * @author max */ class UsageViewTreeCellRenderer extends ColoredTreeCellRenderer { private static final Logger LOG = Logger.getInstance("#com.intellij.usages.impl.UsageViewTreeCellRenderer"); private static final EditorColorsScheme ourColorsScheme = UsageTreeColorsScheme.getInstance().getScheme(); private static final SimpleTextAttributes ourInvalidAttributes = SimpleTextAttributes.fromTextAttributes(ourColorsScheme.getAttributes(UsageTreeColors.INVALID_PREFIX)); private static final SimpleTextAttributes ourReadOnlyAttributes = SimpleTextAttributes.fromTextAttributes(ourColorsScheme.getAttributes(UsageTreeColors.READONLY_PREFIX)); private static final SimpleTextAttributes ourNumberOfUsagesAttribute = SimpleTextAttributes.fromTextAttributes(ourColorsScheme.getAttributes(UsageTreeColors.NUMBER_OF_USAGES)); private static final SimpleTextAttributes ourInvalidAttributesDarcula = new SimpleTextAttributes(null, DarculaColors.RED, null, ourInvalidAttributes.getStyle()); private static final Insets STANDARD_IPAD_NOWIFI = new Insets(1, 2, 1, 2); private boolean myRowBoundsCalled; private final UsageViewPresentation myPresentation; private final UsageView myView; private boolean myCalculated; private int myRowHeight = AllIcons.Nodes.AbstractClass.getIconHeight()+2; UsageViewTreeCellRenderer(@NotNull UsageView view) { myView = view; myPresentation = view.getPresentation(); } private Dimension cachedPreferredSize; @NotNull @Override public Dimension getPreferredSize() { return myCalculated ? super.getPreferredSize() : new Dimension(10, myRowHeight); } @Override public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { boolean showAsReadOnly = false; if (value instanceof Node && value != tree.getModel().getRoot()) { Node node = (Node)value; if (!node.isValid()) { append(UsageViewBundle.message("node.invalid") + " ", UIUtil.isUnderDarcula() ? ourInvalidAttributesDarcula : ourInvalidAttributes); } if (myPresentation.isShowReadOnlyStatusAsRed() && node.isReadOnly()) { showAsReadOnly = true; } } myCalculated = false; if (value instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value; Object userObject = treeNode.getUserObject(); Rectangle visibleRect = ((JViewport)tree.getParent()).getViewRect(); if (!visibleRect.isEmpty()) { //Protection against SOE on some OSes and JDKs IDEA-120631 RowLocation visible = myRowBoundsCalled ? RowLocation.INSIDE_VISIBLE_RECT : isRowVisible(row, visibleRect); myRowBoundsCalled = false; if (visible != RowLocation.INSIDE_VISIBLE_RECT) { // for the node outside visible rect do not compute (expensive) presentation return; } if (!getIpad().equals(STANDARD_IPAD_NOWIFI)) { // for the visible node, return its ipad to the standard value setIpad(STANDARD_IPAD_NOWIFI); } } // we can be called recursively via isRowVisible() if (myCalculated) return; myCalculated = true; if (userObject instanceof UsageTarget) { UsageTarget usageTarget = (UsageTarget)userObject; if (!usageTarget.isValid()) { if (!getCharSequence(false).toString().contains(UsageViewBundle.message("node.invalid"))) { append(UsageViewBundle.message("node.invalid"), ourInvalidAttributes); } return; } final ItemPresentation presentation = usageTarget.getPresentation(); LOG.assertTrue(presentation != null); if (showAsReadOnly) { append(UsageViewBundle.message("node.readonly") + " ", ourReadOnlyAttributes); } final String text = presentation.getPresentableText(); append(text == null ? "" : text, SimpleTextAttributes.REGULAR_ATTRIBUTES); setIcon(presentation.getIcon(expanded)); } else if (treeNode instanceof GroupNode) { GroupNode node = (GroupNode)treeNode; if (node.isRoot()) { append(StringUtil.capitalize(myPresentation.getUsagesWord()), patchAttrs(node, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)); } else { append(node.getGroup().getText(myView), patchAttrs(node, showAsReadOnly ? ourReadOnlyAttributes : SimpleTextAttributes.REGULAR_ATTRIBUTES)); setIcon(node.getGroup().getIcon(expanded)); } int count = node.getRecursiveUsageCount(); SimpleTextAttributes attributes = patchAttrs(node, ourNumberOfUsagesAttribute); append(FontUtil.spaceAndThinSpace() + StringUtil.pluralize(count + " " + myPresentation.getUsagesWord(), count), SimpleTextAttributes.GRAYED_ATTRIBUTES.derive(attributes.getStyle(), null, null, null)); } else if (treeNode instanceof UsageNode) { UsageNode node = (UsageNode)treeNode; setIcon(node.getUsage().getPresentation().getIcon()); if (showAsReadOnly) { append(UsageViewBundle.message("node.readonly") + " ", patchAttrs(node, ourReadOnlyAttributes)); } if (node.isValid()) { TextChunk[] text = node.getUsage().getPresentation().getText(); for (int i = 0; i < text.length; i++) { TextChunk textChunk = text[i]; SimpleTextAttributes simples = textChunk.getSimpleAttributesIgnoreBackground(); append(textChunk.getText() + (i == 0 ? " " : ""), patchAttrs(node, simples), true); } } } else if (userObject instanceof String) { append((String)userObject, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } else { append(userObject == null ? "" : userObject.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES); } } else { append(value.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES); } SpeedSearchUtil.applySpeedSearchHighlighting(tree, this, true, mySelected); } // computes the node text regardless of the node visibility @NotNull String getPlainTextForNode(Object value) { boolean showAsReadOnly = false; StringBuilder result = new StringBuilder(); if (value instanceof Node) { Node node = (Node)value; if (!node.isValid()) { result.append(UsageViewBundle.message("node.invalid")).append(" "); } if (myPresentation.isShowReadOnlyStatusAsRed() && node.isReadOnly()) { showAsReadOnly = true; } } if (value instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value; Object userObject = treeNode.getUserObject(); if (userObject instanceof UsageTarget) { UsageTarget usageTarget = (UsageTarget)userObject; if (usageTarget.isValid()) { final ItemPresentation presentation = usageTarget.getPresentation(); LOG.assertTrue(presentation != null); if (showAsReadOnly) { result.append(UsageViewBundle.message("node.readonly")).append(" "); } final String text = presentation.getPresentableText(); result.append(text == null ? "" : text); } else { result.append(UsageViewBundle.message("node.invalid")); } } else if (treeNode instanceof GroupNode) { GroupNode node = (GroupNode)treeNode; if (node.isRoot()) { result.append(StringUtil.capitalize(myPresentation.getUsagesWord())); } else { result.append(node.getGroup().getText(myView)); } int count = node.getRecursiveUsageCount(); result.append(" (").append(StringUtil.pluralize(count + " " + myPresentation.getUsagesWord(), count)).append(")"); } else if (treeNode instanceof UsageNode) { UsageNode node = (UsageNode)treeNode; if (showAsReadOnly) { result.append(UsageViewBundle.message("node.readonly")).append(" "); } if (node.isValid()) { TextChunk[] text = node.getUsage().getPresentation().getText(); for (TextChunk textChunk : text) { result.append(textChunk.getText()); } } } else if (userObject instanceof String) { result.append((String)userObject); } else { result.append(userObject == null ? "" : userObject.toString()); } } else { result.append(value); } return result.toString(); } enum RowLocation { BEFORE_VISIBLE_RECT, INSIDE_VISIBLE_RECT, AFTER_VISIBLE_RECT } @NotNull RowLocation isRowVisible(int row, @NotNull Rectangle visibleRect) { Dimension pref; if (cachedPreferredSize == null) { cachedPreferredSize = pref = getPreferredSize(); } else { pref = cachedPreferredSize; } pref.width = Math.max(visibleRect.width, pref.width); myRowBoundsCalled = true; JTree tree = getTree(); final Rectangle bounds = tree == null ? null : tree.getRowBounds(row); myRowBoundsCalled = false; if (bounds != null) { myRowHeight = bounds.height; } int y = bounds == null ? 0 : bounds.y; TextRange vis = TextRange.from(Math.max(0, visibleRect.y - pref.height), visibleRect.height + pref.height * 2); boolean inside = vis.contains(y); if (inside) { return RowLocation.INSIDE_VISIBLE_RECT; } return y < vis.getStartOffset() ? RowLocation.BEFORE_VISIBLE_RECT : RowLocation.AFTER_VISIBLE_RECT; } private static SimpleTextAttributes patchAttrs(@NotNull Node node, @NotNull SimpleTextAttributes original) { if (node.isExcluded()) { original = new SimpleTextAttributes(original.getStyle() | SimpleTextAttributes.STYLE_STRIKEOUT, original.getFgColor(), original.getWaveColor()); } if (node instanceof GroupNode) { UsageGroup group = ((GroupNode)node).getGroup(); FileStatus fileStatus = group != null ? group.getFileStatus() : null; if (fileStatus != null && fileStatus != FileStatus.NOT_CHANGED) { original = new SimpleTextAttributes(original.getStyle(), fileStatus.getColor(), original.getWaveColor()); } DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); if (parent != null && parent.isRoot()) { original = new SimpleTextAttributes(original.getStyle() | SimpleTextAttributes.STYLE_BOLD, original.getFgColor(), original.getWaveColor()); } } return original; } static String getTooltipFromPresentation(final Object value) { if (value instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value; if (treeNode instanceof UsageNode) { UsageNode node = (UsageNode)treeNode; return node.getUsage().getPresentation().getTooltipText(); } } return null; } }