/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2014-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 com.sun.javafx.collections.NonIterableChange; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ModifiableObservableListBase; import javafx.embed.swing.SwingFXUtils; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.ContentDisplay; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.ScrollPane; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TablePosition; import javafx.scene.control.TableView; import javafx.scene.image.ImageView; import javafx.scene.text.TextAlignment; import org.geotoolkit.display2d.ext.graduation.GraduationSymbolizer; import org.geotoolkit.display2d.service.DefaultGlyphService; import org.geotoolkit.gui.javafx.layer.FXLayerStylePane; import org.geotoolkit.gui.javafx.style.FXStyleElementController; import org.geotoolkit.gui.javafx.style.FXStyleElementEditor; import org.geotoolkit.gui.javafx.util.FXDeleteTableColumn; import org.geotoolkit.gui.javafx.util.FXMoveDownTableColumn; import org.geotoolkit.gui.javafx.util.FXMoveUpTableColumn; import org.geotoolkit.gui.javafx.util.FXUtilities; import org.geotoolkit.internal.GeotkFX; import org.geotoolkit.map.LayerListener; import org.geotoolkit.map.MapItem; import org.geotoolkit.map.MapLayer; import org.geotoolkit.style.MutableFeatureTypeStyle; import org.geotoolkit.style.MutableRule; import org.geotoolkit.style.MutableStyle; import org.geotoolkit.style.RuleListener; import org.geotoolkit.util.collection.CollectionChangeEvent; import org.opengis.style.FeatureTypeStyle; import org.opengis.style.LineSymbolizer; import org.opengis.style.PointSymbolizer; import org.opengis.style.PolygonSymbolizer; import org.opengis.style.RasterSymbolizer; import org.opengis.style.Rule; import org.opengis.style.Symbolizer; import org.opengis.style.TextSymbolizer; /** * * @author Johann Sorel (Geomatys) */ public class FXStyleSimplePane extends FXLayerStylePane implements LayerListener { private MapLayer layer; //keep track of where the rule was to avoid rewriting the complete style private MutableRule rule; @FXML private ComboBox<FXStyleElementController> uiChoice; @FXML private TableView<Symbolizer> uiTable; @FXML private ImageView uiPreview; @FXML private ScrollPane uiSymbolizerEditorPane; private final LayerListener.Weak layerListener = new LayerListener.Weak(this); private FXStyleElementController symbolizerEditor = null; private boolean editing = false; public FXStyleSimplePane() { GeotkFX.loadJRXML(this,FXStyleSimplePane.class); } @Override public String getTitle() { return GeotkFX.getString(FXStyleSimplePane.class, "title"); } @Override public String getCategory() { return GeotkFX.getString(FXStyleSimplePane.class, "category"); } @FXML void addSymbol(ActionEvent event) { final FXStyleElementController styleController = uiChoice.getSelectionModel().getSelectedItem(); final Symbolizer symbolizer = (Symbolizer) styleController.newValue(); uiTable.getItems().add(symbolizer); } /** * Called by FXMLLoader after creating controller. */ public void initialize(){ FXUtilities.hideTableHeader(uiTable); final List<FXStyleElementController> editors = FXStyleElementEditor.findEditorsForType(Symbolizer.class); uiChoice.setItems(FXCollections.observableArrayList(editors)); uiChoice.setButtonCell(new SymbolizerButtonListCell()); uiChoice.setCellFactory((ListView<FXStyleElementController> param) -> new SymbolizerButtonListCell()); if(!editors.isEmpty()){ uiChoice.getSelectionModel().select(0); } final TableColumn<Symbolizer,Symbolizer> previewCol = new TableColumn<>(); previewCol.setMinWidth(40); previewCol.setEditable(false); previewCol.setCellValueFactory((TableColumn.CellDataFeatures<Symbolizer, Symbolizer> param) -> new SimpleObjectProperty<>((Symbolizer)param.getValue())); previewCol.setCellFactory((TableColumn<Symbolizer, Symbolizer> p) -> new GlyphTableCell()); uiTable.getColumns().add(previewCol); uiTable.getColumns().add(new FXMoveUpTableColumn()); uiTable.getColumns().add(new FXMoveDownTableColumn()); uiTable.getColumns().add(new FXDeleteTableColumn(false)); uiTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); uiTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); uiTable.setTableMenuButtonVisible(false); uiTable.getSelectionModel().getSelectedCells().addListener(new ListChangeListener<TablePosition>() { @Override public void onChanged(ListChangeListener.Change<? extends TablePosition> c) { if(editing){ updatePreview(); return; } uiSymbolizerEditorPane.setContent(null); for(final TablePosition tablePosition : uiTable.getSelectionModel().getSelectedCells()){ final Symbolizer symbolizer = uiTable.getItems().get(tablePosition.getRow()); System.out.println(symbolizer); symbolizerEditor = FXStyleElementEditor.findEditor(symbolizer); if(symbolizerEditor != null){ symbolizerEditor.setLayer(layer); symbolizerEditor.valueProperty().setValue(symbolizer); //listen to editor change symbolizerEditor.valueProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { //apply editor editing = true; final int index = uiTable.getSelectionModel().getSelectedIndex(); if(index>=0){ uiTable.getItems().set(index, (Symbolizer) symbolizerEditor.valueProperty().get()); uiTable.getSelectionModel().select(index); } editing = false; } }); uiSymbolizerEditorPane.setContent(symbolizerEditor); } } updatePreview(); } }); } private void updatePreview(){ final Dimension dim = new Dimension(120, 120); final BufferedImage imge = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); DefaultGlyphService.render(rule, new Rectangle(dim), imge.createGraphics(), null); uiPreview.setImage(SwingFXUtils.toFXImage(imge, null)); } @Override public boolean init(MapLayer candidate, Object StyleElement) { if(!(candidate instanceof MapLayer)) return false; if(this.layer!=null){ layerListener.unregisterSource(this.layer); } this.layer = (MapLayer) candidate; rule = null; loop: for(final FeatureTypeStyle typeStyle : layer.getStyle().featureTypeStyles()){ for(final Rule rule : typeStyle.rules()){ parse((MutableRule) rule); break loop; //we only retrieve the first rule. } } if(this.layer!=null){ layerListener.registerSource(this.layer); } return true; } public void styleChange(MapLayer source, EventObject event) { //check if the first rule changed final MutableStyle ms = this.layer.getStyle(); if(ms.featureTypeStyles().isEmpty()){ init(this.layer,ms); return; } final MutableFeatureTypeStyle fts = ms.featureTypeStyles().get(0); if(fts.rules().isEmpty()){ init(this.layer,ms); return; } final MutableRule r = fts.rules().get(0); if(this.rule!=r){ init(this.layer,ms); return; } } private void parse(final MutableRule rule) { //listen to rule change from other style editors if(this.rule!=rule){ this.rule = rule; uiTable.setItems(new SymbolizersList(rule)); updatePreview(); } } @Override public MutableStyle getMutableStyle() { return layer.getStyle(); } @Override public void itemChange(CollectionChangeEvent<MapItem> event) { } @Override public void propertyChange(PropertyChangeEvent evt) { if(MapLayer.STYLE_PROPERTY.equals(evt.getPropertyName()) && evt.getOldValue()!=evt.getNewValue()){ init(this.layer,this.layer.getStyle()); } } private static class GlyphTableCell extends TableCell<Symbolizer, Symbolizer>{ @Override protected void updateItem(Symbolizer item, boolean empty) { super.updateItem(item, empty); if(item instanceof Symbolizer){ final BufferedImage img = DefaultGlyphService.create(item, new Dimension(24, 24), null); setGraphic(new ImageView(SwingFXUtils.toFXImage(img,null))); String name = item.getName(); if(name==null || name.trim().isEmpty()) name = " - "; setText(name); }else{ setGraphic(null); setText(""); } } } private static class SymbolizersList extends ModifiableObservableListBase<Symbolizer> implements RuleListener{ private final MutableRule rule; public SymbolizersList(MutableRule rule) { this.rule = rule; rule.addListener(this); } @Override public Symbolizer get(int index) { return rule.symbolizers().get(index); } @Override public int size() { return rule.symbolizers().size(); } @Override protected void doAdd(int index, Symbolizer element) { rule.symbolizers().add(index, element); } @Override protected Symbolizer doSet(int index, Symbolizer element) { return rule.symbolizers().set(index, element); } @Override protected Symbolizer doRemove(int index) { return rule.symbolizers().remove(index); } @Override public void symbolizerChange(CollectionChangeEvent<Symbolizer> event) { final int type = event.getType(); final int min = (int) event.getRange().getMinDouble(); final int max = (int) event.getRange().getMaxDouble(); if(type==CollectionChangeEvent.ITEM_ADDED){ fireChange(new NonIterableChange.SimpleAddChange<>(min,max,this)); }else if(type==CollectionChangeEvent.ITEM_REMOVED){ fireChange(new NonIterableChange.GenericAddRemoveChange<>(min,max,new ArrayList(event.getItems()),this)); }else if(type==CollectionChangeEvent.ITEM_CHANGED){ fireChange(new NonIterableChange.SimpleUpdateChange<>(min,max,this)); } } @Override public void propertyChange(PropertyChangeEvent evt) { } } private static class SymbolizerButtonListCell extends ListCell<FXStyleElementController>{ @Override protected void updateItem(FXStyleElementController item, boolean empty) { super.updateItem(item, empty); if (item == null || empty) { setGraphic(null); setText(""); } else { final Symbolizer symbolizer = (Symbolizer) item.newValue(); final Dimension dim = DefaultGlyphService.glyphPreferredSize(symbolizer, null, null); final BufferedImage imge = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); DefaultGlyphService.render(symbolizer, new Rectangle(dim),imge.createGraphics(),null); setGraphic(new ImageView(SwingFXUtils.toFXImage(imge, null))); if(symbolizer instanceof PointSymbolizer){ setText("Point"); }else if(symbolizer instanceof LineSymbolizer){ setText("Line"); }else if(symbolizer instanceof PolygonSymbolizer){ setText("Polygon"); }else if(symbolizer instanceof TextSymbolizer){ setText("Text"); }else if(symbolizer instanceof RasterSymbolizer){ setText("Raster"); }else if(symbolizer instanceof GraduationSymbolizer){ setText("Graduation"); }else{ setText(symbolizer.getClass().getSimpleName()); } setContentDisplay(ContentDisplay.LEFT); setTextAlignment(TextAlignment.CENTER); } } } }