package com.explodingpixels.widgets;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
import com.explodingpixels.macwidgets.plaf.ITunesTableUI;
/**
* Creates a border for a {@link javax.swing.JViewport} that draws a striped background
* corresponding to the row positions of the given {@link javax.swing.JTable}.
*/
public class StripedViewportBorder extends AbstractBorder implements
ListSelectionListener, PropertyChangeListener {
private final JViewport fViewport;
private final JTable fTable;
private final Color fStripeColor;
public StripedViewportBorder(JViewport viewport, JTable table,
Color stripeColor) {
fViewport = viewport;
fTable = table;
fStripeColor = stripeColor;
fTable.getSelectionModel().addListSelectionListener(this);
fTable.addPropertyChangeListener(this);
WindowUtils.installWeakWindowFocusListener(table,
createWindowFocusListener());
}
public StripedViewportBorder(JViewport viewport, JTable table) {
this(viewport, table, new Color(241, 245, 250));
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width,
int height) {
paintStripedBackground(g, y);
paintVerticalGridLines(g, y, height);
}
private void paintStripedBackground(Graphics g, int borderY) {
// get the row index at the top of the clip bounds (the first row
// to paint).
Rectangle clip = g.getClipBounds();
Point viewPosition = fViewport.getViewPosition();
int rowAtPoint = fTable.rowAtPoint(viewPosition);
// get the y coordinate of the first row to paint. if there are no
// rows in the table, start painting at the top of the supplied
// clipping bounds.
int topY = rowAtPoint < 0 ? borderY : fTable.getCellRect(rowAtPoint, 0,
true).y - viewPosition.y + borderY;
// create a counter variable to hold the current row. if there are no
// rows in the table, start the counter at 0.
int currentRow = rowAtPoint < 0 ? 0 : rowAtPoint;
int rowHeight = fTable.getRowHeight();
Border border = null;
if (fTable.getUI() instanceof ITunesTableUI) {
ITunesTableUI ui = (ITunesTableUI) fTable.getUI();
border = ui.getSelectedRowBorder();
}
while (topY < clip.y + clip.height) {
int bottomY = topY + rowHeight;
g.setColor(getRowColor(currentRow));
g.fillRect(clip.x, topY, clip.width, rowHeight);
if (border != null && fTable.isRowSelected(currentRow)) {
border.paintBorder(fViewport, g, 0, topY, fViewport.getWidth(),
fTable.getRowHeight());
}
topY = bottomY;
currentRow++;
}
}
private Color getRowColor(int row) {
if (fTable.isRowSelected(row)) {
return fTable.getSelectionBackground();
}
return row % 2 == 0 ? fStripeColor : fTable.getBackground();
}
private void paintVerticalGridLines(Graphics g, int y, int height) {
// paint the column grid dividers for the non-existent rows.
int x = 0 - fViewport.getViewPosition().x + fViewport.getLocation().x;
g.setColor(fTable.getGridColor());
for (int i = 0; i < fTable.getColumnCount(); i++) {
TableColumn column = fTable.getColumnModel().getColumn(i);
// increase the x position by the width of the current column.
x += column.getWidth();
g.setColor(fTable.getGridColor());
// draw the grid line (not sure what the -1 is for, but BasicTableUI
// also does it.source
g.drawLine(x - 1, y, x - 1, y + height);
}
}
public void valueChanged(ListSelectionEvent e) {
fViewport.repaint();
}
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getSource().equals(fTable)) {
if (evt.getPropertyName().equals("selectionModel")) {
ListSelectionModel oldModel = (ListSelectionModel) evt
.getOldValue();
ListSelectionModel newModel = (ListSelectionModel) evt
.getNewValue();
oldModel.removeListSelectionListener(this);
newModel.addListSelectionListener(this);
} else if (evt.getPropertyName().equals("selectionBackground")) {
fViewport.repaint();
}
}
}
private WindowFocusListener createWindowFocusListener() {
return new WindowFocusListener() {
public void windowGainedFocus(WindowEvent e) {
fViewport.repaint();
}
public void windowLostFocus(WindowEvent e) {
fViewport.repaint();
}
};
}
}