/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.swing;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.border.AbstractBorder;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class TreeCellExtender {
public final JTree tree;
private JWindow cellExtender;
private int offset;
private int row;
private boolean active;
private final MouseHandler mouseHandler;
public static TreeCellExtender equip(JTree tree) {
final TreeCellExtender extender = new TreeCellExtender(tree);
extender.setActive(true);
return extender;
}
public TreeCellExtender(JTree tree) {
this.tree = tree;
unsetRow();
this.mouseHandler = new MouseHandler();
this.active = false;
}
public JTree getTree() {
return tree;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
final boolean oldValue = this.active;
if (oldValue) {
tree.removeMouseListener(mouseHandler);
tree.removeMouseMotionListener(mouseHandler);
disposeCellExtender();
unsetRow();
}
this.active = active;
if (this.active) {
tree.addMouseListener(mouseHandler);
tree.addMouseMotionListener(mouseHandler);
hideCellExtender();
unsetRow();
}
}
private void showOrHideCellExtender(Point point) {
final int oldRow = row;
row = tree.getRowForLocation(point.x, point.y);
if (row == oldRow) {
return;
}
hideCellExtender();
if (row < 0) {
return;
}
TreePath path = tree.getPathForRow(row);
if(path == null) {
return;
}
Rectangle rowRect = tree.getPathBounds(path);
if(rowRect == null) {
return;
}
Rectangle viewRect;
if (tree.getParent() instanceof JViewport) {
viewRect = ((JViewport) tree.getParent()).getViewRect();
} else {
viewRect = tree.getBounds();
}
int rx1 = rowRect.x;
int ry1 = rowRect.y;
int rx2 = rowRect.x + rowRect.width - 1;
int ry2 = rowRect.y + rowRect.height - 1;
// int vx1 = viewRect.x;
int vy1 = viewRect.y;
int vx2 = viewRect.x + viewRect.width - 1;
int vy2 = viewRect.y + viewRect.height - 1;
boolean cellExtenderVisible = rx2 > vx2 && ry1 >= vy1 && ry2 <= vy2;
if (!cellExtenderVisible) {
return;
}
offset = vx2 - rx1;
int wx = rx1 < vx2 ? vx2 : rx1;
int wy = rowRect.y - 1;
int ww = rx2 - wx + 2;
int wh = rowRect.height + 2;
Rectangle windowRect = new Rectangle(wx, wy, ww, wh);
if (windowRect.isEmpty()) {
return;
}
showCellExtender(windowRect);
}
private void showCellExtender(Rectangle windowRect) {
final Rectangle screenBounds = convertToScreen(tree, windowRect);
if (cellExtender == null) {
cellExtender = new JWindow(SwingUtilities.getWindowAncestor(tree));
cellExtender.getContentPane().add(new CellExtenderPanel(), BorderLayout.CENTER);
}
cellExtender.setBounds(screenBounds);
cellExtender.setVisible(true);
}
private void hideCellExtender() {
if (cellExtender != null) {
cellExtender.setVisible(false);
}
}
private void disposeCellExtender() {
if (cellExtender != null) {
cellExtender.dispose();
cellExtender = null;
}
}
private void unsetRow() {
row = -1;
}
private static Rectangle convertToScreen(Component c, Rectangle r) {
Point p = new Point(r.getLocation());
SwingUtilities.convertPointToScreen(p, c);
return new Rectangle(p.x, p.y, r.width, r.height);
}
private class MouseHandler extends MouseAdapter {
private Point point;
@Override
public void mouseMoved(MouseEvent e) {
Point point = e.getPoint();
if (!point.equals(this.point)) {
this.point = point;
showOrHideCellExtender(this.point);
}
}
@Override
public void mouseDragged(MouseEvent e) {
hideCellExtender();
unsetRow();
}
@Override
public void mouseEntered(MouseEvent e) {
// Check, because mouseEntered is also called if cellExtender becomes invisible
// and cursor is over both, tree and cellExtender.
if (e.getPoint().equals(this.point)) {
hideCellExtender();
unsetRow();
}
}
@Override
public void mouseExited(MouseEvent e) {
// Check, because mouseEntered is also called if cellExtender becomes visible
// and cursor is over both, tree and cellExtender.
if (e.getPoint().equals(this.point)) {
disposeCellExtender();
unsetRow();
}
}
}
private class CellExtenderPanel extends JPanel {
public CellExtenderPanel() {
setBorder(new CellExtenderBorder());
setBackground(tree.getBackground());
}
@Override
protected void paintComponent(Graphics g) {
final int width = getWidth();
final int height = getHeight();
if (width <= 0 || height <= 0) {
return;
}
TreePath path = tree.getPathForRow(row);
if (path == null) {
return;
}
Rectangle bounds = getBounds();
Color color = g.getColor();
g.setColor(getBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
g.setColor(color);
TreeCellRenderer renderer = tree.getCellRenderer();
Component rendererComponent = renderer.getTreeCellRendererComponent(tree,
path.getLastPathComponent(),
tree.isPathSelected(path),
tree.isExpanded(path),
true, // todo - leaf ?
row,
false); // has focus ?
rendererComponent.setSize(1024, height);
Graphics g2 = g.create(0, 0, width, height);
g2.translate(-offset, 0);
rendererComponent.paint(g2);
g2.dispose();
}
}
private static class CellExtenderBorder extends AbstractBorder {
@Override
public Insets getBorderInsets(Component c) {
return new Insets(1, 0, 1, 1);
}
@Override
public Insets getBorderInsets(Component c, Insets insets) {
insets.left = 0;
insets.top = insets.right = insets.bottom = 1;
return insets;
}
@Override
public void paintBorder(Component c, Graphics g, int x1, int y1, int width, int height) {
final int x2 = x1 + width - 1;
final int y2 = y1 + height - 1;
final Color color = g.getColor();
g.setColor(Color.DARK_GRAY);
g.drawLine(x1, y1, x2, y1);
g.drawLine(x1, y2, x2, y2);
g.drawLine(x2, y1, x2, y2);
g.setColor(color);
}
}
}