/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2015, 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.style;
import java.io.File;
import org.geotoolkit.gui.javafx.style.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTablePosition;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import javafx.stage.WindowEvent;
import javax.xml.bind.JAXBException;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.display2d.GO2Utilities;
import org.geotoolkit.gui.javafx.contexttree.TreeMenuItem;
import org.geotoolkit.gui.javafx.layer.FXLayerStylesPane;
import org.geotoolkit.gui.javafx.layer.FXPropertyPane;
import org.geotoolkit.gui.javafx.util.FXUtilities;
import org.geotoolkit.internal.GeotkFX;
import org.geotoolkit.map.MapLayer;
import org.geotoolkit.sld.MutableLayer;
import org.geotoolkit.sld.MutableStyledLayerDescriptor;
import org.geotoolkit.sld.xml.Specification;
import org.geotoolkit.sld.xml.StyleXmlIO;
import org.geotoolkit.style.MutableFeatureTypeStyle;
import org.geotoolkit.style.MutableStyle;
import org.opengis.sld.LayerStyle;
import org.opengis.sld.NamedLayer;
import org.opengis.sld.UserLayer;
import org.opengis.style.Style;
import org.opengis.style.Symbolizer;
import org.opengis.util.FactoryException;
/**
* TODO : find a better name.
*
* This panel regroups all other panels in a more friendly design.
* But only display a main tree view.
*
* @author Johann Sorel (Geomatys)
*/
public class FXStyleAggregatedPane extends FXPropertyPane{
private static final MenuItem DUMMY = new CustomMenuItem();
static {
DUMMY.setVisible(false);
}
@FXML protected TreeTableView tree;
@FXML protected BorderPane contentPane;
protected final ObservableList<Object> menuItems = FXCollections.observableArrayList();;
//current style element editor
private TreeItem editorPath;
private FXStyleElementController editor = null;
private MapLayer layer;
public FXStyleAggregatedPane() {
GeotkFX.loadJRXML(this,FXStyleAggregatedPane.class);
}
@Override
public String getTitle() {
return GeotkFX.getString(FXLayerStylesPane.class,"style");
}
@Override
public boolean init(Object candidate) {
if(!(candidate instanceof MapLayer)) return false;
this.layer = (MapLayer) candidate;
initTree();
updateEditor(this.layer.getStyle());
return true;
}
public void initialize() {
menuItems.add(new FXStyleTree.ShowStylePaneAction(new FXStyleClassifRangePane(),GeotkFX.getString(FXStyleClassifRangePane.class,"title")));
menuItems.add(new FXStyleTree.ShowStylePaneAction(new FXStyleClassifSinglePane(),GeotkFX.getString(FXStyleClassifSinglePane.class,"title")));
menuItems.add(new SeparatorMenuItem());
menuItems.add(new FXStyleTree.NewFTSAction());
menuItems.add(new FXStyleTree.NewRuleAction());
final List<FXStyleElementController> editors = FXStyleElementEditor.findEditorsForType(Symbolizer.class);
for(FXStyleElementController editor : editors){
menuItems.add(new FXStyleTree.NewSymbolizerAction(editor));
}
menuItems.add(new SeparatorMenuItem());
menuItems.add(new FXStyleTree.DuplicateAction());
menuItems.add(new FXStyleTree.DeleteAction());
FXUtilities.hideTableHeader(tree);
}
@FXML
void addGroup(ActionEvent event) {
if(layer==null) return;
final MutableStyle style = layer.getStyle();
final MutableFeatureTypeStyle fts = GO2Utilities.STYLE_FACTORY.featureTypeStyle();
style.featureTypeStyles().add(fts);
}
@FXML
void importStyle(ActionEvent event) {
if(layer==null) return;
final File result = new FileChooser().showOpenDialog(null);
if(result==null) return;
final StyleXmlIO tool = new StyleXmlIO();
try {
final MutableStyledLayerDescriptor sld = tool.readSLD(result, Specification.StyledLayerDescriptor.V_1_1_0);
if(sld != null ){
for(MutableLayer sldLayer : sld.layers()){
if(sldLayer instanceof NamedLayer){
final NamedLayer nl = (NamedLayer) sldLayer;
for(LayerStyle ls : nl.styles()){
if(ls instanceof MutableStyle){
layer.setStyle((MutableStyle) ls);
}
}
}else if(sldLayer instanceof UserLayer){
final UserLayer ul = (UserLayer) sldLayer;
for(Style ls : ul.styles()){
if(ls instanceof MutableStyle){
layer.setStyle((MutableStyle) ls);
}
}
}
}
}
updateEditor(layer.getStyle());
return;
} catch (JAXBException | FactoryException ex) {
Logging.getLogger("org.geotoolkit.gui.javafx.layer.style").log(Level.FINEST,ex.getMessage(),ex);
}
try {
final MutableStyle style = tool.readStyle(result, Specification.SymbologyEncoding.V_1_1_0);
layer.setStyle(style);
updateEditor(layer.getStyle());
} catch (JAXBException | FactoryException ex) {
Logging.getLogger("org.geotoolkit.gui.javafx.layer.style").log(Level.FINEST,ex.getMessage(),ex);
}
}
@FXML
void exportStyle(ActionEvent event) {
if(layer==null) return;
final MutableStyle style = layer.getStyle();
final File result = new FileChooser().showSaveDialog(null);
if(result==null) return;
final StyleXmlIO tool = new StyleXmlIO();
try {
tool.writeStyle(result, style, Specification.StyledLayerDescriptor.V_1_1_0);
} catch (JAXBException ex) {
Logging.getLogger("org.geotoolkit.gui.javafx.layer.style").log(Level.WARNING,ex.getMessage(),ex);
}
}
private void initTree(){
tree.setPlaceholder(new Label(""));
tree.setShowRoot(false);
final TreeTableColumn col = new FXStyleTree.NameColumn();
//this will cause the column width to fit the view area
tree.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
tree.getColumns().clear();
final ContextMenu menu = new ContextMenu();
tree.setContextMenu(menu);
tree.getColumns().add(col);
tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
//dummy item to ensure showing will be called
menu.getItems().add(DUMMY);
menu.setOnShowing(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
//update menu items
final ObservableList items = menu.getItems();
items.clear();
items.add(DUMMY);
final List<TreeItem> selection = new ArrayList<>();
for(Object i : tree.getSelectionModel().getSelectedCells()){
final TreeTablePosition ttp = (TreeTablePosition) i;
final TreeItem ti = tree.getTreeItem(ttp.getRow());
if(ti!=null && !selection.contains(ti)) selection.add(ti);
}
for(int i=0,n=menuItems.size();i<n;i++){
final Object candidate = menuItems.get(i);
if(candidate instanceof FXStyleTree.ShowStylePaneAction){
((FXStyleTree.ShowStylePaneAction)candidate).setMapLayer(layer);
}
if(candidate instanceof TreeMenuItem){
final MenuItem mc = ((TreeMenuItem)candidate).init(selection);
if(mc!=null) items.add(mc);
}else if(candidate instanceof SeparatorMenuItem){
//special case, we don't want any separator at the start or end
//or 2 succesive separators
if(i==0 || i==n-1 || items.isEmpty()) continue;
if(items.get(items.size()-1) instanceof SeparatorMenuItem){
continue;
}
items.add((SeparatorMenuItem)candidate);
}else if(candidate instanceof MenuItem){
items.add((MenuItem)candidate);
}
}
//special case, we don't want any separator at the start or end
if(!items.isEmpty()){
if(items.get(0) instanceof SeparatorMenuItem){
items.remove(0);
}
if(!items.isEmpty()){
final int idx = items.size()-1;
if(items.get(idx) instanceof SeparatorMenuItem){
items.remove(idx);
}
}
}
}
});
tree.getSelectionModel().getSelectedItems().addListener(new ListChangeListener() {
@Override
public void onChanged(ListChangeListener.Change c) {
final TreeItem treeItem = (TreeItem) tree.getSelectionModel().getSelectedItem();
// //we validate the previous edition pane
// if(!applying){
// //we keep the same editor if we are currently applying changes
//force request focus, this will remove the focus from the previous
//panel, validating any last changes if any.
tree.requestFocus();
contentPane.setCenter(null);
if(treeItem!=null){
final Object val = treeItem.getValue();
editorPath = treeItem;
editor = FXStyleElementEditor.findEditor(val);
if(editor != null){
editor.setLayer(layer);
editor.valueProperty().setValue(val);
//listen to editor change
editor.valueProperty().addListener(new ChangeListener() {
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
FXStyleTree.applyTreeItemEditor(editor,editorPath);
}
});
contentPane.setCenter(editor);
}
}
// }
}
});
}
protected void updateEditor(MutableStyle styleElement) {
tree.setRoot(new FXStyleTree.StyleTreeItem(styleElement));
FXUtilities.expandAll(tree.getRoot());
}
}