/*
* Copyright (C) 2013 Dr. John Lindsay <jlindsay@uoguelph.ca>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package whiteboxgis.user_interfaces;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.table.AbstractTableModel;
import whitebox.geospatialfiles.shapefile.attributes.AttributeTable;
import whitebox.geospatialfiles.shapefile.attributes.DBFException;
import whitebox.geospatialfiles.shapefile.attributes.DBFField;
import whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType;
/**
* AttributeFieldTableModel. A model for displaying and editing DBFField data
* for an attribute file.
*
* @author Kevin Green
*/
public class AttributeFieldTableModel extends AbstractTableModel {
private AttributeTable attributeTable;
private static final String REMOVED_STRING = "Deleted";
private static final String ADDED_STRING = "New";
private static final String NOT_MODIFIED_STRING = "";
// To keep track of changes to existing fields
private HashMap<Integer, DBFField> newFields = new HashMap<>();
// To keep track of deleted fields
private HashMap<Integer, DBFField> deletedFields = new HashMap<>();
protected enum ColumnName {
NAME("Name", String.class),
TYPE("Type", DBFDataType.class),
LENGTH("Length", Integer.class),
PRECISION("Precision", Integer.class),
MODIFIED("Modified", String.class);
public static final int size = ColumnName.values().length;
private static final ColumnName[] values = ColumnName.values();
private final String displayName;
private final Class<?> columnClass;
ColumnName(String displayName, Class<?> columnClass) {
this.displayName = displayName;
this.columnClass = columnClass;
}
public static ColumnName fromColumnIndex(int columnIndex) {
if (columnIndex < ColumnName.values().length) {
return values[columnIndex];
}
return null;
}
public static int getColumnNameIndex(ColumnName name) {
for (int a = 0; a < values.length; a++) {
if (values[a].displayName.equals(name.displayName)) {
return a;
}
}
return -1;
}
public Class<?> getColumnClass() {
return this.columnClass;
}
@Override
public String toString() {
return this.displayName;
}
}
public AttributeFieldTableModel(AttributeTable attributeTable) {
this.attributeTable = attributeTable;
}
@Override
public int getRowCount() {
return attributeTable.getFieldCount() + newFields.size();
}
@Override
public int getColumnCount() {
return ColumnName.values().length;
}
@Override
public String getColumnName(int column) {
ColumnName columnName = ColumnName.fromColumnIndex(column);
if (columnName != null) {
return columnName.toString();
}
return null;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// Modified column isn't editable
if (ColumnName.fromColumnIndex(columnIndex) == ColumnName.MODIFIED) {
return false;
}
// Only allow new fields to be edited until edit functionality is added
DBFField row = newFields.get(rowIndex);
if (row != null) {
if (row.getDataType() == DBFDataType.DATE) {
if (ColumnName.fromColumnIndex(columnIndex) != ColumnName.LENGTH
&& ColumnName.fromColumnIndex(columnIndex) != ColumnName.PRECISION) {
return true;
}
} else {
return true;
}
}
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
DBFField field = null;
if (rowIndex >= attributeTable.getFieldCount() || columnIndex >= ColumnName.size) {
// If it exists in newFields, it will be returned. Null will be returned otherwise.
field = newFields.get(rowIndex);
if (field == null) {
return null;
}
switch (ColumnName.fromColumnIndex(columnIndex)) {
case MODIFIED:
return ADDED_STRING;
}
}
if (field == null) {
field = attributeTable.getField(rowIndex);
if (field == null) {
return null;
}
}
switch (ColumnName.fromColumnIndex(columnIndex)) {
case MODIFIED:
if (deletedFields.containsKey(rowIndex)) {
return REMOVED_STRING;
} else {
return NOT_MODIFIED_STRING;
}
case NAME:
return field.getName();
case TYPE:
return field.getDataType();
case LENGTH:
return field.getFieldLength();
case PRECISION:
return field.getDecimalCount();
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
ColumnName column = ColumnName.fromColumnIndex(columnIndex);
if (column != null) {
return column.getColumnClass();
} else {
return super.getColumnClass(columnIndex);
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
DBFField row;
if (newFields.containsKey(rowIndex)) {
row = newFields.get(rowIndex);
} else {
row = attributeTable.getField(rowIndex);
}
ColumnName column = ColumnName.fromColumnIndex(columnIndex);
if (column != null) {
switch (column) {
case NAME:
row.setName((String) aValue);
break;
case TYPE:
row.setDataType((DBFDataType) aValue);
break;
case LENGTH:
row.setFieldLength((Integer) aValue);
break;
case PRECISION:
row.setDecimalCount((Integer) aValue);
break;
}
}
this.fireTableRowsUpdated(rowIndex, rowIndex);
}
/**
* Adds a new generic field to the model. The field only exists in the model
* and is not added to the DBF file until saved with @see commitChanges()
*/
public void createNewField() {
createNewField(new DBFField());
}
/**
* Adds new field to the model. The field only exists in the model and is
* not added to the DBF field until saved with @see commitChanges()
*
* @param field
*/
public void createNewField(DBFField field) {
if (field != null) {
int index = getRowCount();
newFields.put(index, field);
fireTableRowsInserted(index, index);
}
}
/**
* Hides the field (row of model) specified by fieldIndex and marks the
* field for deletion on next time changes are saved
*
* @param fieldIndex
*/
public void deleteField(int fieldIndex) {
if (fieldIndex < 0 || fieldIndex >= getRowCount()) {
return;
}
if (newFields.containsKey(fieldIndex)) {
// Delete a field that doesn't exist in the data yet
newFields.remove(fieldIndex);
} else {
// Hide the field and mark for deletion
DBFField deletedField = attributeTable.getField(fieldIndex);
deletedFields.put(fieldIndex, deletedField);
}
fireTableRowsUpdated(fieldIndex, fieldIndex);
}
/**
* Reverts all changes to the given row.
*
* @param row
*/
public void revertRow(int row) {
newFields.remove(row);
deletedFields.remove(row);
fireTableRowsUpdated(row, row);
}
public void changeFieldTitle(int row, String newTitle) {
setValueAt(newTitle, row, ColumnName.getColumnNameIndex(ColumnName.NAME));
attributeTable.updateFieldName(row, newTitle);
fireTableRowsUpdated(row, row);
}
/**
* Saves changes to disk. Adds new fields and makes modifications to
* existing fields
*/
public boolean saveChanges() {
// Add fields in lowest to heighest index to keep correct order in file
List<Map.Entry<Integer, DBFField>> newEntries = new ArrayList(newFields.entrySet());
Collections.sort(newEntries, new Comparator<Map.Entry<Integer, DBFField>>() {
@Override
public int compare(Entry<Integer, DBFField> o1, Entry<Integer, DBFField> o2) {
// Sort in descending order
return Integer.compare(o1.getKey(), o2.getKey());
}
});
for (Map.Entry<Integer, DBFField> entry : newEntries) {
try {
attributeTable.addField(entry.getValue());
newFields.remove(entry.getKey());
} catch (DBFException e) {
System.out.println(e);
}
}
// Delete from greatest index to lowest to prevent changing indexes
List<Map.Entry<Integer, DBFField>> deletedEntries = new ArrayList(deletedFields.entrySet());
Collections.sort(deletedEntries, new Comparator<Map.Entry<Integer, DBFField>>() {
@Override
public int compare(Entry<Integer, DBFField> o1, Entry<Integer, DBFField> o2) {
// Sort in descending order
return Integer.compare(o2.getKey(), o1.getKey());
}
});
for (Map.Entry<Integer, DBFField> entry : deletedEntries) {
try {
//System.out.println("Deleting field from AttributeTable: " + entry.getKey() + " " + entry.getValue());
attributeTable.deleteField(entry.getKey());
deletedFields.remove(entry.getKey());
} catch (DBFException e) {
//System.out.println(e);
}
}
if (!newFields.isEmpty() || !deletedFields.isEmpty()) {
// Some changes weren't saved
return false;
}
return true;
}
/**
* Returns false if there are unsaved changes
*
* @return True if all changes are saved
*/
public boolean isSaved() {
if (newFields.isEmpty() && deletedFields.isEmpty()) {
return true;
} else {
return false;
}
}
/**
* Returns true if the row for the given rowIndex is modified.
*
* @param row
* @return True if the row is modified.
*/
public boolean isModified(int rowIndex) {
if (newFields.containsKey(rowIndex)) {
return true;
} else if (deletedFields.containsKey(rowIndex)) {
return true;
}
return false;
}
}