001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2006, Geomatys
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing.referencing;
018:
019: // J2SE dependencies
020: import java.util.List;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.awt.Color;
025: import java.awt.Component;
026: import javax.swing.JTable;
027: import javax.swing.table.TableModel;
028: import javax.swing.table.AbstractTableModel;
029: import javax.swing.table.TableCellRenderer; // For javadoc
030: import javax.swing.table.DefaultTableCellRenderer;
031:
032: // OpenGIS dependencies
033: import org.opengis.referencing.cs.CoordinateSystem;
034: import org.opengis.referencing.crs.CoordinateReferenceSystem;
035: import org.opengis.referencing.operation.TransformException;
036: import org.opengis.geometry.DirectPosition;
037:
038: // Geotools dependencies
039: import org.geotools.referencing.CRS;
040: import org.geotools.geometry.GeneralEnvelope;
041: import org.geotools.geometry.GeneralDirectPosition;
042: import org.geotools.geometry.TransformedDirectPosition;
043:
044: /**
045: * A table of {@linkplain DirectPosition direct positions}. All coordinates contained in this
046: * table have the same {@linkplain CoordinateReferenceSystem coordinate reference system}, which
047: * is specified at construction time.
048: * <p>
049: * This table model provides a way to display invalid coordinates in a different color.
050: * <cite>Invalide coordinates</cite> are defined here as coordinates outside the CRS
051: * {@linkplain CoordinateReferenceSystem#getValidArea valid area}. This color display
052: * can be enabled by the following code:
053: *
054: * <blockquote><pre>
055: * CoordinateTableModel model = new CoordinateTableModel(crs);
056: * {@linkplain JTable} view = new JTable(model);
057: * {@linkplain TableCellRenderer} renderer = new {@linkplain CellRenderer}();
058: * view.setDefaultRenderer({@linkplain Double}.class, renderer);
059: * </pre></blockquote>
060: *
061: * @since 2.3
062: * @version $Id: CoordinateTableModel.java 24925 2007-03-27 20:12:08Z jgarnett $
063: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/referencing/CoordinateTableModel.java $
064: * @author Cédric Briançon
065: * @author Hoa Nguyen
066: * @author Martin Desruisseaux
067: */
068: public class CoordinateTableModel extends AbstractTableModel {
069: /**
070: * The CRS for all coordinates in this table. This is specified by the user
071: * at construction time.
072: */
073: private final CoordinateReferenceSystem crs;
074:
075: /**
076: * The columns table names. They are inferred from the table CRS specified
077: * at construction time.
078: */
079: private final String[] columnNames;
080:
081: /**
082: * The direct positions to display in the table.
083: */
084: private final List/*<DirectPosition>*/positions = new ArrayList/*<DirectPosition>*/();
085:
086: /**
087: * An unmodifiable view of the positions list. This is the view returned by public accessors.
088: * We do not allow addition or removal of positions through this list because such changes
089: * would not invoke the proper {@code fire} method.
090: */
091: private final List/*<DirectPosition>*/unmodifiablePositions = Collections
092: .unmodifiableList(positions);
093:
094: /**
095: * The CRS valid area.
096: */
097: private final GeneralEnvelope validArea;
098:
099: /**
100: * For transformation frop the table CRS to WGS84.
101: */
102: private final TransformedDirectPosition toWGS84 = new TransformedDirectPosition();
103:
104: /**
105: * Creates an initially empty table model using the specified coordinate reference system.
106: */
107: public CoordinateTableModel(final CoordinateReferenceSystem crs) {
108: this .crs = crs;
109: final CoordinateSystem cs = crs.getCoordinateSystem();
110: columnNames = new String[cs.getDimension()];
111: for (int i = 0; i < columnNames.length; i++) {
112: columnNames[i] = crs.getCoordinateSystem().getAxis(i)
113: .getName().getCode();
114: }
115: validArea = new GeneralEnvelope(CRS.getEnvelope(crs));
116: }
117:
118: /**
119: * Returns the CRS for this table model
120: */
121: public CoordinateReferenceSystem getCoordinateReferenceSystem() {
122: return crs;
123: }
124:
125: /**
126: * Returns all direct positions in this table.
127: *
128: * @see #add(DirectPosition)
129: * @see #add(Collection)
130: */
131: public List/*<DirectPosition>*/getPositions() {
132: return unmodifiablePositions;
133: }
134:
135: /**
136: * Returns the number of rows in the table.
137: */
138: public int getRowCount() {
139: return positions.size();
140: }
141:
142: /**
143: * Returns the number of columns.
144: */
145: public int getColumnCount() {
146: return columnNames.length;
147: }
148:
149: /**
150: * Returns the name for the specified column. The default implementation
151: * returns the name of the corresponding axis in the table CRS.
152: */
153: public String getColumnName(final int columnIndex) {
154: if (columnIndex >= 0 && columnIndex < columnNames.length) {
155: return columnNames[columnIndex];
156: } else {
157: return super .getColumnName(columnIndex);
158: }
159: }
160:
161: /**
162: * Returns tye type of data for the specified column. For coordinate table,
163: * this is always {@code Double.class}.
164: */
165: public Class getColumnClass(int columnIndex) {
166: return Double.class;
167: }
168:
169: /**
170: * Adds a direct position to this table. The position is not cloned. Any cell edited in this
171: * table will write its change directly into the corresponding {@code DirectPosition} object.
172: */
173: public void add(final DirectPosition newPosition) {
174: final int index = positions.size();
175: positions.add(newPosition);
176: fireTableRowsInserted(index, index);
177: }
178:
179: /**
180: * Adds a collection of direct positions to this table. The position is not cloned.
181: * Any cell edited in this table will write its change directly into the corresponding
182: * {@code DirectPosition} object.
183: */
184: public void add(final Collection/*<DirectPosition>*/newPositions) {
185: final int lower = positions.size();
186: positions.addAll(newPositions);
187: final int upper = positions.size();
188: fireTableRowsInserted(lower, upper - 1);
189: }
190:
191: /**
192: * Returns the value in the table at the specified postion.
193: *
194: * @param rowIndex Cell row number.
195: * @param columnIndex Cell column number.
196: * @return The ordinate value, or {@code null} if no value is available for the specified cell.
197: */
198: public Object getValueAt(final int rowIndex, final int columnIndex) {
199: if (rowIndex >= 0 && rowIndex < positions.size()) {
200: final DirectPosition position = (DirectPosition) positions
201: .get(rowIndex);
202: if (position != null && columnIndex >= 0
203: && columnIndex < position.getDimension()) {
204: final double ordinate = position
205: .getOrdinate(columnIndex);
206: if (!Double.isNaN(ordinate)) {
207: return new Double(ordinate);
208: }
209: }
210: }
211: return null;
212: }
213:
214: /**
215: * Sets the value for the specified cell.
216: *
217: * @param value The new value for the cell.
218: * @param rowIndex Row number of the cell modified.
219: * @param columnIndex Column number of the cell modified.
220: */
221: public void setValueAt(final Object value, final int rowIndex,
222: final int columnIndex) {
223: final double ordinate = ((Number) value).doubleValue();
224: ((DirectPosition) positions.get(rowIndex)).setOrdinate(
225: columnIndex, ordinate);
226: fireTableCellUpdated(rowIndex, columnIndex);
227: }
228:
229: /**
230: * Specifies that the user can fill all rows in the table.
231: */
232: public boolean isCellEditable(int rowIndex, int colIndex) {
233: return true;
234: }
235:
236: /**
237: * Returns {@code true} if the position at the specified row is inside the CRS
238: * {@linkplain CoordinateReferenceSystem#getValidArea valid area}. This method
239: * is invoked by {@link CellRenderer} in order to determine if this row should
240: * be colorized.
241: */
242: public boolean isValidCoordinate(final int rowIndex) {
243: final DirectPosition position = (DirectPosition) positions
244: .get(rowIndex);
245: try {
246: toWGS84.transform(position);
247: } catch (TransformException e) {
248: /*
249: * If the coordinate can't be transformed, then there is good chances
250: * that the the coordinate is outside the CRS valid area.
251: */
252: return false;
253: }
254: return validArea.contains(toWGS84);
255: }
256:
257: /**
258: * Returns a string representation of this table. The default implementation
259: * list all coordinates.
260: */
261: public String toString() {
262: final StringBuffer buffer = new StringBuffer();
263: final String lineSeparator = System.getProperty(
264: "line.separator", "\n");
265: final int size = positions.size();
266: for (int i = 0; i < size; i++) {
267: buffer.append(positions.get(i));
268: buffer.append(lineSeparator);
269: }
270: return buffer.toString();
271: }
272:
273: /**
274: * A cell renderer for the {@linkplain CoordinateTableModel coordinate table model}.
275: * This cell renderer can display in a different color coordinates outside the CRS
276: * {@linkplain CoordinateReferenceSystem#getValidArea valid area}. Coordinate validity
277: * is determined by invoking {@link CoordinateTableModel#isValidCoordinate}.
278: *
279: * @since 2.3
280: * @version $Id: CoordinateTableModel.java 24925 2007-03-27 20:12:08Z jgarnett $
281: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/referencing/CoordinateTableModel.java $
282: * @author Cédric Briançon
283: * @author Martin Desruisseaux
284: */
285: public static class CellRenderer extends DefaultTableCellRenderer {
286: /**
287: * The default text and background color.
288: */
289: private Color foreground, background;
290:
291: /**
292: * The text and background color for invalid coordinates.
293: */
294: private Color invalidForeground = Color.RED, invalidBackground;
295:
296: /**
297: * Creates a default cell renderer for {@link CoordinateTableModel}.
298: */
299: public CellRenderer() {
300: super ();
301: foreground = super .getForeground();
302: background = super .getBackground();
303: }
304:
305: /**
306: * Specifies the text color for valid coordinates.
307: */
308: public void setForeground(final Color foreground) {
309: this .foreground = foreground;
310: super .setForeground(foreground);
311: }
312:
313: /**
314: * Specifies the background color for valid coordinates.
315: */
316: public void setBackground(final Color background) {
317: this .background = background;
318: super .setBackground(background);
319: }
320:
321: /**
322: * Specified the text and background colors for invalid coordinates,
323: * or {@code null} for the same color than valid coordinates.
324: */
325: public void setInvalidColor(final Color foreground,
326: final Color background) {
327: this .invalidForeground = foreground;
328: this .invalidBackground = background;
329: }
330:
331: /**
332: * Returns the component for cell rendering.
333: */
334: public Component getTableCellRendererComponent(
335: final JTable table, final Object value,
336: final boolean isSelected, final boolean hasFocus,
337: final int row, final int column) {
338: Color foreground = this .foreground;
339: Color background = this .background;
340: final TableModel candidate = table.getModel();
341: if (candidate instanceof CoordinateTableModel) {
342: final CoordinateTableModel model = (CoordinateTableModel) candidate;
343: if (!model.isValidCoordinate(row)) {
344: if (invalidForeground != null)
345: foreground = invalidForeground;
346: if (invalidBackground != null)
347: background = invalidBackground;
348: }
349: }
350: super.setBackground(background);
351: super.setForeground(foreground);
352: return super.getTableCellRendererComponent(table, value,
353: isSelected, hasFocus, row, column);
354: }
355: }
356: }
|