/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* 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 2 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, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.swing.table;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.table.TableCellRenderer;
/**
* A popup that appears when the user moves the mouse over a table cell whose
* contents are truncated. The popup uses the {@link TableCellRenderer} to
* display the full contents of the cell.
*
* @author james.a.edwards
*/
@SuppressWarnings("serial")
public class TruncatedCellPopup extends JPanel {
private class RendererPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 4327984162213032275L;
private JTable table;
private int row;
private int column;
RendererPanel(JTable table, int row, int column) {
super();
this.table = table;
this.row = row;
this.column = column;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Insets insets = getInsets();
g2.translate(insets.left, insets.top);
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component rendComp = table.prepareRenderer(renderer, row, column);
Color bg = null;
if (rendComp.isOpaque()) {
bg = rendComp.getBackground();
} else {
bg = table.getBackground();
}
if (bg != null) {
g2.setBackground(bg);
g2.clearRect(0, 0, getWidth(), getHeight());
}
rendComp.setSize(getWidth() - insets.left - insets.right, getHeight() - insets.top - insets.bottom);
rendComp.paint(g);
g2.translate(-insets.left, -insets.top);
}
}
private class TableMouseHandler extends MouseAdapter implements MouseMotionListener {
@Override
public void mouseDragged(MouseEvent e) {
showPopupIfNecessary(e);
}
@Override
public void mouseMoved(MouseEvent e) {
showPopupIfNecessary(e);
}
}
/**
*
*/
private static final long serialVersionUID = -5059622598127573017L;
private TableMouseHandler mouseHandler = new TableMouseHandler();
private Popup popup;
private JTable currentTable;
private int currentRow;
private int currentColumn;
private boolean verticalExpand = false;
private boolean horizontalExpand = true;
private Rectangle convertToScreen(Rectangle r, Component c) {
Point p1 = new Point(r.x, r.y);
SwingUtilities.convertPointToScreen(p1, c);
return new Rectangle(p1.x, p1.y, r.width, r.height);
}
private void hidePopup() {
if (popup != null) {
popup.hide();
popup = null;
}
currentTable = null;
currentRow = -1;
currentColumn = -1;
}
public void installOn(JTable table) {
table.addMouseMotionListener(mouseHandler);
}
public boolean isHorizontalExpand() {
return horizontalExpand;
}
public boolean isVerticalExpand() {
return verticalExpand;
}
private Rectangle pad(Rectangle r, Insets i) {
return new Rectangle(r.x - i.left, r.y - i.top, r.width + i.left + i.right, r.height + i.top + i.bottom);
}
private MouseEvent retargetEvent(MouseEvent e, Component target) {
Point newPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), target);
return new MouseEvent(target, e.getID(), e.getWhen(), e.getModifiers(), newPoint.x, newPoint.y,
e.getClickCount(), e.isPopupTrigger(), e.getButton());
}
private MouseEvent retargetEvent(MouseWheelEvent e, Component target) {
Point newPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), target);
return new MouseWheelEvent(target, e.getID(), e.getWhen(), e.getModifiers(), newPoint.x, newPoint.y,
e.getClickCount(), e.isPopupTrigger(), e.getScrollType(), e.getScrollAmount(), e.getWheelRotation());
}
public void setHorizontalExpand(boolean horizontalExpand) {
this.horizontalExpand = horizontalExpand;
}
public void setVerticalExpand(boolean verticalExpand) {
this.verticalExpand = verticalExpand;
}
private void showPopup(final JTable table, int row, int column, Rectangle cellRect, Rectangle wholeRect) {
final RendererPanel rendererPanel = new RendererPanel(table, row, column);
rendererPanel.setBorder(new LineBorder(Color.GRAY));
wholeRect = pad(convertToScreen(wholeRect, table), rendererPanel.getInsets());
rendererPanel.setPreferredSize(wholeRect.getSize());
final Rectangle keepShowingRect = new Rectangle(cellRect);
keepShowingRect.x = rendererPanel.getInsets().left;
keepShowingRect.y = rendererPanel.getInsets().top;
popup = PopupFactory.getSharedInstance().getPopup(table, rendererPanel, wholeRect.x, wholeRect.y);
class PopupMouseHandler extends MouseAdapter implements MouseMotionListener, MouseWheelListener {
@Override
public void mouseDragged(MouseEvent e) {
mouseMoved(e);
rendererPanel.repaint();
}
@Override
public void mouseExited(MouseEvent e) {
hidePopup();
}
@Override
public void mouseMoved(MouseEvent e) {
if (!keepShowingRect.contains(e.getPoint())) {
hidePopup();
}
}
@Override
public void mousePressed(MouseEvent e) {
table.dispatchEvent(retargetEvent(e, table));
rendererPanel.repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
table.dispatchEvent(retargetEvent(e, table));
rendererPanel.repaint();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
hidePopup();
table.dispatchEvent(retargetEvent(e, table));
// without this a blank spot where the popup used to be might
// remain
table.repaint();
}
}
;
PopupMouseHandler popupMouseHandler = new PopupMouseHandler();
rendererPanel.addMouseListener(popupMouseHandler);
rendererPanel.addMouseMotionListener(popupMouseHandler);
rendererPanel.addMouseWheelListener(popupMouseHandler);
popup.show();
// without this the popup might be blank
rendererPanel.repaint();
}
private void showPopupIfNecessary(MouseEvent e) {
JTable table = (JTable) e.getComponent();
int row = table.rowAtPoint(e.getPoint());
int column = table.columnAtPoint(e.getPoint());
if (table == currentTable && row == currentRow && column == currentColumn) {
return;
}
hidePopup();
if (row < 0 || column < 0) {
return;
}
Rectangle cellRect = table.getCellRect(row, column, false);
if (!cellRect.contains(e.getPoint())) {
return;
}
currentTable = table;
currentRow = row;
currentColumn = column;
TableCellRenderer cellRenderer = table.getCellRenderer(row, column);
Component rendComp = table.prepareRenderer(cellRenderer, row, column);
Dimension prefSize = rendComp.getPreferredSize();
Rectangle wholeRect = new Rectangle(cellRect);
Rectangle visibleCellRect = cellRect.intersection(table.getVisibleRect());
if (horizontalExpand) {
wholeRect.width = Math.max(cellRect.width, prefSize.width);
}
if (verticalExpand) {
wholeRect.height = Math.max(cellRect.height, prefSize.height);
}
if (!wholeRect.equals(visibleCellRect)) {
showPopup(table, row, column, cellRect, wholeRect);
}
}
public void uninstallFrom(JTable table) {
table.removeMouseMotionListener(mouseHandler);
}
}