/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.gui.swing.referencing;
// J2SE dependencies
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.TableModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer; // For javadoc
import javax.swing.table.DefaultTableCellRenderer;
// OpenGIS dependencies
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.opengis.geometry.DirectPosition;
// Geotools dependencies
import org.geotools.referencing.CRS;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.geometry.TransformedDirectPosition;
/**
* A table of {@linkplain DirectPosition direct positions}. All coordinates contained in this
* table have the same {@linkplain CoordinateReferenceSystem coordinate reference system}, which
* is specified at construction time.
* <p>
* This table model provides a way to display invalid coordinates in a different color.
* <cite>Invalide coordinates</cite> are defined here as coordinates outside the CRS
* {@linkplain CoordinateReferenceSystem#getValidArea valid area}. This color display
* can be enabled by the following code:
*
* <blockquote><pre>
* CoordinateTableModel model = new CoordinateTableModel(crs);
* {@linkplain JTable} view = new JTable(model);
* {@linkplain TableCellRenderer} renderer = new {@linkplain CellRenderer}();
* view.setDefaultRenderer({@linkplain Double}.class, renderer);
* </pre></blockquote>
*
* @since 2.3
* @version $Id$
* @source $URL$
* @author Cédric Briançon
* @author Hoa Nguyen
* @author Martin Desruisseaux
*/
public class CoordinateTableModel extends AbstractTableModel {
/**
* The CRS for all coordinates in this table. This is specified by the user
* at construction time.
*/
private final CoordinateReferenceSystem crs ;
/**
* The columns table names. They are inferred from the table CRS specified
* at construction time.
*/
private final String[] columnNames;
/**
* The direct positions to display in the table.
*/
private final List/*<DirectPosition>*/ positions = new ArrayList/*<DirectPosition>*/();
/**
* An unmodifiable view of the positions list. This is the view returned by public accessors.
* We do not allow addition or removal of positions through this list because such changes
* would not invoke the proper {@code fire} method.
*/
private final List/*<DirectPosition>*/ unmodifiablePositions = Collections.unmodifiableList(positions);
/**
* The CRS valid area.
*/
private final GeneralEnvelope validArea;
/**
* For transformation frop the table CRS to WGS84.
*/
private final TransformedDirectPosition toWGS84 = new TransformedDirectPosition();
/**
* Creates an initially empty table model using the specified coordinate reference system.
*/
public CoordinateTableModel(final CoordinateReferenceSystem crs) {
this.crs = crs;
final CoordinateSystem cs = crs.getCoordinateSystem();
columnNames = new String[cs.getDimension()];
for (int i=0; i<columnNames.length; i++){
columnNames[i] = crs.getCoordinateSystem().getAxis(i).getName().getCode();
}
validArea = new GeneralEnvelope(CRS.getEnvelope(crs));
}
/**
* Returns the CRS for this table model
*/
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return crs;
}
/**
* Returns all direct positions in this table.
*
* @see #add(DirectPosition)
* @see #add(Collection)
*/
public List/*<DirectPosition>*/ getPositions() {
return unmodifiablePositions;
}
/**
* Returns the number of rows in the table.
*/
public int getRowCount() {
return positions.size();
}
/**
* Returns the number of columns.
*/
public int getColumnCount() {
return columnNames.length;
}
/**
* Returns the name for the specified column. The default implementation
* returns the name of the corresponding axis in the table CRS.
*/
public String getColumnName(final int columnIndex){
if (columnIndex>=0 && columnIndex<columnNames.length) {
return columnNames[columnIndex];
} else {
return super.getColumnName(columnIndex);
}
}
/**
* Returns tye type of data for the specified column. For coordinate table,
* this is always {@code Double.class}.
*/
public Class getColumnClass(int columnIndex) {
return Double.class;
}
/**
* Adds a direct position to this table. The position is not cloned. Any cell edited in this
* table will write its change directly into the corresponding {@code DirectPosition} object.
*/
public void add(final DirectPosition newPosition) {
final int index = positions.size();
positions.add(newPosition);
fireTableRowsInserted(index, index);
}
/**
* Adds a collection of direct positions to this table. The position is not cloned.
* Any cell edited in this table will write its change directly into the corresponding
* {@code DirectPosition} object.
*/
public void add(final Collection/*<DirectPosition>*/ newPositions) {
final int lower = positions.size();
positions.addAll(newPositions);
final int upper = positions.size();
fireTableRowsInserted(lower, upper-1);
}
/**
* Returns the value in the table at the specified postion.
*
* @param rowIndex Cell row number.
* @param columnIndex Cell column number.
* @return The ordinate value, or {@code null} if no value is available for the specified cell.
*/
public Object getValueAt(final int rowIndex, final int columnIndex) {
if (rowIndex >= 0 && rowIndex < positions.size()) {
final DirectPosition position = (DirectPosition) positions.get(rowIndex);
if (position != null && columnIndex >= 0 && columnIndex < position.getDimension()) {
final double ordinate = position.getOrdinate(columnIndex);
if (!Double.isNaN(ordinate)) {
return new Double(ordinate);
}
}
}
return null;
}
/**
* Sets the value for the specified cell.
*
* @param value The new value for the cell.
* @param rowIndex Row number of the cell modified.
* @param columnIndex Column number of the cell modified.
*/
public void setValueAt(final Object value, final int rowIndex, final int columnIndex) {
final double ordinate = ((Number) value).doubleValue();
((DirectPosition) positions.get(rowIndex)).setOrdinate(columnIndex, ordinate);
fireTableCellUpdated(rowIndex, columnIndex);
}
/**
* Specifies that the user can fill all rows in the table.
*/
public boolean isCellEditable(int rowIndex, int colIndex) {
return true;
}
/**
* Returns {@code true} if the position at the specified row is inside the CRS
* {@linkplain CoordinateReferenceSystem#getValidArea valid area}. This method
* is invoked by {@link CellRenderer} in order to determine if this row should
* be colorized.
*/
public boolean isValidCoordinate(final int rowIndex) {
final DirectPosition position = (DirectPosition) positions.get(rowIndex);
try {
toWGS84.transform(position);
} catch (TransformException e) {
/*
* If the coordinate can't be transformed, then there is good chances
* that the the coordinate is outside the CRS valid area.
*/
return false;
}
return validArea.contains(toWGS84);
}
/**
* Returns a string representation of this table. The default implementation
* list all coordinates.
*/
public String toString() {
final StringBuffer buffer = new StringBuffer();
final String lineSeparator = System.getProperty("line.separator", "\n");
final int size = positions.size();
for (int i=0; i<size; i++) {
buffer.append(positions.get(i));
buffer.append(lineSeparator);
}
return buffer.toString();
}
/**
* A cell renderer for the {@linkplain CoordinateTableModel coordinate table model}.
* This cell renderer can display in a different color coordinates outside the CRS
* {@linkplain CoordinateReferenceSystem#getValidArea valid area}. Coordinate validity
* is determined by invoking {@link CoordinateTableModel#isValidCoordinate}.
*
* @since 2.3
* @version $Id$
* @source $URL$
* @author Cédric Briançon
* @author Martin Desruisseaux
*/
public static class CellRenderer extends DefaultTableCellRenderer {
/**
* The default text and background color.
*/
private Color foreground, background;
/**
* The text and background color for invalid coordinates.
*/
private Color invalidForeground = Color.RED, invalidBackground;
/**
* Creates a default cell renderer for {@link CoordinateTableModel}.
*/
public CellRenderer() {
super();
foreground = super.getForeground();
background = super.getBackground();
}
/**
* Specifies the text color for valid coordinates.
*/
public void setForeground(final Color foreground){
this.foreground = foreground;
super.setForeground(foreground);
}
/**
* Specifies the background color for valid coordinates.
*/
public void setBackground(final Color background){
this.background = background;
super.setBackground(background);
}
/**
* Specified the text and background colors for invalid coordinates,
* or {@code null} for the same color than valid coordinates.
*/
public void setInvalidColor(final Color foreground, final Color background) {
this.invalidForeground = foreground;
this.invalidBackground = background;
}
/**
* Returns the component for cell rendering.
*/
public Component getTableCellRendererComponent(final JTable table, final Object value,
final boolean isSelected, final boolean hasFocus, final int row, final int column)
{
Color foreground = this.foreground;
Color background = this.background;
final TableModel candidate = table.getModel();
if (candidate instanceof CoordinateTableModel) {
final CoordinateTableModel model = (CoordinateTableModel) candidate;
if (!model.isValidCoordinate(row)) {
if (invalidForeground != null) foreground = invalidForeground;
if (invalidBackground != null) background = invalidBackground;
}
}
super.setBackground(background);
super.setForeground(foreground);
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}