com.codename1.ui.table.Table Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui.table;
import com.codename1.io.Util;
import com.codename1.ui.Button;
import com.codename1.ui.CheckBox;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.spinner.Picker;
import com.codename1.ui.validation.Constraint;
import com.codename1.ui.validation.Validator;
import com.codename1.util.CaseInsensitiveOrder;
import java.util.Comparator;
import java.util.Date;
/**
* The {@code 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.
* {@code Table} relies heavily on the {@link com.codename1.ui.table.TableLayout} class and
* {@link com.codename1.ui.table.TableModel} interface to present its UI. Unlike a
* {@link com.codename1.ui.List} a {@code Table} doesn't feature a separate renderer
* and instead allows developers to derive the class.
*
*
*
*
*
*
*
* @author Shai Almog
*/
public class Table extends Container {
/**
* Constant denoting that inner borders should not be drawn at all
*/
public static final int INNER_BORDERS_NONE = 0;
/**
* Constant denoting that only inner borders rows should be drawn
*/
public static final int INNER_BORDERS_ROWS = 1;
/**
* Constant denoting that only inner borders columns should be drawn
*/
public static final int INNER_BORDERS_COLS = 2;
/**
* Constant denoting that inner borders should be drawn fully
*/
public static final int INNER_BORDERS_ALL = 3;
private TableModel model;
private Listener listener = new Listener();
private boolean drawBorder = true;
private boolean collapseBorder = true;
private boolean drawEmptyCellsBorder = true;
private int horizontalBorderSpacing = 0;
private int verticalBorderSpacing = 0;
private boolean includeHeader = true;
private int innerBorder = INNER_BORDERS_ALL;
/**
* Indicates the alignment of the title see label alignment for details
*
* @see com.codename1.ui.Label#setAlignment(int)
*/
private int titleAlignment = Label.CENTER;
/**
* Indicates the alignment of the cells see label alignment for details
*
* @see com.codename1.ui.Label#setAlignment(int)
*/
private int cellAlignment = Label.LEFT;
/**
* This flag allows us to workaround issue 275 without incurring too many updateModel calls
*/
private boolean potentiallyDirtyModel;
/**
* Sort support can be toggled with this flag
*/
private boolean sortSupported;
private int sortedColumn = -1;
private boolean ascending;
/**
* Constructor for usage by GUI builder and automated tools, normally one
* should use the version that accepts the model
*/
public Table() {
this(new DefaultTableModel(new String[]{"Col1", "Col2"}, new String[][]{
{"1", "2"},
{"3", "4"}}));
}
/**
* Create a table with a new model
*
* @param model the model underlying this table
*/
public Table(TableModel model) {
setUIID("Table");
this.model = model;
updateModel();
}
/**
* 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;
}
/**
* By default createCell/constraint won't be invoked for null values by overriding this method to return true
* you can replace this behavior
* @return false by default
*/
protected boolean includeNullValues() {
return false;
}
/**
* 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 || includeNullValues()) {
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(r > model.getRowCount()) {
return;
}
addComponent(con, cell);
if(r == selectionRow && c == selectionColumn) {
cell.requestFocus();
}
}
}
}
}
}
/**
* {@inheritDoc}
*/
protected void paintGlass(Graphics g) {
if ((drawBorder) && (innerBorder!=INNER_BORDERS_NONE)) {
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());
int alpha = g.concatenateAlpha(getStyle().getFgAlpha());
TableLayout t = (TableLayout)getLayout();
int actualWidth = Math.max(getWidth(), getScrollDimension().getWidth());
int actualHeight = Math.max(getHeight(), getScrollDimension().getHeight());
if ((collapseBorder) || (innerBorder!=INNER_BORDERS_ALL) || // inner borders cols/rows are supported only in collapsed mode
(t.hasHorizontalSpanning()) || (t.hasVerticalSpanning())) { // TODO - We currently don't support separate borders for tables with spanned cells
if ((innerBorder==INNER_BORDERS_ALL) || (innerBorder==INNER_BORDERS_ROWS)) {
if(t.hasVerticalSpanning()) {
// 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.
for(int cellRow = 0 ; cellRow < rows - 1; cellRow++) {
for(int cellColumn = 0 ; cellColumn < cols ; cellColumn++) {
// if this isn't the last row
if(cellRow + t.getCellVerticalSpan(cellRow, cellColumn) - 1 != rows - 1) {
// if this is a spanned through cell we don't want to draw a line here
if(t.isCellSpannedThroughHorizontally(cellRow, cellColumn)) {
continue;
}
int x = t.getColumnPosition(cellColumn);
int y = t.getRowPosition(cellRow);
int rowHeight = t.getRowPosition(cellRow + t.getCellVerticalSpan(cellRow, cellColumn)) - y;
int columnWidth;
if(cellColumn < getModel().getColumnCount() - 1) {
columnWidth = t.getColumnPosition(cellColumn + 1) - x;
} else {
columnWidth = getWidth() - y;
}
if ((innerBorder!=INNER_BORDERS_ROWS) || (shouldDrawInnerBorderAfterRow(cellRow))) {
g.drawLine(x, y + rowHeight, x + columnWidth, y + rowHeight);
}
}
}
}
} 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);
if ((innerBorder!=INNER_BORDERS_ROWS) || (shouldDrawInnerBorderAfterRow(row-1))) {
g.drawLine(0, y, actualWidth, y);
}
//g.drawLine(0+2, y+2, actualWidth-2, y+2);
}
}
}
if ((innerBorder==INNER_BORDERS_ALL) || (innerBorder==INNER_BORDERS_COLS)) {
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.
for(int cellRow = 0 ; cellRow < rows ; cellRow++) {
for(int cellColumn = 0 ; cellColumn < cols - 1 ; cellColumn++) {
// if this isn't the last column
if(cellColumn + t.getCellHorizontalSpan(cellRow, cellColumn) - 1 != cols - 1) {
// if this is a spanned through cell we don't want to draw a line here
if(t.isCellSpannedThroughVertically(cellRow, cellColumn)) {
continue;
}
int x = t.getColumnPosition(cellColumn);
int y = t.getRowPosition(cellRow);
int rowHeight;
int columnWidth = t.getColumnPosition(cellColumn + t.getCellHorizontalSpan(cellRow, cellColumn)) - 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);
}
if(t.getCellHorizontalSpan(cellRow, cellColumn) > 1){
cellColumn += t.getCellHorizontalSpan(cellRow, cellColumn) - 1;
}
}
}
} else {
for(int col = 1 ; col < cols ; col++) {
int x = t.getColumnPosition(col);
g.drawLine(x, 0, x, actualHeight);
//g.drawLine(x+2, 0+2, x+2, actualHeight-2);
}
}
}
} else { // separate border
//if ((!t.hasHorizontalSpanning()) && (!t.hasVerticalSpanning())) {
for(int row = 0 ; row < rows; row++) {
int y = t.getRowPosition(row);
int h;
if (row+10) &&
(comp.getHeight()-comp.getStyle().getPaddingTop() - comp.getStyle().getPaddingBottom()>0)))) {
int rightMargin=comp.getStyle().getMarginRightNoRTL();
int bottomMargin=comp.getStyle().getMarginBottom();
if (col==0) {
rightMargin*=2; // Since the first cell includes margins from both sides (left/right) so the next cell location is farther away - but we don't want to paint the border up to it
}
if (row==0) {
bottomMargin*=2;
}
g.drawRect(x+comp.getStyle().getMarginLeftNoRTL(), y+comp.getStyle().getMarginTop(), w-2-rightMargin, h-2-bottomMargin);
}
}
}
}
g.translate(-xPos, -yPos);
g.setAlpha(alpha);
}
}
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);
} else {
if(c instanceof Button) {
((Button)c).addActionListener(listener);
}
}
Style s = c.getSelectedStyle();
//s.setMargin(0, 0, 0, 0);
s.setMargin(verticalBorderSpacing, verticalBorderSpacing, horizontalBorderSpacing, horizontalBorderSpacing);
if ((drawBorder) && (innerBorder!=INNER_BORDERS_NONE)) {
s.setBorder(null);
s = c.getUnselectedStyle();
s.setBorder(null);
} else {
s = c.getUnselectedStyle();
}
//s.setBgTransparency(0);
//s.setMargin(0, 0, 0, 0);
s.setMargin(verticalBorderSpacing, verticalBorderSpacing, horizontalBorderSpacing, horizontalBorderSpacing);
return c;
}
/**
* Returns a generic comparator that tries to work in a way that will sort columns with similar object types.
* This method can be overriden to create custom sort orders or return null and thus disable sorting for a
* specific column
*
* @param column the column that's sorted
* @return the comparator instance
*/
protected Comparator createColumnSortComparator(int column) {
final CaseInsensitiveOrder ccmp = new CaseInsensitiveOrder();
return new Comparator() {
public int compare(Object o1, Object o2) {
if(o1 == null) {
if(o2 == null) {
return 0;
}
return -1;
} else {
if(o2 == null) {
return 1;
}
}
if(o1 instanceof String && o2 instanceof String) {
return ccmp.compare((String)o1, (String)o2);
}
try {
double d = Util.toDoubleValue(o1) - Util.toDoubleValue(o2);
if(d > 0) {
return 1;
}
if(d < 0) {
return -1;
}
} catch(IllegalArgumentException err) {
long dd = Util.toDateValue(o1).getTime() - Util.toDateValue(o2).getTime();
return (int)dd;
}
return 0;
}
};
}
/**
* Sorts the given column programmatically
* @param column the column to sort
* @param ascending true to sort in ascending order
*/
public void sort(int column, boolean ascending) {
sortedColumn = column;
Comparator cmp = createColumnSortComparator(column);
if(model instanceof SortableTableModel) {
model = ((SortableTableModel)model).getUnderlying();
}
setModel(new SortableTableModel(sortedColumn, ascending, model, cmp));
}
/**
* 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, final int column, boolean editable) {
if(row == -1) {
Button header = new Button((String)value, getUIID() + "Header");
header.getAllStyles().setAlignment(titleAlignment);
header.setTextPosition(LEFT);
if(isSortSupported()) {
header.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Comparator cmp = createColumnSortComparator(column);
if(cmp == null) {
return;
}
if(column == sortedColumn) {
ascending = !ascending;
} else {
sortedColumn = column;
ascending = false;
}
if(model instanceof SortableTableModel) {
model = ((SortableTableModel)model).getUnderlying();
}
setModel(new SortableTableModel(sortedColumn, ascending, model, cmp));
}
});
if(sortedColumn == column) {
if(ascending) {
FontImage.setMaterialIcon(header, FontImage.MATERIAL_ARROW_DROP_UP);
} else {
FontImage.setMaterialIcon(header, FontImage.MATERIAL_ARROW_DROP_DOWN);
}
}
}
return header;
}
int constraint = TextArea.ANY;
Constraint validation = null;
if(isAbstractTableModel()) {
Class type = ((AbstractTableModel)model).getCellType(row, column);
if(type == Boolean.class) {
CheckBox cell = new CheckBox();
cell.setSelected(Util.toBooleanValue(value));
cell.setUIID(getUIID() + "Cell");
cell.setEnabled(editable);
return cell;
}
if(editable && (type == null || type == String.class)) {
String[] multiChoice = ((AbstractTableModel)model).getMultipleChoiceOptions(row, column);
if(multiChoice != null) {
Picker cell = new Picker();
cell.setStrings(multiChoice);
if(value != null) {
cell.setSelectedString((String)value);
}
cell.setUIID(getUIID() + "Cell");
return cell;
}
}
if(editable && type == Date.class) {
Picker cell = new Picker();
cell.setType(Display.PICKER_TYPE_DATE);
if(value != null) {
cell.setDate((Date)value);
}
cell.setUIID(getUIID() + "Cell");
return cell;
}
if(type == Integer.class || type == Long.class || type == Short.class ||
type == Byte.class) {
constraint = TextArea.NUMERIC;
} else {
if(type == Float.class || type == Double.class) {
constraint = TextArea.DECIMAL;
}
}
if(((AbstractTableModel)model).getValidator() != null) {
validation = ((AbstractTableModel)model).getValidationConstraint(row, column);
}
}
if(editable) {
TextField cell = new TextField(value == null ? "" : "" + value, -1);
cell.setConstraint(constraint);
cell.setLeftAndRightEditingTrigger(false);
cell.setUIID(getUIID() + "Cell");
if(validation != null) {
Validator v = ((AbstractTableModel)model).getValidator();
v.addConstraint(cell, validation);
}
return cell;
}
Label cell = new Label(value == null ? "" : "" + value);
cell.setUIID(getUIID() + "Cell");
cell.getUnselectedStyle().setAlignment(cellAlignment);
cell.getSelectedStyle().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
// Prevent the model listener from being removed when the VKB is shown
if(!Display.getInstance().isVirtualKeyboardShowing()) {
potentiallyDirtyModel = true;
model.removeDataChangeListener(listener);
} else {
potentiallyDirtyModel = false;
}
}
/**
* 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() {
if(sortedColumn > -1) {
return ((SortableTableModel)model).getUnderlying();
}
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) {
if(this.drawBorder != drawBorder) {
this.drawBorder = drawBorder;
updateModel();
revalidate();
}
}
/**
* Sets how to draw the inner border (All of it, only rows/columns, none, groups)
* Note that setting to any mode other than NONE/ALL will result in the border drawing as collapsed whether this is a collpased border or not
*
* @param innerBorder one of the INNER_BORDER_* constants
*/
public void setInnerBorderMode(int innerBorder) {
if ((innerBorderINNER_BORDERS_ALL)) {
throw new IllegalArgumentException("Inner border mode must be one of the INNER_BORDER_* constants");
}
if(this.innerBorder != innerBorder) {
this.innerBorder=innerBorder;
updateModel();
revalidate();
}
}
/**
* Returns the current inner border mode
*
* @return the current inner border mode (one of the INNER_BORDER_* constants)
*/
public int getInnerBorderMode() {
return innerBorder;
}
/**
* Returns whether an inner border should be drawn after the specified row.
* This allows customization in subclasses to create for example the effects of segments in atable, i.e. instead of a line after each row - lines after "chunks" of rows.
* Note that this is queried only when the inner border mode is set to INNER_BORDER_ROWS
*
* @param row The row in question
* @return true to draw inner border, false otherwise
*/
protected boolean shouldDrawInnerBorderAfterRow(int row) {
return true;
}
/**
* Indicates whether the borders of the cells should collapse to form a one line border
*
* @param collapseBorder true to collapse (default), false for separate borders
*/
public void setCollapseBorder(boolean collapseBorder) {
if (this.collapseBorder!=collapseBorder) {
this.collapseBorder = collapseBorder;
if ((horizontalBorderSpacing!=0) || (verticalBorderSpacing!=0)) { // Only if one of the spacing was not 0, then we need to update, since otherwise the margin is 0 for both collapse and separate modes
updateMargins();
}
repaint();
}
}
/**
* Indicates whether empty cells should have borders (relevant only for separate borders and not for collapsed)
*
* @param drawEmptyCellsBorder - true to draw (default), false otherwise
*/
public void setDrawEmptyCellsBorder(boolean drawEmptyCellsBorder) {
this.drawEmptyCellsBorder = drawEmptyCellsBorder;
repaint();
}
/**
* Sets the spacing of cells border (relevant only for separate borders and not for collapsed)
*
* @param horizontal - The horizontal spacing
* @param vertical - The vertical spacing
*/
public void setBorderSpacing(int horizontal, int vertical) {
horizontalBorderSpacing=horizontal;
verticalBorderSpacing=vertical;
updateMargins();
}
private void updateMargins() {
TableLayout t = (TableLayout)getLayout();
int hSpace=horizontalBorderSpacing;
int vSpace=verticalBorderSpacing;
if (collapseBorder) { // not relevant for collapse border
hSpace=0;
vSpace=0;
}
if ((!t.hasHorizontalSpanning()) && (!t.hasVerticalSpanning())) {
for(int row = 0 ; row < t.getRows(); row++) {
for(int col = 0 ; col < t.getColumns() ; col++) {
Component cmp=null;
try {
cmp=t.getComponentAt(row, col);
} catch (Exception e) {
// parent of cmp can be null as well - TODO - check why
}
if (cmp!=null) {
int leftMargin=(col==0)?hSpace:0;
int topMargin=(row==0)?vSpace:0;
cmp.getUnselectedStyle().setMargin(topMargin, vSpace, leftMargin, hSpace);
cmp.getSelectedStyle().setMargin(topMargin, vSpace, leftMargin, hSpace);
}
}
}
}
repaint();
}
/**
* Indicates the alignment of the title see label alignment for details
*
* @return the title alignment
* @see com.codename1.ui.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.codename1.ui.Label#setAlignment(int)
*/
public void setTitleAlignment(int titleAlignment) {
this.titleAlignment = titleAlignment;
for(int iter = 0 ; iter < model.getColumnCount() ; iter++) {
listener.dataChanged(-1, iter);
}
}
/**
* 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.codename1.ui.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.codename1.ui.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;
updateModel();
}
/**
* 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);
}
/**
* {@inheritDoc}
*/
public String[] getPropertyNames() {
return new String[] {"data", "header"};
}
/**
* {@inheritDoc}
*/
public Class[] getPropertyTypes() {
return new Class[] {com.codename1.impl.CodenameOneImplementation.getStringArray2DClass(),
com.codename1.impl.CodenameOneImplementation.getStringArrayClass()};
}
/**
* {@inheritDoc}
*/
public String[] getPropertyTypeNames() {
return new String[] {"String[][]", "String[]"};
}
/**
* {@inheritDoc}
*/
public Object getPropertyValue(String name) {
if(name.equals("data")) {
String[][] result = new String[((DefaultTableModel)model).data.size()][];
for(int iter = 0 ; iter < result.length ; iter++) {
Object[] o = ((DefaultTableModel)model).data.get(iter);
String[] arr = new String[o.length];
result[iter] = arr;
for(int ai = 0 ; ai < arr.length ; ai++) {
Object current = o[ai];
if(current instanceof String) {
arr[ai] = (String)current;
} else {
if(current != null) {
arr[iter] = current.toString();
}
}
}
}
return result;
}
if(name.equals("header")) {
return ((DefaultTableModel)model).columnNames;
}
return null;
}
/**
* {@inheritDoc}
*/
public String setPropertyValue(String name, Object value) {
if(name.equals("data")) {
setModel(new DefaultTableModel(((DefaultTableModel)model).columnNames, (Object[][])value));
return null;
}
if(name.equals("header")) {
setModel(new DefaultTableModel((String[])value, ((DefaultTableModel)model).data, ((DefaultTableModel)model).editable));
return null;
}
return super.setPropertyValue(name, value);
}
/**
* If the table is sorted returns the position of the row in the actual
* underlying model
* @param row the row as it visually appears in the table or in the
* {@code createCell} method
* @return the position of the row in the physical model, this will be
* the same value if the table isn't sorted
*/
public int translateSortedRowToModelRow(int row) {
if(model instanceof SortableTableModel) {
return ((SortableTableModel)model).getSortedPosition(row);
}
return row;
}
/**
* Sort support can be toggled with this flag
* @return the sortSupported
*/
public boolean isSortSupported() {
return sortSupported;
}
/**
* Sort support can be toggled with this flag
* @param sortSupported the sortSupported to set
*/
public void setSortSupported(boolean sortSupported) {
if(this.sortSupported != sortSupported) {
this.sortSupported = sortSupported;
setModel(getModel());
}
}
class Listener implements DataChangedListener, ActionListener {
private int editingColumn = -1;
private int editingRow = -1;
/**
* {@inheritDoc}
*/
public final void dataChanged(int row, int column) {
if(row == Integer.MIN_VALUE) {
// special case... Rebuild the table
updateModel();
revalidate();
return;
}
// prevents the table from rebuilding on every text field edit which makes the table
// more usable on iOS devices with the VKB/Native editing
if(editingColumn == column && editingRow == row) {
editingColumn = -1;
editingRow = -1;
return;
}
Object value;
boolean e;
if(row < 0) {
e = false;
value = model.getColumnName(column);
} else {
value = model.getValueAt(row, column);
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++;
}
Component c = t.getComponentAt(row, column);
if(c != null) {
removeComponent(c);
// a repaint sent right before this might result in an artifact for some use cases so
// removing visibility essentially cancels repaints
c.setVisible(false);
}
addComponent(con, cell);
layoutContainer();
cell.requestFocus();
revalidate();
}
public void actionPerformed(ActionEvent evt) {
Component c = (Component)evt.getSource();
int row = getCellRow(c);
int column = getCellColumn(c);
if(c instanceof TextArea) {
TextArea t = (TextArea)c;
editingColumn = column;
editingRow = row;
if(isAbstractTableModel()) {
Class type = ((AbstractTableModel)model).getCellType(row, column);
if(type == Integer.class) {
model.setValueAt(row, column, t.getAsInt(0));
return;
}
if(type == Long.class) {
model.setValueAt(row, column, t.getAsLong(0));
return;
}
if(type == Short.class) {
model.setValueAt(row, column, (short)t.getAsInt(0));
return;
}
if(type == Byte.class) {
model.setValueAt(row, column, (byte)t.getAsInt(0));
return;
}
if(type == Float.class) {
model.setValueAt(row, column, (float)t.getAsDouble(0));
return;
}
if(type == Double.class) {
model.setValueAt(row, column, t.getAsDouble(0));
return;
}
if(type == Character.class) {
if(t.getText().length() > 0) {
model.setValueAt(row, column, t.getText().charAt(0));
}
return;
}
}
model.setValueAt(row, column, t.getText());
} else {
if(c instanceof Picker) {
switch(((Picker)c).getType()) {
case Display.PICKER_TYPE_DATE:
model.setValueAt(row, column, ((Picker)c).getDate());
break;
case Display.PICKER_TYPE_STRINGS:
model.setValueAt(row, column, ((Picker)c).getSelectedString());
break;
}
} else {
if(c instanceof CheckBox) {
model.setValueAt(row, column, ((CheckBox)c).isSelected());
}
}
}
}
}
private boolean isAbstractTableModel() {
if(model instanceof SortableTableModel) {
return ((SortableTableModel)model).getUnderlying() instanceof AbstractTableModel;
}
return model instanceof AbstractTableModel;
}
}