/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014, Geomatys
*
* 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.geotoolkit.gui.javafx.layer;
import com.vividsolutions.jts.geom.Geometry;
import java.util.AbstractSequentialList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import javafx.beans.InvalidationListener;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.Callback;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.display2d.GO2Utilities;
import org.opengis.feature.Feature;
import org.geotoolkit.internal.GeotkFX;
import org.geotoolkit.internal.Loggers;
import org.geotoolkit.map.FeatureMapLayer;
import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyType;
import org.opengis.filter.Id;
import org.opengis.filter.identity.Identifier;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class FXFeatureTable extends FXPropertyPane{
protected final TableView<Feature> table = new TableView<>();
private boolean loadAll = false;
protected FeatureMapLayer layer;
// Bundle management
/**
* bundles contains ResourceBundles indexed by table names.
*/
private final Map<String, ResourceBundle> bundles = new HashMap<>();
private String bundlePrefix;
public FXFeatureTable() {
final ScrollPane scroll = new ScrollPane(table);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
scroll.setPrefSize(600, 400);
scroll.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setCenter(scroll);
//listen to table selection
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<Feature>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Feature> c) {
final Iterator<Feature> ite = table.getSelectionModel().getSelectedItems().iterator();
final Set<Identifier> set = new HashSet<>();
while(ite.hasNext()){
set.add(FeatureExt.getId(ite.next()));
}
final Id selection = GO2Utilities.FILTER_FACTORY.id(set);
if(layer!=null){
layer.setSelectionFilter(selection);
}
}
});
final Button loadButton = new Button(GeotkFX.getString(FXFeatureTable.class, "loadData"));
loadButton.setOnAction(this::loadData);
table.setPlaceholder(loadButton);
}
public FXFeatureTable(final Map<String, String> bundleMapping){
this();
for(final String key : bundleMapping.keySet()){
final String bundleBaseName = bundleMapping.get(key);
try{
final ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName);
bundles.put(key, bundle);
}
catch(Exception ex){
Loggers.JAVAFX.log(Level.INFO, ex.getMessage(),ex);
}
}
}
public FXFeatureTable(final String bundleBaseNamePrefix){
this();
bundlePrefix = bundleBaseNamePrefix;
}
@Override
public String getTitle() {
return "Feature table";
}
public String getCategory(){
return "";
}
public boolean isLoadAll() {
return loadAll;
}
public void setLoadAll(boolean loadAll) {
this.loadAll = loadAll;
}
public void setEditable(boolean editable){
table.setEditable(editable);
}
public boolean isEditable(){
return table.isEditable();
}
public boolean init(Object candidate){
if(!(candidate instanceof FeatureMapLayer)) return false;
layer = (FeatureMapLayer) candidate;
final FeatureCollection features = layer.getCollection();
table.getColumns().clear();
final FeatureType featureType = features.getFeatureType();
for(PropertyType prop : featureType.getProperties(true)){
final TableColumn<Feature,String> column = new TableColumn<Feature,String>(generateFinalColumnName(prop));
column.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Feature, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Feature, String> param) {
final Object val = param.getValue().getPropertyValue(prop.getName().toString());
if(val instanceof Geometry){
return new SimpleObjectProperty<>("{geometry}");
}else{
return new SimpleObjectProperty<>(String.valueOf(val));
}
}
});
column.setCellFactory(TextFieldTableCell.forTableColumn());
table.getColumns().add(column);
}
if(loadAll){
table.setItems(FXCollections.observableArrayList((Feature[])features.toArray(new Feature[0])));
}
//TODO make a caching version, this is too slow for today use.
//final ObservableFeatureCollection obsCol = new ObservableFeatureCollection((FeatureCollection<Feature>) col);
//table.setItems(obsCol);
return true;
}
private String generateFinalColumnName(final PropertyType prop) {
Map<String, Entry<String, String>> labelInfo;
try {
labelInfo = (Map) prop.getDesignation();
} catch (Exception ex) {
Loggers.JAVAFX.log(Level.INFO, ex.getMessage(), ex);
labelInfo = null;
}
final String labelName = prop.getName().toString();
String columnName = labelName;
String tableName = null;
// If exists, explore labelInfo to retrive table and column respect to this label.
if (labelInfo != null) {
final Entry<String, String> entry = labelInfo.get(labelName);
if (entry != null) {
if (entry.getKey() != null) {
tableName = entry.getKey();
} else {
tableName = null;
}
if (entry.getValue() != null) {
columnName = entry.getValue();
} else {
columnName = labelName;
}
}
}
//If table name is not null, try to found resourcebundle for this table.
if (tableName != null) {
// If there isn't resource bundles (or not for the curruen table), try to generate.
if (bundles.get(tableName) == null) {
if (bundlePrefix != null) {
bundles.put(tableName, ResourceBundle.getBundle(bundlePrefix + tableName));
}
}
}
final ResourceBundle bundle = bundles.get(tableName);
String finalColumnName;
if (labelName == null) {
finalColumnName = "";
} else if (bundle == null) {
if (!labelName.equals(columnName)) {
finalColumnName = columnName + " as " + labelName;
} else {
finalColumnName = columnName;
}
} else {
try {
if (!labelName.equals(columnName)) {
finalColumnName = bundle.getString(columnName) + " as " + labelName;
} else {
finalColumnName = bundle.getString(columnName);
}
} catch (MissingResourceException ex) {
if (!labelName.equals(columnName)) {
finalColumnName = columnName + " as " + labelName;
} else {
finalColumnName = columnName;
}
}
}
return finalColumnName;
}
private void loadData(ActionEvent event){
final FeatureCollection col = layer.getCollection();
table.setItems(FXCollections.observableArrayList((Feature[])col.toArray(new Feature[0])));
}
private static class ObservableFeatureCollection extends AbstractSequentialList<Feature> implements ObservableList<Feature>{
private final FeatureCollection features;
private ObservableFeatureCollection(FeatureCollection features){
this.features = features;
}
@Override
public ListIterator<Feature> listIterator(int index) {
final QueryBuilder qb = new QueryBuilder(features.getFeatureType().getName().toString());
qb.setStartIndex(index);
final FeatureCollection subcol;
try {
subcol = features.subCollection(qb.buildQuery());
} catch (DataStoreException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(),ex);
return Collections.EMPTY_LIST.listIterator();
}
final Iterator ite = subcol.iterator();
final ListIterator lite = new ListIterator() {
@Override
public boolean hasNext() {
return ite.hasNext();
}
@Override
public Object next() {
return ite.next();
}
@Override
public boolean hasPrevious() {
return index>0;
}
@Override
public Object previous() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int nextIndex() {
return index+1;
}
@Override
public int previousIndex() {
return index-1;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void set(Object e) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void add(Object e) {
throw new UnsupportedOperationException("Not supported yet.");
}
};
return lite;
}
@Override
public int size() {
return features.size();
}
@Override
public void addListener(ListChangeListener<? super Feature> listener) {
}
@Override
public void removeListener(ListChangeListener<? super Feature> listener) {
}
@Override
public void addListener(InvalidationListener listener) {
}
@Override
public void removeListener(InvalidationListener listener) {
}
@Override
public boolean addAll(Feature... elements) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean setAll(Feature... elements) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean setAll(Collection<? extends Feature> col) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean removeAll(Feature... elements) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean retainAll(Feature... elements) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void remove(int from, int to) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
}