/*
* (swing1.1beta3)
*
*/
package simplesheet;
import java.awt.event.KeyEvent;
import java.util.Iterator;
import javax.swing.border.Border;
import simplesheet.model.column.Column;
import simplesheet.model.selection.CellPosition;
import simplesheet.model.CellRenderer;
import simplesheet.model.SheetModel;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import simplesheet.model.column.ColumnListener;
import simplesheet.model.row.Row;
import simplesheet.model.row.RowListener;
import simplesheet.model.selection.SheetSelectionModel;
/**
* @version 1.0 11/26/98
*/
class SheetTableUI extends ComponentUI {
private SheetTable table;
private MyMouse mouseListener = new MyMouse();
private MyKeyboard keyboardListener = new MyKeyboard();
private ColumnListener localColumnsListener = new ColumnsListener();
private RowListener localRowsListener = new RowsListener();
private HashSet<Integer> recalcCols = new HashSet<Integer>();
private HashSet<Integer> recalcRows = new HashSet<Integer>();
public SheetTable getTable() {
return table;
}
public void setTable(SheetTable table) {
this.table = table;
SheetModel model = table.getModel();
for(int i=0; i<model.getRowCount(); i++) {
model.getRow(i).addRowListener(localRowsListener);
}
for(int i=0; i<model.getColumnCount(); i++) {
model.getColumn(i).addColumnListener(localColumnsListener);
}
}
@Override
public void installUI(JComponent c) {
if(c instanceof SheetTable == false) {
throw new IllegalArgumentException(getClass().getSimpleName()
+ " can only be used in conjuction with object instance of "
+ SheetTable.class.getSimpleName());
}
super.installUI(c);
c.addMouseListener(mouseListener);
c.addMouseMotionListener(mouseListener);
c.addKeyListener(keyboardListener);
}
@Override
public void uninstallUI(JComponent c) {
c.removeMouseListener(mouseListener);
c.removeMouseMotionListener(mouseListener);
c.removeKeyListener(keyboardListener);
table = null;
}
private class Spanned {
public int rows;
public int cols;
public int width;
public Spanned(int rows, int cols, int width) {
this.rows = rows;
this.cols = cols;
this.width = width;
}
}
@Override
public void paint(Graphics g, JComponent c) {
recalcDimension(g);
Color oldColor = g.getColor();
Rectangle oldRc = g.getClipBounds();
Insets borderInsets = null;
Border border = table.getBorder();
if(border != null) {
borderInsets = border.getBorderInsets(table);
} else {
borderInsets = new Insets(0, 0, 0, 0);
}
Rectangle clipRc = fixRectangle(oldRc, borderInsets);
//calc rectangle
SheetModel model = table.getModel();
CellPosition origin0 = getTopLeft(clipRc);
CellPosition originN = getBottomRight(clipRc);
if(origin0 == null || originN == null) {
//no cells
return;
}
int yBase = borderInsets.top;
for (int iRow = 0; iRow < origin0.row; iRow++) {
yBase += model.getRow(iRow).getHeight();
}
int xBase = borderInsets.left;
for (int iCol = 0; iCol < origin0.col; iCol++) {
xBase += model.getColumn(iCol).getWidth();
}
Spanned spanned[] = new Spanned[originN.col - origin0.col + 1];
for (int iRow = origin0.row; iRow <= originN.row; iRow++) {
int xLocalBase = xBase;
for (int iCol = origin0.col; iCol <= originN.col; ) { /// increment at end
//check if spanned
int index = iCol - origin0.col;
if(spanned[index] != null) {
spanned[index].rows--;
iCol += spanned[index].cols;
xLocalBase += spanned[index].width;
if(spanned[index].rows == 0) {
spanned[index] = null;
}
continue;
}
//define dimensions
CellPosition origin = model.getOrigin(new CellPosition(iRow, iCol));
Dimension size = model.getSize(origin);
Rectangle rc = new Rectangle();
//y offset for spanned cells may be > 0
rc.y = yBase;
for(int i=0; i < iRow-origin.row; i++) {
rc.y -= model.getRow(origin.row + i).getHeight();
}
//x offset for spanned cells may be > 0
rc.x = xLocalBase;
for(int i=0; i < iCol-origin.col; i++) {
rc.x -= model.getColumn(origin.col + i).getWidth();
}
//define cell width
rc.width = 0;
for (int i = 0; i < size.width; i++) {
rc.width += model.getColumn(origin.col + i).getWidth();
}
//define cell height
rc.height = 0;
for (int i = 0; i < size.height; i++) {
rc.height += model.getRow(origin.row + i).getHeight();
}
if(clipRc.intersects(rc)) {
//paint cell
g.setClip(clipRc.createIntersection(rc));
paintCell(g, rc, origin);
//update span info
int yLeft = (origin.row + size.height) - iRow;
if(yLeft > 1) {
int xLeft = (origin.col + size.width) - iCol;
spanned[index] = new Spanned(yLeft-1, xLeft, rc.width);
}
}
//update current position
iCol += size.width;
xLocalBase += rc.width;
}
//update current position
yBase += model.getRow(iRow).getHeight();
}
g.setColor(oldColor);
g.setClip(oldRc);
}
/**
* make clip smaller than table
* @param rc
* @return
*/
private Rectangle fixRectangle(Rectangle paintRc, Insets insets) {
Rectangle visibleRc = table.getVisibleRect();
Rectangle fixRc = new Rectangle();
fixRc.x = Math.max(visibleRc.x + insets.left, paintRc.x);
fixRc.y = Math.max(visibleRc.y + insets.top, paintRc.y);
fixRc.width = paintRc.width;
if(paintRc.x + paintRc.width > visibleRc.x + visibleRc.width) {
fixRc.width -= (paintRc.x + paintRc.width) - (visibleRc.x + visibleRc.width);
}
fixRc.height = paintRc.height;
if(paintRc.y + paintRc.height > visibleRc.y + visibleRc.height) {
fixRc.width -= (paintRc.y + paintRc.height) - (visibleRc.y + visibleRc.height);
}
return fixRc;
}
/**
*
* @param rc
* @return nearest cell in top left rect point, or null if no cell specified
*/
private CellPosition getTopLeft(Rectangle rc) {
Point topLeft = new Point(rc.x, rc.y);
CellPosition pos = table.getCellAtPoint(topLeft);
if(pos == null) {
return null;
}
return table.getModel().getOrigin(pos);
}
/**
*
* @param rc
* @return nearest cell in bottom right rect point, or null if no cell specified
*/
private CellPosition getBottomRight(Rectangle rc) {
Point bottomLeft = new Point(rc.x + rc.width, rc.y + rc.height);
CellPosition pos = table.getCellAtPoint(bottomLeft);
if(pos == null) {
return null;
}
SheetModel model = table.getModel();
CellPosition originN = model.getOrigin(pos);
Dimension sizeN = model.getSize(originN);
return new CellPosition(originN.row + sizeN.height-1, originN.col + sizeN.width-1);
}
private void paintCell(Graphics g, Rectangle cellRect, CellPosition pos) {
CellPosition editingCell = table.getEditingCell();
if (editingCell != null && editingCell.equals(pos)) {
Component component = table.getEditorComponent();
component.setBounds(cellRect);
component.validate();
} else {
drawGrid(g, cellRect);
CellRenderer renderer = table.getDefaultRenderer();
Component component = table.prepareRenderer(renderer, pos);
component.setBounds(cellRect);
component.paint(g);
}
}
private void drawGrid(Graphics g, Rectangle cellRect) {
Color c = g.getColor();
g.setColor(table.getDefaultBackgroundColor());
g.fillRect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
g.setColor(table.getGridColor());
//g.clearRect(cellRect.x, cellRect.y, cellRect.width - 1, cellRect.height - 1);
int bottom = cellRect.y + cellRect.height - 1;
int right = cellRect.x + cellRect.width - 1;
g.drawPolyline(
new int[]{cellRect.x, right, right },
new int[]{bottom, bottom, cellRect.y},
3);
g.setColor(c);
}
private void recalcDimension(Graphics g) {
SheetModel model = table.getModel();
Iterator<Integer> it = recalcCols.iterator();
while(it.hasNext()) {
int index = it.next();
Column col = model.getColumn(index);
if(col.isForceRecalcWidth()) {
recalcColWidth(g, index);
}
}
recalcCols.clear();
it = recalcRows.iterator();
while(it.hasNext()) {
int index = it.next();
Row row = model.getRow(index);
if(row.isForceRecalcHeight()) {
recalcRowHeight(g, index);
}
}
recalcRows.clear();
}
/**
* calculates column width
* @param g
* @param iCol
*/
private void recalcColWidth(Graphics g, int iCol) {
int maxWidth = 0;
SheetModel model = table.getModel();
CellRenderer renderer = table.getDefaultRenderer();
for(int iRow=0; iRow<model.getRowCount(); /* ath the end*/) {
CellPosition pos = model.getOrigin(new CellPosition(iRow, iCol));
Dimension size = model.getSize(pos);
if(size.height > 1 || size.width > 1) {
iRow += size.height;
continue;
}
int iWidth = renderer.getPreferredWidth(table, g, model.getValueAt(pos));
if(iWidth > maxWidth) {
maxWidth = iWidth;
}
iRow++;
}
Column column = model.getColumn(iCol);
if(maxWidth < column.getMinWidth()) {
column.setWidth(column.getMinWidth());
} else if(maxWidth == 0) {
//min width may be zero
column.setDefaultWidth();
} else if(maxWidth > column.getMaxWidth()) {
column.setWidth(column.getMaxWidth());
} else {
column.setWidth(maxWidth);
}
}
private void recalcRowHeight(Graphics g, int iRow) {
int maxHeight = 0;
SheetModel model = table.getModel();
CellRenderer renderer = table.getDefaultRenderer();
for(int iCol=0; iCol<model.getColumnCount(); /* ath the end*/) {
CellPosition pos = model.getOrigin(new CellPosition(iRow, iCol));
Dimension size = model.getSize(pos);
if(size.height > 1 || size.width > 1) {
iCol += size.width;
continue;
}
int width = model.getColumn(iCol).getWidth();
int iHeight = renderer.getPreferredHeight(table, g, model.getValueAt(pos), width);
if(iHeight > maxHeight) {
maxHeight = iHeight;
}
iCol++;
}
Row row = model.getRow(iRow);
if(maxHeight < row.getMinHeight()) {
row.setHeight(row.getMinHeight());
} else if(maxHeight == 0) {
//min width may be zero
row.setDefaultHeight();
} else if(maxHeight > row.getMaxHeight()) {
row.setHeight(row.getMaxHeight());
} else {
row.setHeight(maxHeight);
}
}
/**
*
* @param c
* @return
*/
@Override
public Dimension getMaximumSize(JComponent c) {
return new Dimension(65000, 65000);
}
private Dimension getBorderedDimensions(JComponent c) {
Border border = c.getBorder();
Dimension res = new Dimension();
if(border != null) {
Insets insets = border.getBorderInsets(c);
res.height += insets.top + insets.bottom;
res.width += insets.left + insets.right;
}
return res;
}
/**
*
* @param c
* @return
*/
@Override
public Dimension getMinimumSize(JComponent c) {
SheetModel model = table.getModel();
Dimension res = getBorderedDimensions(c);
for(int i=0; i<model.getRowCount(); i++) {
res.height += model.getRow(i).getMinHeight();
}
for(int i=0; i<model.getColumnCount(); i++) {
res.width += model.getColumn(i).getMinWidth();
}
return res;
}
@Override
public Dimension getPreferredSize(JComponent c) {
SheetModel model = table.getModel();
Dimension res = getBorderedDimensions(c);
for(int i=0; i<model.getRowCount(); i++) {
res.height += model.getRow(i).getHeight();
}
for(int i=0; i<model.getColumnCount(); i++) {
res.width += model.getColumn(i).getWidth();
}
return res;
}
private class MyKeyboard extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
if(table.getEditingCell() != null) {
return;
}
SheetSelectionModel sm = table.getSelectionModel();
if(sm.isSelectionEmpty() || sm.getValueIsAdjusting()) {
return;
}
CellPosition lead = getNextLead(e, sm.getLead());
if(lead == null) {
return;
}
CellPosition ankor = null;
if((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) > 0) {
ankor = sm.getAnchor();
} else {
ankor = lead;
}
sm.setSelectionInterval(ankor, lead);
}
private CellPosition getNextLead(KeyEvent e, CellPosition lead) {
SheetModel model = table.getModel();
lead = model.getOrigin(lead);
switch(e.getKeyCode()) {
case KeyEvent.VK_DOWN : {
Dimension size = model.getSize(lead);
if(lead.row + size.height < model.getRowCount()) {
return model.getOrigin(new CellPosition(lead.row+size.height, lead.col));
}
break;
}
case KeyEvent.VK_UP : {
if(lead.row -1 >= 0) {
return model.getOrigin(new CellPosition(lead.row-1, lead.col));
}
break;
}
case KeyEvent.VK_RIGHT : {
Dimension size = model.getSize(lead);
if(lead.col + size.width < model.getColumnCount()) {
return model.getOrigin(new CellPosition(lead.row, lead.col+size.width));
}
break;
}
case KeyEvent.VK_LEFT : {
if(lead.col - 1 >= 0) {
return model.getOrigin(new CellPosition(lead.row, lead.col-1));
}
break;
}
}
return null;
}
}
private class MyMouse extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
table.requestFocus();
SheetSelectionModel semod = table.getSelectionModel();
CellPosition pos = table.getCellAtPoint(e.getPoint());
if(pos == null) {
return;
}
pos = table.getModel().getOrigin(pos);
if(!semod.getValueIsAdjusting()) {
semod.setValueIsAdjusting(true);
semod.setSelectionInterval(pos, pos);
} else {
semod.addSelectionInterval(pos);
}
}
@Override
public void mouseReleased(MouseEvent e) {
SheetSelectionModel semod = table.getSelectionModel();
semod.setValueIsAdjusting(false);
}
@Override
public void mouseDragged(MouseEvent e) {
SheetSelectionModel semod = table.getSelectionModel();
CellPosition pos = table.getCellAtPoint(e.getPoint());
if(pos == null) {
return;
}
if(semod.getValueIsAdjusting()) {
semod.addSelectionInterval(pos);
}
}
}
/**
*
*/
private class ColumnsListener implements ColumnListener {
@Override
public void widthChanged(int index) {}
@Override
public void recalcNeeded(int index) {
recalcCols.add(index);
}
}
/**
*
*/
private class RowsListener implements RowListener {
@Override
public void heightChanged(int index) {}
@Override
public void recalcNeeded(int index) {
recalcRows.add(index);
}
}
}