/platform/platform-impl/src/com/intellij/ui/MultilineTreeCellRenderer.java
https://bitbucket.org/nbargnesi/idea · Java · 476 lines · 355 code · 69 blank · 52 comment · 67 complexity · 924d74e47de34a03f038a341d8de9129 MD5 · raw file
- /*
- * 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.
- */
- package com.intellij.ui;
- import com.intellij.openapi.util.text.StringUtil;
- import com.intellij.ui.components.JBScrollPane;
- import com.intellij.util.ArrayUtil;
- import com.intellij.util.ui.UIUtil;
- import com.intellij.util.ui.tree.WideSelectionTreeUI;
- import org.jetbrains.annotations.NonNls;
- import javax.swing.*;
- import javax.swing.plaf.TreeUI;
- import javax.swing.tree.DefaultMutableTreeNode;
- import javax.swing.tree.TreeCellRenderer;
- import java.awt.*;
- import java.awt.event.ComponentAdapter;
- import java.awt.event.ComponentEvent;
- import java.beans.PropertyChangeEvent;
- import java.beans.PropertyChangeListener;
- import java.util.ArrayList;
- public abstract class MultilineTreeCellRenderer extends JComponent implements TreeCellRenderer {
- private boolean myWrapsCalculated = false;
- private boolean myTooSmall = false;
- private int myHeightCalculated = -1;
- private int myWrapsCalculatedForWidth = -1;
- private ArrayList myWraps = new ArrayList();
- private int myMinHeight = 1;
- private Insets myTextInsets;
- private final Insets myLabelInsets = new Insets(1, 2, 1, 2);
- private boolean mySelected;
- private boolean myHasFocus;
- private Icon myIcon;
- private String[] myLines = ArrayUtil.EMPTY_STRING_ARRAY;
- private String myPrefix;
- private int myTextLength;
- private int myPrefixWidth;
- @NonNls protected static final String FONT_PROPERTY_NAME = "font";
- private JTree myTree;
- public MultilineTreeCellRenderer() {
- myTextInsets = new Insets(0,0,0,0);
- addComponentListener(new ComponentAdapter() {
- public void componentResized(ComponentEvent e) {
- onSizeChanged();
- }
- });
- addPropertyChangeListener(new PropertyChangeListener() {
- public void propertyChange(PropertyChangeEvent evt) {
- if (FONT_PROPERTY_NAME.equalsIgnoreCase(evt.getPropertyName())) {
- onFontChanged();
- }
- }
- });
- }
- protected void setMinHeight(int height) {
- myMinHeight = height;
- myHeightCalculated = Math.max(myMinHeight, myHeightCalculated);
- }
- protected void setTextInsets(Insets textInsets) {
- myTextInsets = textInsets;
- onSizeChanged();
- }
- private void onFontChanged() {
- myWrapsCalculated = false;
- }
- private void onSizeChanged() {
- int currWidth = getWidth();
- if (currWidth != myWrapsCalculatedForWidth) {
- myWrapsCalculated = false;
- myHeightCalculated = -1;
- myWrapsCalculatedForWidth = -1;
- }
- }
- private FontMetrics getCurrFontMetrics() {
- return getFontMetrics(getFont());
- }
- public void paint(Graphics g) {
- int height = getHeight();
- int width = getWidth();
- int borderX = myLabelInsets.left - 1;
- int borderY = myLabelInsets.top - 1;
- int borderW = width - borderX - myLabelInsets.right + 2;
- int borderH = height - borderY - myLabelInsets.bottom + 1;
- if (myIcon != null) {
- int verticalIconPosition = (height - myIcon.getIconHeight())/2;
- myIcon.paintIcon(this, g, 0, verticalIconPosition);
- borderX += myIcon.getIconWidth();
- borderW -= myIcon.getIconWidth();
- }
- Color bgColor;
- Color fgColor;
- if (mySelected && myHasFocus){
- bgColor = UIUtil.getTreeSelectionBackground();
- fgColor = UIUtil.getTreeSelectionForeground();
- }
- else{
- bgColor = UIUtil.getTreeTextBackground();
- fgColor = getForeground();
- }
- // fill background
- if (!(myTree.getUI() instanceof WideSelectionTreeUI) || !((WideSelectionTreeUI)myTree.getUI()).isWideSelection()) {
- g.setColor(bgColor);
- g.fillRect(borderX, borderY, borderW, borderH);
- // draw border
- if (mySelected) {
- g.setColor(UIUtil.getTreeSelectionBorderColor());
- UIUtil.drawDottedRectangle(g, borderX, borderY, borderX + borderW - 1, borderY + borderH - 1);
- }
- }
- // paint text
- recalculateWraps();
- if (myTooSmall) { // TODO ???
- return;
- }
- int fontHeight = getCurrFontMetrics().getHeight();
- int currBaseLine = getCurrFontMetrics().getAscent();
- currBaseLine += myTextInsets.top;
- g.setFont(getFont());
- g.setColor(fgColor);
- UIUtil.applyRenderingHints(g);
- if (!StringUtil.isEmpty(myPrefix)) {
- g.drawString(myPrefix, myTextInsets.left - myPrefixWidth + 1, currBaseLine);
- }
- for (int i = 0; i < myWraps.size(); i++) {
- String currLine = (String)myWraps.get(i);
- g.drawString(currLine, myTextInsets.left, currBaseLine);
- currBaseLine += fontHeight; // first is getCurrFontMetrics().getAscent()
- }
- }
- public void setText(String[] lines, String prefix) {
- myLines = lines;
- myTextLength = 0;
- for (int i = 0; i < lines.length; i++) {
- myTextLength += lines[i].length();
- }
- myPrefix = prefix;
- myWrapsCalculated = false;
- myHeightCalculated = -1;
- myWrapsCalculatedForWidth = -1;
- }
- public void setIcon(Icon icon) {
- myIcon = icon;
- myWrapsCalculated = false;
- myHeightCalculated = -1;
- myWrapsCalculatedForWidth = -1;
- }
- public Dimension getMinimumSize() {
- if (getFont() != null) {
- int minHeight = getCurrFontMetrics().getHeight();
- return new Dimension(minHeight, minHeight);
- }
- return new Dimension(
- MIN_WIDTH + myTextInsets.left + myTextInsets.right,
- MIN_WIDTH + myTextInsets.top + myTextInsets.bottom
- );
- }
- private static final int MIN_WIDTH = 10;
- // Calculates height for current width.
- public Dimension getPreferredSize() {
- recalculateWraps();
- return new Dimension(myWrapsCalculatedForWidth, myHeightCalculated);
- }
- // Calculate wraps for the current width
- private void recalculateWraps() {
- int currwidth = getWidth();
- if (myWrapsCalculated) {
- if (currwidth == myWrapsCalculatedForWidth) {
- return;
- }
- else {
- myWrapsCalculated = false;
- }
- }
- int wrapsCount = calculateWraps(currwidth);
- myTooSmall = (wrapsCount == -1);
- if (myTooSmall) {
- wrapsCount = myTextLength;
- }
- int fontHeight = getCurrFontMetrics().getHeight();
- myHeightCalculated = wrapsCount * fontHeight + myTextInsets.top + myTextInsets.bottom;
- myHeightCalculated = Math.max(myMinHeight, myHeightCalculated);
- int maxWidth = 0;
- for (int i=0; i < myWraps.size(); i++) {
- String s = (String)myWraps.get(i);
- int width = getCurrFontMetrics().stringWidth(s);
- maxWidth = Math.max(maxWidth, width);
- }
- myWrapsCalculatedForWidth = myTextInsets.left + maxWidth + myTextInsets.right;
- myWrapsCalculated = true;
- }
- private int calculateWraps(int width) {
- myTooSmall = width < MIN_WIDTH;
- if (myTooSmall) {
- return -1;
- }
- int result = 0;
- myWraps = new ArrayList();
- for (int i = 0; i < myLines.length; i++) {
- String aLine = myLines[i];
- int lineFirstChar = 0;
- int lineLastChar = aLine.length() - 1;
- int currFirst = lineFirstChar;
- int printableWidth = width - myTextInsets.left - myTextInsets.right;
- if (aLine.length() == 0) {
- myWraps.add(aLine);
- result++;
- }
- else {
- while (currFirst <= lineLastChar) {
- int currLast = calculateLastVisibleChar(aLine, printableWidth, currFirst, lineLastChar);
- if (currLast < lineLastChar) {
- int currChar = currLast + 1;
- if (!Character.isWhitespace(aLine.charAt(currChar))) {
- while (currChar >= currFirst) {
- if (Character.isWhitespace(aLine.charAt(currChar))) {
- break;
- }
- currChar--;
- }
- if (currChar > currFirst) {
- currLast = currChar;
- }
- }
- }
- myWraps.add(aLine.substring(currFirst, currLast + 1));
- currFirst = currLast + 1;
- while ((currFirst <= lineLastChar) && (Character.isWhitespace(aLine.charAt(currFirst)))) {
- currFirst++;
- }
- result++;
- }
- }
- }
- return result;
- }
- private int calculateLastVisibleChar(String line, int viewWidth, int firstChar, int lastChar) {
- if (firstChar == lastChar) return lastChar;
- if (firstChar > lastChar) throw new IllegalArgumentException("firstChar=" + firstChar + ", lastChar=" + lastChar);
- int totalWidth = getCurrFontMetrics().stringWidth(line.substring(firstChar, lastChar + 1));
- if (totalWidth == 0 || viewWidth > totalWidth) {
- return lastChar;
- }
- else {
- int newApprox = (lastChar - firstChar + 1) * viewWidth / totalWidth;
- int currChar = firstChar + Math.max(newApprox - 1, 0);
- int currWidth = getCurrFontMetrics().stringWidth(line.substring(firstChar, currChar + 1));
- while (true) {
- if (currWidth > viewWidth) {
- currChar--;
- if (currChar <= firstChar) {
- return firstChar;
- }
- currWidth -= getCurrFontMetrics().charWidth(line.charAt(currChar + 1));
- if (currWidth <= viewWidth) {
- return currChar;
- }
- }
- else {
- currChar++;
- if (currChar > lastChar) {
- return lastChar;
- }
- currWidth += getCurrFontMetrics().charWidth(line.charAt(currChar));
- if (currWidth >= viewWidth) {
- return currChar - 1;
- }
- }
- }
- }
- }
- private int getChildIndent(JTree tree) {
- TreeUI newUI = tree.getUI();
- if (newUI instanceof javax.swing.plaf.basic.BasicTreeUI) {
- javax.swing.plaf.basic.BasicTreeUI btreeui = (javax.swing.plaf.basic.BasicTreeUI)newUI;
- return btreeui.getLeftChildIndent() + btreeui.getRightChildIndent();
- }
- else {
- return ((Integer)UIUtil.getTreeLeftChildIndent()).intValue() + ((Integer)UIUtil.getTreeRightChildIndent()).intValue();
- }
- }
- private int getAvailableWidth(Object forValue, JTree tree) {
- DefaultMutableTreeNode node = (DefaultMutableTreeNode)forValue;
- int busyRoom = tree.getInsets().left + tree.getInsets().right + getChildIndent(tree) * node.getLevel();
- return tree.getVisibleRect().width - busyRoom - 2;
- }
- protected abstract void initComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus);
- public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
- setFont(UIUtil.getTreeFont());
- initComponent(tree, value, selected, expanded, leaf, row, hasFocus);
- mySelected = selected;
- myHasFocus = hasFocus;
- myTree = tree;
- int availWidth = getAvailableWidth(value, tree);
- if (availWidth > 0) {
- setSize(availWidth, 100); // height will be calculated automatically
- }
- int leftInset = myLabelInsets.left;
- if (myIcon != null) {
- leftInset += myIcon.getIconWidth() + 2;
- }
- if (!StringUtil.isEmpty(myPrefix)) {
- myPrefixWidth = getCurrFontMetrics().stringWidth(myPrefix) + 5;
- leftInset += myPrefixWidth;
- }
- setTextInsets(new Insets(myLabelInsets.top, leftInset, myLabelInsets.bottom, myLabelInsets.right));
- if (myIcon != null) {
- setMinHeight(myIcon.getIconHeight());
- }
- else {
- setMinHeight(1);
- }
- setSize(getPreferredSize());
- recalculateWraps();
- return this;
- }
- public static JScrollPane installRenderer(final JTree tree, final MultilineTreeCellRenderer renderer) {
- final TreeCellRenderer defaultRenderer = tree.getCellRenderer();
- JScrollPane scrollpane = new JBScrollPane(tree){
- private int myAddRemoveCounter = 0;
- private boolean myShouldResetCaches = false;
- public void setSize(Dimension d) {
- boolean isChanged = getWidth() != d.width || myShouldResetCaches;
- super.setSize(d);
- if (isChanged) resetCaches();
- }
- public void reshape(int x, int y, int w, int h) {
- boolean isChanged = w != getWidth() || myShouldResetCaches;
- super.reshape(x, y, w, h);
- if (isChanged) resetCaches();
- }
- private void resetCaches() {
- resetHeightCache(tree, defaultRenderer, renderer);
- myShouldResetCaches = false;
- }
- public void addNotify() {
- super.addNotify(); //To change body of overriden methods use Options | File Templates.
- if (myAddRemoveCounter == 0) myShouldResetCaches = true;
- myAddRemoveCounter++;
- }
- public void removeNotify() {
- super.removeNotify(); //To change body of overriden methods use Options | File Templates.
- myAddRemoveCounter--;
- }
- };
- scrollpane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
- scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
- tree.setCellRenderer(renderer);
- scrollpane.addComponentListener(new ComponentAdapter() {
- public void componentResized(ComponentEvent e) {
- resetHeightCache(tree, defaultRenderer, renderer);
- }
- public void componentShown(ComponentEvent e) {
- // componentResized not called when adding to opened tool window.
- // Seems to be BUG#4765299, however I failed to create same code to reproduce it.
- // To reproduce it with IDEA: 1. remove this method, 2. Start any Ant task, 3. Keep message window open 4. start Ant task again.
- resetHeightCache(tree, defaultRenderer, renderer);
- }
- });
- return scrollpane;
- }
- private static void resetHeightCache(final JTree tree,
- final TreeCellRenderer defaultRenderer,
- final MultilineTreeCellRenderer renderer) {
- tree.setCellRenderer(defaultRenderer);
- tree.setCellRenderer(renderer);
- }
- // private static class DelegatingScrollablePanel extends JPanel implements Scrollable {
- // private final Scrollable myDelegatee;
- //
- // public DelegatingScrollablePanel(Scrollable delegatee) {
- // super(new BorderLayout(0, 0));
- // myDelegatee = delegatee;
- // add((JComponent)delegatee, BorderLayout.CENTER);
- // }
- //
- // public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
- // return myDelegatee.getScrollableUnitIncrement(visibleRect, orientation, direction);
- // }
- //
- // public boolean getScrollableTracksViewportWidth() {
- // return myDelegatee.getScrollableTracksViewportWidth();
- // }
- //
- // public Dimension getPreferredScrollableViewportSize() {
- // return myDelegatee.getPreferredScrollableViewportSize();
- // }
- //
- // public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
- // return myDelegatee.getScrollableBlockIncrement(visibleRect, orientation, direction);
- // }
- //
- // public boolean getScrollableTracksViewportHeight() {
- // return myDelegatee.getScrollableTracksViewportHeight();
- // }
- // }
- }