package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import apple.laf.*;
import apple.laf.JRSUIConstants.*;
import apple.laf.JRSUIState.AnimationFrameState;
public class AquaTreeUI extends BasicTreeUI {
public static ComponentUI createUI(final JComponent c) {
return new AquaTreeUI();
}
private static final String LINE_STYLE = "JTree.lineStyle";
private static final String LEG_LINE_STYLE_STRING = "Angled";
private static final String HORIZ_STYLE_STRING = "Horizontal";
private static final String NO_STYLE_STRING = "None";
private static final int LEG_LINE_STYLE = 2;
private static final int HORIZ_LINE_STYLE = 1;
private static final int NO_LINE_STYLE = 0;
private int lineStyle = HORIZ_LINE_STYLE;
private final PropertyChangeListener lineStyleListener = new LineListener();
protected TreePath fTrackingPath;
protected boolean fIsPressed = false;
protected boolean fIsInBounds = false;
protected int fAnimationFrame = -1;
protected TreeArrowMouseInputHandler fMouseHandler;
protected final AquaPainter<AnimationFrameState> painter = AquaPainter.create(JRSUIStateFactory.getDisclosureTriangle());
public AquaTreeUI() {
}
public void installUI(final JComponent c) {
super.installUI(c);
final Object lineStyleFlag = c.getClientProperty(LINE_STYLE);
decodeLineStyle(lineStyleFlag);
c.addPropertyChangeListener(lineStyleListener);
}
public void uninstallUI(final JComponent c) {
c.removePropertyChangeListener(lineStyleListener);
super.uninstallUI(c);
}
protected FocusListener createFocusListener() {
return new AquaTreeUI.FocusHandler();
}
protected void decodeLineStyle(final Object lineStyleFlag) {
if (lineStyleFlag == null || NO_STYLE_STRING.equals(lineStyleFlag)) {
lineStyle = NO_LINE_STYLE;
return;
}
if (LEG_LINE_STYLE_STRING.equals(lineStyleFlag)) {
lineStyle = LEG_LINE_STYLE;
} else if (HORIZ_STYLE_STRING.equals(lineStyleFlag)) {
lineStyle = HORIZ_LINE_STYLE;
}
}
public TreePath getClosestPathForLocation(final JTree treeLocal, final int x, final int y) {
if (treeLocal == null || treeState == null) return null;
Insets i = treeLocal.getInsets();
if (i == null) i = new Insets(0, 0, 0, 0);
return treeState.getPathClosestTo(x - i.left, y - i.top);
}
public void paint(final Graphics g, final JComponent c) {
super.paint(g, c);
if (lineStyle == HORIZ_LINE_STYLE && !largeModel) {
paintHorizontalSeparators(g, c);
}
}
protected void paintHorizontalSeparators(final Graphics g, final JComponent c) {
g.setColor(UIManager.getColor("Tree.line"));
final Rectangle clipBounds = g.getClipBounds();
final int beginRow = getRowForPath(tree, getClosestPathForLocation(tree, 0, clipBounds.y));
final int endRow = getRowForPath(tree, getClosestPathForLocation(tree, 0, clipBounds.y + clipBounds.height - 1));
if (beginRow <= -1 || endRow <= -1) { return; }
for (int i = beginRow; i <= endRow; ++i) {
final TreePath path = getPathForRow(tree, i);
if (path != null && path.getPathCount() == 2) {
final Rectangle rowBounds = getPathBounds(tree, getPathForRow(tree, i));
if (rowBounds != null) g.drawLine(clipBounds.x, rowBounds.y, clipBounds.x + clipBounds.width, rowBounds.y);
}
}
}
protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) {
if (lineStyle == LEG_LINE_STYLE) {
super.paintVerticalPartOfLeg(g, clipBounds, insets, path);
}
}
protected void paintHorizontalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final Rectangle bounds, final TreePath path, final int row, final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf) {
if (lineStyle == LEG_LINE_STYLE) {
super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
}
}
class LineListener implements PropertyChangeListener {
public void propertyChange(final PropertyChangeEvent e) {
final String name = e.getPropertyName();
if (name.equals(LINE_STYLE)) {
decodeLineStyle(e.getNewValue());
}
}
}
protected void paintExpandControl(final Graphics g, final Rectangle clipBounds, final Insets insets, final Rectangle bounds, final TreePath path, final int row, final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf) {
final Object value = path.getLastPathComponent();
if (isLeaf || (hasBeenExpanded && treeModel.getChildCount(value) <= 0)) return;
final boolean isLeftToRight = AquaUtils.isLeftToRight(tree);
final State state = getState(path);
if (fAnimationFrame == -1 && state != State.PRESSED) {
super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
return;
}
final Icon icon = isExpanded ? getExpandedIcon() : getCollapsedIcon();
if (!(icon instanceof UIResource)) {
super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
return;
}
int middleXOfKnob;
if (isLeftToRight) {
middleXOfKnob = bounds.x - (getRightChildIndent() - 1);
} else {
middleXOfKnob = clipBounds.x + clipBounds.width / 2;
}
final int middleYOfKnob = bounds.y + (bounds.height / 2);
final int x = middleXOfKnob - icon.getIconWidth() / 2;
final int y = middleYOfKnob - icon.getIconHeight() / 2;
final int height = icon.getIconHeight();
final int width = 20;
setupPainter(state, isExpanded, isLeftToRight);
painter.paint(g, tree, x, y, width, height);
}
@Override
public Icon getCollapsedIcon() {
final Icon icon = super.getCollapsedIcon();
if (AquaUtils.isLeftToRight(tree)) return icon;
if (!(icon instanceof UIResource)) return icon;
return UIManager.getIcon("Tree.rightToLeftCollapsedIcon");
}
protected void setupPainter(State state, final boolean isExpanded, final boolean leftToRight) {
if (!fIsInBounds && state == State.PRESSED) state = State.ACTIVE;
painter.state.set(state);
if (JRSUIUtils.Tree.useLegacyTreeKnobs()) {
if (fAnimationFrame == -1) {
painter.state.set(isExpanded ? Direction.DOWN : Direction.RIGHT);
} else {
painter.state.set(Direction.NONE);
painter.state.setAnimationFrame(fAnimationFrame - 1);
}
} else {
painter.state.set(getDirection(isExpanded, leftToRight));
painter.state.setAnimationFrame(fAnimationFrame);
}
}
protected Direction getDirection(final boolean isExpanded, final boolean isLeftToRight) {
if (isExpanded && (fAnimationFrame == -1)) return Direction.DOWN;
return isLeftToRight ? Direction.RIGHT : Direction.LEFT;
}
protected State getState(final TreePath path) {
if (!tree.isEnabled()) return State.DISABLED;
if (fIsPressed) {
if (fTrackingPath.equals(path)) return State.PRESSED;
}
return State.ACTIVE;
}
protected void handleExpandControlClick(final TreePath path, final int mouseX, final int mouseY) {
fMouseHandler = new TreeArrowMouseInputHandler(path);
}
protected boolean isToggleSelectionEvent(final MouseEvent event) {
return SwingUtilities.isLeftMouseButton(event) && event.isMetaDown();
}
class FocusHandler extends BasicTreeUI.FocusHandler {
public void focusGained(final FocusEvent e) {
super.focusGained(e);
AquaBorder.repaintBorder(tree);
}
public void focusLost(final FocusEvent e) {
super.focusLost(e);
AquaBorder.repaintBorder(tree);
}
}
protected PropertyChangeListener createPropertyChangeListener() {
return new MacPropertyChangeHandler();
}
public class MacPropertyChangeHandler extends PropertyChangeHandler {
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if (prop.equals(AquaFocusHandler.FRAME_ACTIVE_PROPERTY)) {
AquaBorder.repaintBorder(tree);
AquaFocusHandler.swapSelectionColors("Tree", tree, e.getNewValue());
} else {
super.propertyChange(e);
}
}
}
class TreeArrowMouseInputHandler extends MouseInputAdapter {
protected Rectangle fPathBounds = new Rectangle();
protected boolean fIsLeaf, fIsExpanded, fHasBeenExpanded;
protected Rectangle fBounds, fVisibleRect;
int fTrackingRow;
Insets fInsets;
Color fBackground;
TreeArrowMouseInputHandler(final TreePath path) {
fTrackingPath = path;
fIsPressed = true;
fIsInBounds = true;
this.fPathBounds = getPathArrowBounds(path);
tree.addMouseListener(this);
tree.addMouseMotionListener(this);
fBackground = tree.getBackground();
if (!tree.isOpaque()) {
final Component p = tree.getParent();
if (p != null) fBackground = p.getBackground();
}
fVisibleRect = tree.getVisibleRect();
fInsets = tree.getInsets();
if (fInsets == null) fInsets = new Insets(0, 0, 0, 0);
fIsLeaf = treeModel.isLeaf(path.getLastPathComponent());
if (fIsLeaf) fIsExpanded = fHasBeenExpanded = false;
else {
fIsExpanded = treeState.getExpandedState(path);
fHasBeenExpanded = tree.hasBeenExpanded(path);
}
final Rectangle boundsBuffer = new Rectangle();
fBounds = treeState.getBounds(fTrackingPath, boundsBuffer);
fBounds.x += fInsets.left;
fBounds.y += fInsets.top;
fTrackingRow = getRowForPath(fTrackingPath);
paintOneControl();
}
public void mouseDragged(final MouseEvent e) {
fIsInBounds = fPathBounds.contains(e.getX(), e.getY());
paintOneControl();
}
@Override
public void mouseExited(MouseEvent e) {
fIsInBounds = fPathBounds.contains(e.getX(), e.getY());
paintOneControl();
}
public void mouseReleased(final MouseEvent e) {
if (tree == null) return;
if (fIsPressed) {
final boolean wasInBounds = fIsInBounds;
fIsPressed = false;
fIsInBounds = false;
if (wasInBounds) {
fIsExpanded = !fIsExpanded;
paintAnimation(fIsExpanded);
if (e.isAltDown()) {
if (fIsExpanded) {
expandNode(fTrackingRow, true);
} else {
collapseNode(fTrackingRow, true);
}
} else {
toggleExpandState(fTrackingPath);
}
}
}
fTrackingPath = null;
removeFromSource();
}
protected void paintAnimation(final boolean expanding) {
if (expanding) {
paintAnimationFrame(1);
paintAnimationFrame(2);
paintAnimationFrame(3);
} else {
paintAnimationFrame(3);
paintAnimationFrame(2);
paintAnimationFrame(1);
}
fAnimationFrame = -1;
}
protected void paintAnimationFrame(final int frame) {
fAnimationFrame = frame;
paintOneControl();
try { Thread.sleep(20); } catch (final InterruptedException e) { }
}
void paintOneControl() {
if (tree == null) return;
final Graphics g = tree.getGraphics();
if (g == null) {
return;
}
try {
g.setClip(fVisibleRect);
g.setColor(fBackground);
g.fillRect(fPathBounds.x, fPathBounds.y, fPathBounds.width, fPathBounds.height);
if (fTrackingPath == null) return;
final TreePath parentPath = fTrackingPath.getParentPath();
if (parentPath != null) {
paintVerticalPartOfLeg(g, fPathBounds, fInsets, parentPath);
paintHorizontalPartOfLeg(g, fPathBounds, fInsets, fBounds, fTrackingPath, fTrackingRow, fIsExpanded, fHasBeenExpanded, fIsLeaf);
} else if (isRootVisible() && fTrackingRow == 0) {
paintHorizontalPartOfLeg(g, fPathBounds, fInsets, fBounds, fTrackingPath, fTrackingRow, fIsExpanded, fHasBeenExpanded, fIsLeaf);
}
paintExpandControl(g, fPathBounds, fInsets, fBounds, fTrackingPath, fTrackingRow, fIsExpanded, fHasBeenExpanded, fIsLeaf);
} finally {
g.dispose();
}
}
protected void removeFromSource() {
tree.removeMouseListener(this);
tree.removeMouseMotionListener(this);
}
}
protected int getRowForPath(final TreePath path) {
return treeState.getRowForPath(path);
}
protected Rectangle getPathArrowBounds(final TreePath path) {
final Rectangle bounds = getPathBounds(tree, path);
final Insets i = tree.getInsets();
if (getExpandedIcon() != null) bounds.width = getExpandedIcon().getIconWidth();
else bounds.width = 8;
int boxLeftX = (i != null) ? i.left : 0;
if (AquaUtils.isLeftToRight(tree)) {
boxLeftX += (((path.getPathCount() + depthOffset - 2) * totalChildIndent) + getLeftChildIndent()) - bounds.width / 2;
} else {
boxLeftX += tree.getWidth() - 1 - ((path.getPathCount() - 2 + depthOffset) * totalChildIndent) - getLeftChildIndent() - bounds.width / 2;
}
bounds.x = boxLeftX;
return bounds;
}
protected void installKeyboardActions() {
super.installKeyboardActions();
tree.getActionMap().put("aquaExpandNode", new KeyboardExpandCollapseAction(true, false));
tree.getActionMap().put("aquaCollapseNode", new KeyboardExpandCollapseAction(false, false));
tree.getActionMap().put("aquaFullyExpandNode", new KeyboardExpandCollapseAction(true, true));
tree.getActionMap().put("aquaFullyCollapseNode", new KeyboardExpandCollapseAction(false, true));
}
@SuppressWarnings("serial")
class KeyboardExpandCollapseAction extends AbstractAction {
final boolean expand;
final boolean recursive;
public KeyboardExpandCollapseAction(final boolean expand, final boolean recursive) {
this.expand = expand;
this.recursive = recursive;
}
public void actionPerformed(final ActionEvent e) {
if (tree == null || 0 > getRowCount(tree)) return;
final TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths == null) return;
for (int i = selectionPaths.length - 1; i >= 0; i--) {
final TreePath path = selectionPaths[i];
if (expand) {
expandNode(tree.getRowForPath(path), recursive);
continue;
}
if (selectionPaths.length == 1 && tree.isCollapsed(path)) {
final TreePath parentPath = path.getParentPath();
if (parentPath != null && (!(parentPath.getParentPath() == null) || tree.isRootVisible())) {
tree.scrollPathToVisible(parentPath);
tree.setSelectionPath(parentPath);
}
continue;
}
collapseNode(tree.getRowForPath(path), recursive);
}
}
public boolean isEnabled() {
return (tree != null && tree.isEnabled());
}
}
void expandNode(final int row, final boolean recursive) {
final TreePath path = getPathForRow(tree, row);
if (path == null) return;
tree.expandPath(path);
if (!recursive) return;
expandAllNodes(path, row + 1);
}
void expandAllNodes(final TreePath parent, final int initialRow) {
for (int i = initialRow; true; i++) {
final TreePath path = getPathForRow(tree, i);
if (!parent.isDescendant(path)) return;
tree.expandPath(path);
}
}
void collapseNode(final int row, final boolean recursive) {
final TreePath path = getPathForRow(tree, row);
if (path == null) return;
if (recursive) {
collapseAllNodes(path, row + 1);
}
tree.collapsePath(path);
}
void collapseAllNodes(final TreePath parent, final int initialRow) {
int lastRow = -1;
for (int i = initialRow; lastRow == -1; i++) {
final TreePath path = getPathForRow(tree, i);
if (!parent.isDescendant(path)) {
lastRow = i - 1;
}
}
for (int i = lastRow; i >= initialRow; i--) {
final TreePath path = getPathForRow(tree, i);
tree.collapsePath(path);
}
}
}