package org.limewire.ui.swing.table;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.limewire.ui.swing.util.PropertyUtils;
import ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.DefaultEventTableModel;
public class MouseableTable extends StripedJXTable {
private TablePopupHandler popupHandler;
private TableDoubleClickHandler rowDoubleClickHandler;
private TableColumnDoubleClickHandler columnDoubleClickHandler;
private TableColors colors = newTableColors();
private TableCellHeaderRenderer defaultRenderer;
protected MouseMotionListener mouseOverEditorListener;
public MouseableTable() {
initialize();
}
protected TableColors newTableColors() {
return new TableColors();
}
public TableColors getTableColors() {
return colors;
}
public MouseableTable(TableModel model) {
super(model);
initialize();
}
public void setPopupHandler(TablePopupHandler popupHandler) {
this.popupHandler = popupHandler;
}
public void setDoubleClickHandler(TableDoubleClickHandler tableDoubleClickHandler) {
this.rowDoubleClickHandler = tableDoubleClickHandler;
}
public void setColumnDoubleClickHandler(TableColumnDoubleClickHandler columnDoubleClickHandler) {
this.columnDoubleClickHandler = columnDoubleClickHandler;
}
@Override
public String getToolTipText(MouseEvent event) {
int row = rowAtPoint(event.getPoint());
int col = columnAtPoint(event.getPoint());
if (row > -1 && col > -1) {
return getToolTipText(row, col);
}
return null;
}
/**
* Returns the tooltip text for the item at the given row and column.
* The default implementation only shows the tooltip text if the text is clipped.
*/
protected String getToolTipText(int row, int col) {
Object value = getValueAt(row, col);
JComponent renderer = getRendererComponent(row, col, value);
if (value != null && isClipped(renderer, col)) {
String toolTip = renderer.getToolTipText();
if (toolTip != null) {
return toolTip;
} else if (renderer instanceof JLabel) {
// works for DefaultTableCellRenderer
return ((JLabel) renderer).getText();
}
return PropertyUtils.getToolTipText(value);
}
return null;
}
/**
* Checks if the renderer fits in the column.
*
* @param row the view index of the row
* @param col the view index of the column
* @return true if the column width is less than the preferred width of the
* renderer
*/
private boolean isClipped(JComponent renderer, int col) {
return renderer.getPreferredSize().width > getColumnModel().getColumn(col).getWidth();
}
private JComponent getRendererComponent(int row, int col, Object value) {
TableCellRenderer tcr = getCellRenderer(row, col);
return (JComponent) tcr.getTableCellRendererComponent(this, value, false, false, row, col);
}
protected void initialize() {
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setCellSelectionEnabled(false);
setRowSelectionAllowed(true);
setTableHeaderRenderer();
setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
setFont(colors.getTableFont());
//HighlightPredicate.EVEN and HighlightPredicate.ODD are zero based
setHighlighters(colors.getEvenHighlighter(),
colors.getOddHighlighter(),
new ColorHighlighter(new MenuHighlightPredicate(this), colors.menuRowColor, colors.menuRowForeground, colors.menuRowColor, colors.menuRowForeground));
setGridColor(colors.getGridColor());
//so that mouseovers will work within table
mouseOverEditorListener = new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
// Get the table cell that the mouse is over.
int row = rowAtPoint(e.getPoint());
int col = columnAtPoint(e.getPoint());
// If the cell is editable and
// it's not already being edited ...
if (isCellEditable(row, col) && (row != getEditingRow() || col != getEditingColumn())) {
editCellAt(row, col);
} else {
maybeCancelEditing();
}
}
};
addMouseMotionListener(mouseOverEditorListener);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {//adding this to editor messes up popups
int col = columnAtPoint(e.getPoint());
int row = rowAtPoint(e.getPoint());
if (row >= 0 && col >= 0) {
if (rowDoubleClickHandler != null || columnDoubleClickHandler != null) {
Component component = e.getComponent();
//launch file on double click unless the click is on a button
if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)
&& !(component.getComponentAt(e.getPoint()) instanceof JButton)) {
if (rowDoubleClickHandler != null) {
rowDoubleClickHandler.handleDoubleClick(row);
}
if (columnDoubleClickHandler != null) {
columnDoubleClickHandler.handleDoubleClick(col);
}
}
}
}
}
@Override
public void mouseExited(MouseEvent e) {
maybeCancelEditing();
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mousePressed(MouseEvent e) {
int col = columnAtPoint(e.getPoint());
int row = rowAtPoint(e.getPoint());
if (isEditing() && isCellEditable(row, col)) {
TableCellEditor editor = getCellEditor(row, col);
if (editor != null) {
// force update editor colors
prepareEditor(editor, row, col);
}
}
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger() && popupHandler != null) {
int col = columnAtPoint(e.getPoint());
int row = rowAtPoint(e.getPoint());
if (row >= 0 && col >= 0) {
popupHandler.maybeShowPopup(
e.getComponent(), e.getX(), e.getY());
TableCellEditor editor = getCellEditor();
if (editor != null) {
editor.cancelCellEditing();
}
}
}
}
});
}
//Don't set the cell value when editing is cancelled
@Override
public void editingStopped(ChangeEvent e) {
TableCellEditor editor = getCellEditor();
if (editor != null) {
removeEditor();
}
}
public void setStripeHighlighterEnabled(boolean striped){
if (striped) {
// HighlightPredicate.EVEN and HighlightPredicate.ODD are zero based
setHighlighters(
colors.getEvenHighlighter(),
colors.getOddHighlighter());
} else {
setHighlighters(
new ColorHighlighter(HighlightPredicate.EVEN, colors.evenColor,
colors.evenForeground, colors.evenColor,
colors.evenForeground),
new ColorHighlighter(HighlightPredicate.ODD, colors.evenColor,
colors.evenForeground, colors.evenColor,
colors.evenForeground),
new ColorHighlighter(new MenuHighlightPredicate(this), colors.menuRowColor,
colors.menuRowForeground, colors.menuRowColor, colors.menuRowForeground));
}
}
// gets rid of default editor color so that editors are colored by highlighters and selection color is shown
@Override
public Component prepareEditor(TableCellEditor editor, int row, int column) {
Component comp = super.prepareEditor(editor, row, column);
if (compoundHighlighter != null) {
ComponentAdapter adapter = getComponentAdapter(row, column);
comp = compoundHighlighter.highlight(comp, adapter);
}
return comp;
}
protected void setTableHeaderRenderer() {
JTableHeader th = getTableHeader();
th.setDefaultRenderer(getTableCellHeaderRenderer());
}
private TableCellRenderer getTableCellHeaderRenderer() {
if(defaultRenderer == null)
defaultRenderer = new TableCellHeaderRenderer();
return defaultRenderer;
}
/**
* Fills in the top right corner if a scrollbar appears
* with an empty table header.
*/
@Override
protected void configureEnclosingScrollPane() {
super.configureEnclosingScrollPane();
Container p = getParent();
if (p instanceof JViewport) {
Container gp = p.getParent();
if (gp instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane)gp;
// Make certain we are the viewPort's view and not, for
// example, the rowHeaderView of the scrollPane -
// an implementor of fixed columns might do this.
JViewport viewport = scrollPane.getViewport();
if (viewport == null || viewport.getView() != this) {
return;
}
JTableHeader th = new JTableHeader();
th.setDefaultRenderer(getTableCellHeaderRenderer());
// Put a dummy header in the upper-right corner.
final Component renderer = th.getDefaultRenderer().getTableCellRendererComponent(null, "", false, false, -1, -1);
JPanel cornerComponent = new JPanel(new BorderLayout());
cornerComponent.add(renderer, BorderLayout.CENTER);
scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, cornerComponent);
}
}
}
/**
* @return whether or not a popup menu is showing on the row
*/
public boolean isMenuShowing(int row) {
if(popupHandler != null) {
return popupHandler.isPopupShowing(row);
}
return false;
}
@Override
public boolean isCellEditable(int row, int col) {
if (row >= getRowCount() || col >= getColumnCount() || row < 0 || col < 0) {
return false;
}
return getColumnModel().getColumn(col).getCellEditor() != null;
}
/**
* Does this row have a popup menu showing?
*/
private static class MenuHighlightPredicate implements HighlightPredicate {
private MouseableTable table;
public MenuHighlightPredicate(MouseableTable table) {
this.table = table;
}
public boolean isHighlighted(Component renderer,
ComponentAdapter adapter) {
if (!adapter.getComponent().isEnabled())
return false;
return table.isMenuShowing(adapter.row);
}
}
@Override
public void setDefaultEditor(Class clazz, TableCellEditor editor) {
boolean usesEventTableModel = getModel() instanceof DefaultEventTableModel;
boolean usesAdvancedTableFormat = false;
TableFormat tableFormat = null;
if (usesEventTableModel) {
tableFormat = ((DefaultEventTableModel)getModel()).getTableFormat();
usesAdvancedTableFormat =
tableFormat instanceof AdvancedTableFormat;
}
if (usesEventTableModel && usesAdvancedTableFormat) {
AdvancedTableFormat format = (AdvancedTableFormat) tableFormat;
for (int i = 0; i < getModel().getColumnCount(); i++) {
Class columnClass = format.getColumnClass(i);
if (columnClass == clazz) {
getColumnModel().getColumn(i).setCellEditor(editor);
}
}
} else {
super.setDefaultEditor(clazz, editor);
}
}
@Override
public void setDefaultRenderer(Class clazz, TableCellRenderer renderer) {
boolean usesEventTableModel = getModel() instanceof DefaultEventTableModel;
boolean usesAdvancedTableFormat = false;
TableFormat tableFormat = null;
if (usesEventTableModel) {
tableFormat = ((DefaultEventTableModel)getModel()).getTableFormat();
usesAdvancedTableFormat =
tableFormat instanceof AdvancedTableFormat;
}
if (usesEventTableModel && usesAdvancedTableFormat) {
AdvancedTableFormat format = (AdvancedTableFormat) tableFormat;
for (int i = 0; i < getModel().getColumnCount(); i++) {
Class columnClass = format.getColumnClass(i);
if (columnClass == clazz) {
getColumnModel().getColumn(i).setCellRenderer(renderer);
break;
}
}
} else {
super.setDefaultRenderer(clazz, renderer);
}
}
/**
* Ensures the given row is visible.
*/
public void ensureRowVisible(int row) {
if(row != -1) {
Rectangle cellRect = getCellRect(row, 0, false);
Rectangle visibleRect = getVisibleRect();
if( !visibleRect.intersects(cellRect) )
scrollRectToVisible(cellRect);
}
}
public boolean isColumnVisible(int column) {
Rectangle cellRect = getCellRect(0, column, false);
Rectangle visibleRect = getVisibleRect();
return visibleRect.intersects(cellRect);
}
//clears mouseover color
private void maybeCancelEditing() {
Point mousePosition = getMousePosition();
if (getCellEditor() != null &&
(mousePosition == null || rowAtPoint(mousePosition) == -1 || columnAtPoint(mousePosition) == -1)){
getCellEditor().cancelCellEditing();
}
}
}