blob: 30314135a5432febbcd378b9c3ef3ebaf398bea5 [file] [log] [blame]
/*
* Copyright 2000-2014 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.treeStructure;
import com.intellij.Patches;
import com.intellij.ide.util.treeView.*;
import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.*;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.ui.*;
import com.intellij.util.ui.tree.WideSelectionTreeUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.plaf.TreeUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.text.Position;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.dnd.Autoscroll;
import java.awt.event.*;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;
public class Tree extends JTree implements ComponentWithEmptyText, ComponentWithExpandableItems<Integer>, Autoscroll, Queryable,
ComponentWithFileColors {
private final StatusText myEmptyText;
private final ExpandableItemsHandler<Integer> myExpandableItemsHandler;
private AsyncProcessIcon myBusyIcon;
private boolean myBusy;
private Rectangle myLastVisibleRec;
private Dimension myHoldSize;
private final MySelectionModel mySelectionModel = new MySelectionModel();
private boolean myHorizontalAutoScrolling = true;
public Tree() {
this(getDefaultTreeModel());
}
public Tree(TreeNode root) {
this(new DefaultTreeModel(root, false));
}
public Tree(TreeModel treemodel) {
super(treemodel);
myEmptyText = new StatusText(this) {
@Override
protected boolean isStatusVisible() {
return Tree.this.isEmpty();
}
};
myExpandableItemsHandler = ExpandableItemsHandlerFactory.install(this);
addMouseListener(new MyMouseListener());
if (Patches.SUN_BUG_ID_4893787) {
addFocusListener(new MyFocusListener());
}
setCellRenderer(new NodeRenderer());
setSelectionModel(mySelectionModel);
}
@Override
public void setUI(final TreeUI ui) {
TreeUI actualUI = ui;
if (!isCustomUI()) {
if (!(ui instanceof WideSelectionTreeUI) && isWideSelection() && !UIUtil.isUnderGTKLookAndFeel()) {
actualUI = new WideSelectionTreeUI(isWideSelection(), getWideSelectionBackgroundCondition());
}
}
super.setUI(actualUI);
}
public boolean isEmpty() {
TreeModel model = getModel();
if (model == null) return true;
if (model.getRoot() == null) return true;
if (!isRootVisible()) {
final int childCount = model.getChildCount(model.getRoot());
if (childCount == 0) {
return true;
}
if (childCount == 1) {
final Object node = model.getChild(model.getRoot(), 0);
if (node instanceof LoadingNode) {
return true;
}
}
}
return false;
}
protected boolean isCustomUI() {
return false;
}
/**
* Will be removed in version 13
*
* @deprecated use isWideSelection
* @see #isWideSelection()
*/
protected boolean isMacWideSelection() {
return isWideSelection();
}
protected boolean isWideSelection() {
return true;
}
/**
* @return a strategy which determines if a wide selection should be drawn for a target row (it's number is
* {@link Condition#value(Object) given} as an argument to the strategy)
*/
@SuppressWarnings("unchecked")
@NotNull
protected Condition<Integer> getWideSelectionBackgroundCondition() {
return Conditions.alwaysTrue();
}
@Override
public boolean isFileColorsEnabled() {
return false;
}
@NotNull
@Override
public StatusText getEmptyText() {
return myEmptyText;
}
@Override
@NotNull
public ExpandableItemsHandler<Integer> getExpandableItemsHandler() {
return myExpandableItemsHandler;
}
@Override
public void setExpandableItemsEnabled(boolean enabled) {
myExpandableItemsHandler.setEnabled(enabled);
}
@Override
public Color getBackground() {
return isBackgroundSet() ? super.getBackground() : UIUtil.getTreeTextBackground();
}
@Override
public Color getForeground() {
return isForegroundSet() ? super.getForeground() : UIUtil.getTreeForeground();
}
@Override
public void addNotify() {
super.addNotify();
updateBusy();
}
@Override
public void removeNotify() {
super.removeNotify();
if (myBusyIcon != null) {
remove(myBusyIcon);
Disposer.dispose(myBusyIcon);
myBusyIcon = null;
}
}
@Override
public void doLayout() {
super.doLayout();
updateBusyIconLocation();
}
private void updateBusyIconLocation() {
if (myBusyIcon != null) {
myBusyIcon.updateLocation(this);
}
}
@Override
public void paint(Graphics g) {
final Rectangle visible = getVisibleRect();
boolean canHoldSelection = false;
TreePath[] paths = getSelectionModel().getSelectionPaths();
if (paths != null) {
for (TreePath each : paths) {
final Rectangle selection = getPathBounds(each);
if (selection != null && (g.getClipBounds().intersects(selection) || g.getClipBounds().contains(selection))) {
if (myBusy) {
Rectangle busyIconBounds = myBusyIcon.getBounds();
if (selection.contains(busyIconBounds) || selection.intersects(busyIconBounds)) {
canHoldSelection = false;
break;
} else {
canHoldSelection = true;
}
} else {
canHoldSelection = true;
}
}
}
}
if (canHoldSelection) {
if (!AbstractTreeBuilder.isToPaintSelection(this)) {
mySelectionModel.holdSelection();
}
}
try {
super.paint(g);
if (!visible.equals(myLastVisibleRec)) {
updateBusyIconLocation();
}
myLastVisibleRec = visible;
}
finally {
mySelectionModel.unholdSelection();
}
}
public void setPaintBusy(boolean paintBusy) {
if (myBusy == paintBusy) return;
myBusy = paintBusy;
updateBusy();
}
private void updateBusy() {
if (myBusy) {
if (myBusyIcon == null) {
myBusyIcon = new AsyncProcessIcon(toString()).setUseMask(false);
myBusyIcon.setOpaque(false);
myBusyIcon.setPaintPassiveIcon(false);
add(myBusyIcon);
myBusyIcon.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (!UIUtil.isActionClick(e)) return;
AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(Tree.this);
if (builder != null) {
builder.cancelUpdate();
}
}
});
}
}
if (myBusyIcon != null) {
if (myBusy) {
if (shouldShowBusyIconIfNeeded()) {
myBusyIcon.resume();
myBusyIcon.setToolTipText("Update is in progress. Click to cancel");
}
}
else {
myBusyIcon.suspend();
myBusyIcon.setToolTipText(null);
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (myBusyIcon != null) {
repaint();
}
}
});
}
updateBusyIconLocation();
}
}
protected boolean shouldShowBusyIconIfNeeded() {
// http://youtrack.jetbrains.com/issue/IDEA-101422 "Rotating wait symbol in Project list whenever typing"
return hasFocus();
}
protected boolean paintNodes() {
return false;
}
@Override
protected void paintComponent(Graphics g) {
if (paintNodes()) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
paintNodeContent(g);
}
if (isFileColorsEnabled()) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
paintFileColorGutter(g);
}
super.paintComponent(g);
myEmptyText.paint(this, g);
}
protected void paintFileColorGutter(final Graphics g) {
final GraphicsConfig config = new GraphicsConfig(g);
final Rectangle rect = getVisibleRect();
final int firstVisibleRow = getClosestRowForLocation(rect.x, rect.y);
final int lastVisibleRow = getClosestRowForLocation(rect.x, rect.y + rect.height);
for (int row = firstVisibleRow; row <= lastVisibleRow; row++) {
final TreePath path = getPathForRow(row);
if (path != null) {
final Rectangle bounds = getRowBounds(row);
Object component = path.getLastPathComponent();
final Object[] pathObjects = path.getPath();
if (component instanceof LoadingNode && pathObjects.length > 1) {
component = pathObjects[pathObjects.length - 2];
}
Color color = getFileColorFor((DefaultMutableTreeNode)component);
if (color != null) {
g.setColor(color);
g.fillRect(0, bounds.y, getWidth(), bounds.height);
}
}
}
config.restore();
}
@Nullable
public Color getFileColorForPath(@Nullable TreePath path) {
if (path != null) {
final Object node = path.getLastPathComponent();
if (node instanceof DefaultMutableTreeNode) {
return getFileColorFor(((DefaultMutableTreeNode)node).getUserObject());
}
}
return null;
}
@Nullable
public Color getFileColorFor(Object object) {
return null;
}
@Nullable
public Color getFileColorFor(DefaultMutableTreeNode node) {
return getFileColorFor(node.getUserObject());
}
@Override
protected void processKeyEvent(KeyEvent e) {
super.processKeyEvent(e);
}
/**
* Hack to prevent loosing multiple selection on Mac when clicking Ctrl+Left Mouse Button.
* See faulty code at BasicTreeUI.selectPathForEvent():2245
*
* Another hack to match selection UI (wide) and selection behavior (narrow) in Nimbus/GTK+.
*/
@Override
protected void processMouseEvent(final MouseEvent e) {
MouseEvent e2 = e;
if (SystemInfo.isMac) {
if (SwingUtilities.isLeftMouseButton(e) && e.isControlDown() && e.getID() == MouseEvent.MOUSE_PRESSED) {
int modifiers = e.getModifiers() & ~(InputEvent.CTRL_MASK | InputEvent.BUTTON1_MASK) | InputEvent.BUTTON3_MASK;
e2 = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), modifiers, e.getX(), e.getY(), e.getClickCount(),
true, MouseEvent.BUTTON3);
}
}
else if (UIUtil.isUnderNimbusLookAndFeel() || UIUtil.isUnderGTKLookAndFeel()) {
if (SwingUtilities.isLeftMouseButton(e) && (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_CLICKED)) {
final TreePath path = getClosestPathForLocation(e.getX(), e.getY());
if (path != null) {
final Rectangle bounds = getPathBounds(path);
if (bounds != null &&
e.getY() > bounds.y && e.getY() < bounds.y + bounds.height &&
(e.getX() >= bounds.x + bounds.width ||
e.getX() < bounds.x && !isLocationInExpandControl(path, e.getX(), e.getY()))) {
int newX = bounds.x + bounds.width - 2;
e2 = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), newX, e.getY(), e.getClickCount(),
e.isPopupTrigger(), e.getButton());
}
}
}
}
super.processMouseEvent(e2);
}
private boolean isLocationInExpandControl(final TreePath path, final int x, final int y) {
final TreeUI ui = getUI();
if (!(ui instanceof BasicTreeUI)) return false;
try {
Class aClass = ui.getClass();
while (BasicTreeUI.class.isAssignableFrom(aClass) && !BasicTreeUI.class.equals(aClass)) {
aClass = aClass.getSuperclass();
}
final Method method = ReflectionUtil.getDeclaredMethod(aClass, "isLocationInExpandControl", TreePath.class, int.class, int.class);
if (method != null) {
return (Boolean)method.invoke(ui, path, x, y);
}
}
catch (Throwable ignore) { }
return false;
}
/**
* Disable Sun's speed search
*/
@Override
public TreePath getNextMatch(String prefix, int startingRow, Position.Bias bias) {
return null;
}
private static final int AUTOSCROLL_MARGIN = 10;
@Override
public Insets getAutoscrollInsets() {
return new Insets(getLocation().y + AUTOSCROLL_MARGIN, 0, getParent().getHeight() - AUTOSCROLL_MARGIN, getWidth() - 1);
}
@Override
public void autoscroll(Point p) {
int realRow = getClosestRowForLocation(p.x, p.y);
if (getLocation().y + p.y <= AUTOSCROLL_MARGIN) {
if (realRow >= 1) realRow--;
}
else {
if (realRow < getRowCount() - 1) realRow++;
}
scrollRowToVisible(realRow);
}
protected boolean highlightSingleNode() {
return true;
}
private void paintNodeContent(Graphics g) {
if (!(getUI() instanceof BasicTreeUI)) return;
final AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(this);
if (builder == null || builder.isDisposed()) return;
GraphicsConfig config = new GraphicsConfig(g);
config.setAntialiasing(true);
final AbstractTreeStructure structure = builder.getTreeStructure();
for (int eachRow = 0; eachRow < getRowCount(); eachRow++) {
final TreePath path = getPathForRow(eachRow);
PresentableNodeDescriptor node = toPresentableNode(path.getLastPathComponent());
if (node == null) continue;
if (!node.isContentHighlighted()) continue;
if (highlightSingleNode()) {
if (node.isContentHighlighted()) {
final TreePath nodePath = getPath(node);
Rectangle rect;
final Rectangle parentRect = getPathBounds(nodePath);
if (isExpanded(nodePath)) {
final int[] max = getMax(node, structure);
rect = new Rectangle(parentRect.x, parentRect.y, Math.max((int) parentRect.getMaxX(), max[1]) - parentRect.x - 1,
Math.max((int) parentRect.getMaxY(), max[0]) - parentRect.y - 1);
}
else {
rect = parentRect;
}
if (rect != null) {
final Color highlightColor = node.getHighlightColor();
g.setColor(highlightColor);
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height, 4, 4);
g.setColor(highlightColor.darker());
g.drawRoundRect(rect.x, rect.y, rect.width, rect.height, 4, 4);
}
}
}
else {
//todo: to investigate why it might happen under 1.6: http://www.productiveme.net:8080/browse/PM-217
if (node.getParentDescriptor() == null) continue;
final Object[] kids = structure.getChildElements(node);
if (kids.length == 0) continue;
PresentableNodeDescriptor first = null;
PresentableNodeDescriptor last = null;
int lastIndex = -1;
for (int i = 0; i < kids.length; i++) {
final Object kid = kids[i];
if (kid instanceof PresentableNodeDescriptor) {
PresentableNodeDescriptor eachKid = (PresentableNodeDescriptor) kid;
if (!node.isHighlightableContentNode(eachKid)) continue;
if (first == null) {
first = eachKid;
}
last = eachKid;
lastIndex = i;
}
}
if (first == null || last == null) continue;
Rectangle firstBounds = getPathBounds(getPath(first));
if (isExpanded(getPath(last))) {
if (lastIndex + 1 < kids.length) {
final Object child = kids[lastIndex + 1];
if (child instanceof PresentableNodeDescriptor) {
PresentableNodeDescriptor nextKid = (PresentableNodeDescriptor) child;
int nextRow = getRowForPath(getPath(nextKid));
last = toPresentableNode(getPathForRow(nextRow - 1).getLastPathComponent());
}
}
else {
NodeDescriptor parentNode = node.getParentDescriptor();
if (parentNode instanceof PresentableNodeDescriptor) {
final PresentableNodeDescriptor ppd = (PresentableNodeDescriptor)parentNode;
int nodeIndex = node.getIndex();
if (nodeIndex + 1 < structure.getChildElements(ppd).length) {
PresentableNodeDescriptor nextChild = ppd.getChildToHighlightAt(nodeIndex + 1);
int nextRow = getRowForPath(getPath(nextChild));
TreePath prevPath = getPathForRow(nextRow - 1);
if (prevPath != null) {
last = toPresentableNode(prevPath.getLastPathComponent());
}
}
else {
int lastRow = getRowForPath(getPath(last));
PresentableNodeDescriptor lastParent = last;
boolean lastWasFound = false;
for (int i = lastRow + 1; i < getRowCount(); i++) {
PresentableNodeDescriptor eachNode = toPresentableNode(getPathForRow(i).getLastPathComponent());
if (!node.isParentOf(eachNode)) {
last = lastParent;
lastWasFound = true;
break;
}
lastParent = eachNode;
}
if (!lastWasFound) {
last = toPresentableNode(getPathForRow(getRowCount() - 1).getLastPathComponent());
}
}
}
}
}
if (last == null) continue;
Rectangle lastBounds = getPathBounds(getPath(last));
if (firstBounds == null || lastBounds == null) continue;
Rectangle toPaint = new Rectangle(firstBounds.x, firstBounds.y, 0, (int)lastBounds.getMaxY() - firstBounds.y - 1);
toPaint.width = getWidth() - toPaint.x - 4;
final Color highlightColor = first.getHighlightColor();
g.setColor(highlightColor);
g.fillRoundRect(toPaint.x, toPaint.y, toPaint.width, toPaint.height, 4, 4);
g.setColor(highlightColor.darker());
g.drawRoundRect(toPaint.x, toPaint.y, toPaint.width, toPaint.height, 4, 4);
}
}
config.restore();
}
private int[] getMax(final PresentableNodeDescriptor node, final AbstractTreeStructure structure) {
int x = 0;
int y = 0;
final Object[] children = structure.getChildElements(node);
for (final Object child : children) {
if (child instanceof PresentableNodeDescriptor) {
final TreePath childPath = getPath((PresentableNodeDescriptor)child);
if (childPath != null) {
if (isExpanded(childPath)) {
final int[] tmp = getMax((PresentableNodeDescriptor)child, structure);
y = Math.max(y, tmp[0]);
x = Math.max(x, tmp[1]);
}
final Rectangle r = getPathBounds(childPath);
if (r != null) {
y = Math.max(y, (int)r.getMaxY());
x = Math.max(x, (int)r.getMaxX());
}
}
}
}
return new int[]{y, x};
}
@Nullable
private static PresentableNodeDescriptor toPresentableNode(final Object pathComponent) {
if (!(pathComponent instanceof DefaultMutableTreeNode)) return null;
final Object userObject = ((DefaultMutableTreeNode)pathComponent).getUserObject();
if (!(userObject instanceof PresentableNodeDescriptor)) return null;
return (PresentableNodeDescriptor)userObject;
}
public TreePath getPath(PresentableNodeDescriptor node) {
final AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(this);
final DefaultMutableTreeNode treeNode = builder.getNodeForElement(node);
return treeNode != null ? new TreePath(treeNode.getPath()) : new TreePath(node);
}
private static class MySelectionModel extends DefaultTreeSelectionModel {
private TreePath[] myHeldSelection;
@Override
protected void fireValueChanged(TreeSelectionEvent e) {
if (myHeldSelection == null) {
super.fireValueChanged(e);
}
}
public void holdSelection() {
myHeldSelection = getSelectionPaths();
clearSelection();
}
public void unholdSelection() {
if (myHeldSelection != null) {
setSelectionPaths(myHeldSelection);
myHeldSelection = null;
}
}
}
private class MyMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent mouseevent) {
if (!JBSwingUtilities.isLeftMouseButton(mouseevent) &&
(JBSwingUtilities.isRightMouseButton(mouseevent) || JBSwingUtilities.isMiddleMouseButton(mouseevent))) {
TreePath treepath = getPathForLocation(mouseevent.getX(), mouseevent.getY());
if (treepath != null) {
if (getSelectionModel().getSelectionMode() != TreeSelectionModel.SINGLE_TREE_SELECTION) {
TreePath[] selectionPaths = getSelectionModel().getSelectionPaths();
if (selectionPaths != null) {
for (TreePath selectionPath : selectionPaths) {
if (selectionPath != null && selectionPath.equals(treepath)) return;
}
}
}
getSelectionModel().setSelectionPath(treepath);
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && isLocationInExpandControl(getClosestPathForLocation(e.getX(), e.getY()), e.getX())) {
e.consume();
}
}
/**
* Returns true if <code>mouseX</code> falls
* in the area of row that is used to expand/collapse the node and
* the node at <code>row</code> does not represent a leaf.
*/
}
protected boolean isLocationInExpandControl(@Nullable TreePath path, int mouseX) {
if (path == null) return false;
TreeUI ui = getUI();
if (!(ui instanceof BasicTreeUI)) return false;
BasicTreeUI treeUI = (BasicTreeUI)ui;
if (!treeModel.isLeaf(path.getLastPathComponent())) {
Insets insets = Tree.this.getInsets();
int boxWidth = treeUI.getExpandedIcon() != null ? treeUI.getExpandedIcon().getIconWidth() : 8;
int boxLeftX = treeUI.getLeftChildIndent() + treeUI.getRightChildIndent() * (path.getPathCount() - 1);
if (getComponentOrientation().isLeftToRight()) {
boxLeftX = boxLeftX + insets.left - treeUI.getRightChildIndent() + 1;
}
else {
boxLeftX = getWidth() - boxLeftX - insets.right + treeUI.getRightChildIndent() - 1;
}
boxLeftX -= getComponentOrientation().isLeftToRight() ? (int)Math.ceil(boxWidth / 2.0) : (int)Math.floor(boxWidth / 2.0);
return mouseX >= boxLeftX && mouseX < boxLeftX + boxWidth;
}
return false;
}
/**
* This is patch for 4893787 SUN bug. The problem is that the BasicTreeUI.FocusHandler repaints
* only lead selection index on focus changes. It's a problem with multiple selected nodes.
*/
private class MyFocusListener extends FocusAdapter {
private void focusChanges() {
TreePath[] paths = getSelectionPaths();
if (paths != null) {
TreeUI ui = getUI();
for (int i = paths.length - 1; i >= 0; i--) {
Rectangle bounds = ui.getPathBounds(Tree.this, paths[i]);
if (bounds != null) {
repaint(bounds);
}
}
}
}
@Override
public void focusGained(FocusEvent e) {
focusChanges();
}
@Override
public void focusLost(FocusEvent e) {
focusChanges();
}
}
public final void setLineStyleAngled() {
UIUtil.setLineStyleAngled(this);
}
@NotNull
public <T> T[] getSelectedNodes(Class<T> nodeType, @Nullable NodeFilter<T> filter) {
TreePath[] paths = getSelectionPaths();
if (paths == null) return (T[])Array.newInstance(nodeType, 0);
ArrayList<T> nodes = new ArrayList<T>();
for (TreePath path : paths) {
Object last = path.getLastPathComponent();
if (nodeType.isAssignableFrom(last.getClass())) {
if (filter != null && !filter.accept((T)last)) continue;
nodes.add((T)last);
}
}
T[] result = (T[])Array.newInstance(nodeType, nodes.size());
nodes.toArray(result);
return result;
}
public interface NodeFilter<T> {
boolean accept(T node);
}
@Override
public void putInfo(@NotNull Map<String, String> info) {
final TreePath[] selection = getSelectionPaths();
if (selection == null) return;
final StringBuilder nodesText = new StringBuilder();
for (TreePath eachPath : selection) {
final Object eachNode = eachPath.getLastPathComponent();
final Component c =
getCellRenderer().getTreeCellRendererComponent(this, eachNode, false, false, false, getRowForPath(eachPath), false);
if (c != null) {
if (nodesText.length() > 0) {
nodesText.append(";");
}
nodesText.append(c);
}
}
if (nodesText.length() > 0) {
info.put("selectedNodes", nodesText.toString());
}
}
public void setHoldSize(boolean hold) {
if (hold && myHoldSize == null) {
myHoldSize = getPreferredSize();
} else if (!hold && myHoldSize != null) {
myHoldSize = null;
revalidate();
}
}
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (myHoldSize != null) {
size.width = Math.max(size.width, myHoldSize.width);
size.height = Math.max(size.height, myHoldSize.height);
}
return size;
}
public boolean isHorizontalAutoScrollingEnabled() {
return myHorizontalAutoScrolling;
}
public void setHorizontalAutoScrollingEnabled(boolean enabled) {
myHorizontalAutoScrolling = enabled;
}
/**
* Returns the deepest visible component
* that will be rendered at the specified location.
*
* @param x horizontal location in the tree
* @param y vertical location in the tree
* @return the deepest visible component of the renderer
*/
@Nullable
public Component getDeepestRendererComponentAt(int x, int y) {
int row = getRowForLocation(x, y);
if (row >= 0) {
TreeCellRenderer renderer = getCellRenderer();
if (renderer != null) {
TreePath path = getPathForRow(row);
Object node = path.getLastPathComponent();
Component component = renderer.getTreeCellRendererComponent(this, node,
isRowSelected(row),
isExpanded(row),
getModel().isLeaf(node),
row, true);
Rectangle bounds = getPathBounds(path);
if (bounds != null) {
component.setBounds(bounds); // initialize size to layout complex renderer
return SwingUtilities.getDeepestComponentAt(component, x - bounds.x, y - bounds.y);
}
}
}
return null;
}
}