/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.table;
import com.sun.lwuit.Component;
import com.sun.lwuit.Container;
import com.sun.lwuit.Display;
import com.sun.lwuit.Form;
import com.sun.lwuit.Graphics;
import com.sun.lwuit.Label;
import com.sun.lwuit.TextArea;
import com.sun.lwuit.TextField;
import com.sun.lwuit.events.ActionEvent;
import com.sun.lwuit.events.ActionListener;
import com.sun.lwuit.events.DataChangedListener;
import com.sun.lwuit.geom.Rectangle;
import com.sun.lwuit.plaf.Border;
import com.sun.lwuit.plaf.Style;
/**
* The table class represents a grid of data that can be used for rendering a grid
* of components/labels. The table reflects and updates the underlying model data.
*
* @author Shai Almog
*/
public class Table extends Container {
private TableModel model;
private Listener listener = new Listener();
private boolean drawBorder = true;
private boolean includeHeader = true;
/**
* Indicates the alignment of the title see label alignment for details
*
* @see com.sun.lwuit.Label#setAlignment(int)
*/
private int titleAlignment = Label.CENTER;
/**
* Indicates the alignment of the cells see label alignment for details
*
* @see com.sun.lwuit.Label#setAlignment(int)
*/
private int cellAlignment = Label.LEFT;
/**
* This flag allows us to workaround issue 275 without incuring too many updateModel calls
*/
private boolean potentiallyDirtyModel;
/**
* Create a table with a new model
*
* @param model the model underlying this table
*/
public Table(TableModel model) {
this.model = model;
updateModel();
setUIID("Table");
}
/**
* Create a table with a new model
*
* @param model the model underlying this table
* @param includeHeader Indicates whether the table should render a table header as the first row
*/
public Table(TableModel model, boolean includeHeader) {
setUIID("Table");
this.includeHeader = includeHeader;
this.model = model;
updateModel();
}
/**
* Returns the selected row in the table
*
* @return the offset of the selected row in the table if a selection exists
*/
public int getSelectedRow() {
Form f = getComponentForm();
if(f != null) {
Component c = f.getFocused();
if(c != null) {
return getCellRow(c);
}
}
return -1;
}
/**
* Returns the selected column in the table
*
* @return the offset of the selected column in the table if a selection exists
*/
public int getSelectedColumn() {
Form f = getComponentForm();
if(f != null) {
Component c = f.getFocused();
if(c != null) {
return getCellColumn(c);
}
}
return -1;
}
private void updateModel() {
int selectionRow = -1, selectionColumn = -1;
Form f = getComponentForm();
if(f != null) {
Component c = f.getFocused();
if(c != null) {
selectionRow = getCellRow(c);
selectionColumn = getCellColumn(c);
}
}
removeAll();
int columnCount = model.getColumnCount();
// another row for the table header
if(includeHeader) {
setLayout(new TableLayout(model.getRowCount() + 1, columnCount));
for(int iter = 0 ; iter < columnCount ; iter++) {
String name = model.getColumnName(iter);
Component header = createCellImpl(name, -1, iter, false);
TableLayout.Constraint con = createCellConstraint(name, -1, iter);
addComponent(con, header);
}
} else {
setLayout(new TableLayout(model.getRowCount(), columnCount));
}
for(int r = 0 ; r < model.getRowCount() ; r++) {
for(int c = 0 ; c < columnCount ; c++) {
Object value = model.getValueAt(r, c);
// null should be returned for spanned over values
if(value != null) {
boolean e = model.isCellEditable(r, c);
Component cell = createCellImpl(value, r, c, e);
if(cell != null) {
TableLayout.Constraint con = createCellConstraint(value, r, c);
// returns the current row we iterate about
int currentRow = ((TableLayout)getLayout()).getNextRow();
if(currentRow > model.getRowCount()) {
return;
}
addComponent(con, cell);
if(r == selectionRow && c == selectionColumn) {
cell.requestFocus();
}
}
}
}
}
}
/**
* @inheritDoc
*/
protected void paintGlass(Graphics g) {
if(drawBorder) {
int xPos = getAbsoluteX();
int yPos = getAbsoluteY();
g.translate(xPos, yPos);
int rows = model.getRowCount();
int cols = model.getColumnCount();
if(includeHeader) {
rows++;
}
g.setColor(getStyle().getFgColor());
TableLayout t = (TableLayout)getLayout();
int actualWidth = Math.max(getWidth(), getScrollDimension().getWidth());
if(t.hasVerticalSpanning()) {
// iterate over the components and draw a line at the bottom of all
// the components other than the ones that are at the last row.
int compCount = getComponentCount();
int padd = getStyle().getPadding(LEFT) + getStyle().getMargin(RIGHT);
for(int iter = 0 ; iter < compCount ; iter++) {
Component c = getComponentAt(iter);
if(getCellRow(c) != rows - 1) {
int y = c.getY() + c.getHeight();
int left = c.getStyle().getMargin(LEFT);
int right = c.getStyle().getMargin(RIGHT);
g.drawLine(c.getX() - left - padd, y, c.getX() + c.getWidth() + left + right - padd, y);
}
}
} else {
// this is much faster since we don't need to check spanning
for(int row = 1 ; row < rows; row++) {
int y = t.getRowPosition(row);
g.drawLine(0, y, actualWidth, y);
}
}
int actualHeight = Math.max(getHeight(), getScrollDimension().getHeight());
if(t.hasHorizontalSpanning()) {
// iterate over the components and draw a line on the side of all
// the components other than the ones that are at the last column.
int compCount = getComponentCount();
for(int iter = 0 ; iter < compCount ; iter++) {
Component c = getComponentAt(iter);
int cellColumn = getCellColumn(c);
int cellRow = getCellRow(c);
// if this isn't the last column
if(cellColumn != cols - 1 && cellColumn + t.getCellHorizontalSpan(cellRow, cellColumn) - 1 != cols - 1) {
int x = t.getColumnPosition(cellColumn);
int y = t.getRowPosition(cellRow);
int rowHeight;
int columnWidth = t.getColumnPosition(cellColumn + 1) - x;
if(cellRow < getModel().getRowCount() - 1) {
rowHeight = t.getRowPosition(cellRow + 1) - y;
} else {
rowHeight = getHeight() - y;
}
g.drawLine(x + columnWidth, y, x + columnWidth, y + rowHeight);
}
}
} else {
for(int col = 1 ; col < cols ; col++) {
int x = t.getColumnPosition(col);
g.drawLine(x, 0, x, actualHeight);
}
}
g.translate(-xPos, -yPos);
}
}
private Component createCellImpl(Object value, final int row, final int column, boolean editable) {
Component c = createCell(value, row, column, editable);
c.putClientProperty("row", new Integer(row));
c.putClientProperty("column", new Integer(column));
// we do this here to allow subclasses to return a text area or its subclass
if(c instanceof TextArea) {
((TextArea)c).addActionListener(listener);
}
Style s = c.getSelectedStyle();
s.setMargin(0, 0, 0, 0);
if(drawBorder) {
s.setBorder(null);
s = c.getUnselectedStyle();
s.setBorder(null);
} else {
s = c.getUnselectedStyle();
}
s.setBgTransparency(0);
s.setMargin(0, 0, 0, 0);
return c;
}
/**
* Creates a cell based on the given value
*
* @param value the new value object
* @param row row number, -1 for the header rows
* @param column column number
* @param editable true if the cell is editable
* @return cell component instance
*/
protected Component createCell(Object value, int row, int column, boolean editable) {
if(row == -1) {
Label header = new Label((String)value);
header.setUIID("TableHeader");
header.setAlignment(titleAlignment);
header.setFocusable(true);
return header;
}
if(editable) {
TextField cell = new TextField("" + value, -1);
cell.setLeftAndRightEditingTrigger(false);
cell.setUIID("TableCell");
return cell;
}
Label cell = new Label("" + value);
cell.setUIID("TableCell");
cell.setAlignment(cellAlignment);
cell.setFocusable(true);
return cell;
}
/**
* @inheritDoc
*/
public void initComponent() {
// this can happen if deinitialize is invoked due to a menu command which modifies
// the content of the table while the listener wasn't bound
if(potentiallyDirtyModel) {
updateModel();
potentiallyDirtyModel = false;
}
model.addDataChangeListener(listener);
}
/**
* @inheritDoc
*/
public void deinitialize() {
// we unbind the listener to prevent a memory leak for the use case of keeping
// the model while discarding the component
potentiallyDirtyModel = true;
model.removeDataChangeListener(listener);
}
/**
* Replaces the underlying model
*
* @param model the new model
*/
public void setModel(TableModel model) {
this.model = model;
updateModel();
revalidate();
}
/**
* Returns the model instance
*
* @return the model instance
*/
public TableModel getModel() {
return model;
}
/**
* Indicates whether the table border should be drawn
*
* @return the drawBorder
*/
public boolean isDrawBorder() {
return drawBorder;
}
/**
* Indicates whether the table border should be drawn
*
* @param drawBorder the drawBorder to set
*/
public void setDrawBorder(boolean drawBorder) {
this.drawBorder = drawBorder;
repaint();
}
/**
* Indicates the alignment of the title see label alignment for details
*
* @return the title alignment
* @see com.sun.lwuit.Label#setAlignment(int)
*/
public int getTitleAlignment() {
return titleAlignment;
}
/**
* Indicates the alignment of the title see label alignment for details
*
* @param titleAlignment the title alignment
* @see com.sun.lwuit.Label#setAlignment(int)
*/
public void setTitleAlignment(int titleAlignment) {
this.titleAlignment = titleAlignment;
repaint();
}
/**
* Returns the column in which the given cell is placed
*
* @param cell the component representing the cell placed in the table
* @return the column in which the cell was placed in the table
*/
public int getCellColumn(Component cell) {
Integer i = ((Integer)cell.getClientProperty("column"));
if(i != null) {
return i.intValue();
}
return -1;
}
/**
* Returns the row in which the given cell is placed
*
* @param cell the component representing the cell placed in the table
* @return the row in which the cell was placed in the table
*/
public int getCellRow(Component cell) {
Integer i = ((Integer)cell.getClientProperty("row"));
if(i != null) {
return i.intValue();
}
return -1;
}
/**
* Indicates the alignment of the cells see label alignment for details
*
* @see com.sun.lwuit.Label#setAlignment(int)
* @return the cell alignment
*/
public int getCellAlignment() {
return cellAlignment;
}
/**
* Indicates the alignment of the cells see label alignment for details
*
* @param cellAlignment the table cell alignment
* @see com.sun.lwuit.Label#setAlignment(int)
*/
public void setCellAlignment(int cellAlignment) {
this.cellAlignment = cellAlignment;
repaint();
}
/**
* Indicates whether the table should render a table header as the first row
*
* @return the includeHeader
*/
public boolean isIncludeHeader() {
return includeHeader;
}
/**
* Indicates whether the table should render a table header as the first row
*
* @param includeHeader the includeHeader to set
*/
public void setIncludeHeader(boolean includeHeader) {
this.includeHeader = includeHeader;
}
/**
* Creates the table cell constraint for the given cell, this method can be overriden for
* the purposes of modifying the table constraints.
*
* @param value the value of the cell
* @param row the table row
* @param column the table column
* @return the table constraint
*/
protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) {
if(includeHeader) {
row++;
}
TableLayout t = (TableLayout)getLayout();
return t.createConstraint(row, column);
}
class Listener implements DataChangedListener, ActionListener {
/**
* @inheritDoc
*/
public final void dataChanged(int row, int column) {
Object value = model.getValueAt(row, column);
boolean e = model.isCellEditable(row, column);
Component cell = createCellImpl(value, row, column, e);
TableLayout t = (TableLayout)getLayout();
TableLayout.Constraint con = createCellConstraint(value, row, column);
if(includeHeader) {
row++;
}
removeComponent(t.getComponentAt(row, column));
addComponent(con, cell);
layoutContainer();
cell.requestFocus();
revalidate();
}
public void actionPerformed(ActionEvent evt) {
TextArea t = (TextArea)evt.getSource();
int row = getCellRow(t);
int column = getCellColumn(t);
getModel().setValueAt(row, column, t.getText());
}
}
}