/* * 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.render2d.edition; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.beans.value.WeakChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.geometry.HPos; import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Accordion; import javafx.scene.control.Button; import javafx.scene.control.ContentDisplay; import javafx.scene.control.TitledPane; import javafx.scene.control.ToggleButton; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.RowConstraints; import org.controlsfx.control.action.ActionUtils; import org.geotoolkit.gui.javafx.action.CommitAction; import org.geotoolkit.gui.javafx.action.RollbackAction; import org.geotoolkit.gui.javafx.chooser.FXMapLayerComboBox; import org.geotoolkit.gui.javafx.render2d.FXCanvasHandler; import org.geotoolkit.gui.javafx.render2d.FXMap; import org.geotoolkit.internal.GeotkFX; import org.geotoolkit.map.FeatureMapLayer; import org.geotoolkit.map.MapBuilder; import org.geotoolkit.map.MapContext; import org.geotoolkit.map.MapLayer; import org.opengis.util.InternationalString; /** * * @author Johann Sorel (Geomatys) */ public class FXToolBox extends BorderPane { private final ObservableList<EditionTool.Spi> tools = FXCollections.observableArrayList(); private final IntegerProperty toolPerRow = new SimpleIntegerProperty(-1); private int dynamicNbCol = 1; private final GridPane grid = new GridPane(); private final Accordion accordion = new Accordion(); private final TitledPane helpPane = new TitledPane(GeotkFX.getString(FXToolBox.class, "help"), null); private final TitledPane paramsPane = new TitledPane(GeotkFX.getString(FXToolBox.class, "params"), null); private final HBox commitRollBackBar = new HBox(); private final FXMapLayerComboBox combo = new FXMapLayerComboBox(); private final FXMap map; private final CommitAction commitAction = new CommitAction(); private final RollbackAction rollbackAction = new RollbackAction(); private final ChangeListener<FXCanvasHandler> lst = new ChangeListener<FXCanvasHandler>() { @Override public void changed(ObservableValue<? extends FXCanvasHandler> observable, FXCanvasHandler oldValue, FXCanvasHandler newValue) { //listen to map handler change, unselect buttons for(Node n : grid.getChildren()){ final ToggleButton tb = (ToggleButton) n; final EditionTool editionTool = (EditionTool) tb.getUserData(); tb.setSelected( newValue == editionTool); } if(newValue instanceof EditionTool){ final EditionTool et = (EditionTool) newValue; helpPane.setContent(et.getHelpPane()); paramsPane.setContent(et.getConfigurationPane()); helpPane.setExpanded(helpPane.getContent()!=null); paramsPane.setExpanded(paramsPane.getContent()!=null); }else{ helpPane.setContent(null); paramsPane.setContent(null); helpPane.setExpanded(false); paramsPane.setExpanded(false); } } }; /** * Create tool box with all map layers. * * @param map source map widget */ public FXToolBox(final FXMap map) { this.map = map; init(map.getContainer().getContext()); } /** * Create toolbox for a single layer. * * @param map source map widget * @param layer edited layer */ public FXToolBox(final FXMap map, final MapLayer layer) { this.map = map; final MapContext context = MapBuilder.createContext(); context.layers().add(layer); init(context); //hide compbo box, we have only one layer. combo.setVisible(false); combo.setManaged(false); commitRollBackBar.managedProperty().bind(commitRollBackBar.visibleProperty()); map.getHandlerProperty().addListener(new WeakChangeListener(lst)); } /** * Control visibility of commit/rollback buttons. * @return commit rollback property */ public BooleanProperty commitRollbackVisibleProperty(){ return commitRollBackBar.visibleProperty(); } private void init(final MapContext context) { getStylesheets().add("/org/geotoolkit/gui/javafx/buttonbar.css"); //commit and rollback buttons final Button rollback = rollbackAction.createButton(ActionUtils.ActionTextBehavior.HIDE); final Button commit = commitAction.createButton(ActionUtils.ActionTextBehavior.HIDE); rollback.styleProperty().unbind(); rollback.setStyle("-fx-base : #FFAAAA;"); rollback.getStyleClass().add("buttongroup-left"); commit.styleProperty().unbind(); commit.setStyle("-fx-base : #AAFFAA;"); commit.getStyleClass().add("buttongroup-right"); commitRollBackBar.getChildren().addAll(rollback,commit); grid.setMaxWidth(Double.MAX_VALUE); tools.addListener((Change<? extends EditionTool.Spi> c) -> updateGrid()); toolPerRow.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> updateGrid()); final GridPane top = new GridPane(); top.getColumnConstraints().add(new ColumnConstraints(250, GridPane.USE_COMPUTED_SIZE, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true)); top.getRowConstraints().add(new RowConstraints(USE_PREF_SIZE, USE_COMPUTED_SIZE, USE_PREF_SIZE, Priority.NEVER, VPos.CENTER, false)); top.getRowConstraints().add(new RowConstraints(USE_PREF_SIZE, USE_COMPUTED_SIZE, USE_PREF_SIZE, Priority.NEVER, VPos.CENTER, false)); top.getRowConstraints().add(new RowConstraints(USE_PREF_SIZE, USE_COMPUTED_SIZE, Double.MAX_VALUE, Priority.ALWAYS, VPos.TOP, true)); top.add(combo, 0, 0); top.add(commitRollBackBar, 1, 0); top.add(grid, 0, 1, 2, 1); top.add(accordion, 0, 2, 2, 1); top.setVgap(10); top.setHgap(10); setCenter(top); accordion.getPanes().add(helpPane); accordion.getPanes().add(paramsPane); helpPane.disableProperty().bind(helpPane.contentProperty().isNull()); paramsPane.disableProperty().bind(paramsPane.contentProperty().isNull()); combo.setMapContext(context); combo.setPrefWidth(50); combo.setMinWidth(50); combo.setMaxWidth(Double.MAX_VALUE); combo.valueProperty().addListener((ObservableValue<? extends MapLayer> observable, MapLayer oldValue, MapLayer newValue) -> updateGrid()); commitAction.layerProperty().bind(GeotkFX.isInstance(combo.valueProperty(), FeatureMapLayer.class)); rollbackAction.layerProperty().bind(GeotkFX.isInstance(combo.valueProperty(), FeatureMapLayer.class)); //listen to grid size change final ToggleButton button = new ToggleButton(); button.getStyleClass().add("buttongroup-right"); button.setGraphic(new ImageView(GeotkFX.ICON_ADD)); button.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); button.setAlignment(Pos.CENTER); button.setMaxHeight(GridPane.USE_PREF_SIZE); button.setMaxWidth(GridPane.USE_PREF_SIZE); final Scene snapScene = new Scene(button); snapScene.snapshot(null); final double buttonSize = button.getWidth(); top.widthProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { dynamicNbCol = Math.max(1, (int)(newValue.intValue()/buttonSize) ); if(toolPerRow.get()==-1) updateGrid(); } }); } /** * Live list of tools displayed. * @return list of tools SPI */ public ObservableList<EditionTool.Spi> getTools() { return tools; } /** * Number of tools on each row. * @return number of tools per line property */ public IntegerProperty getToolPerRow() { return toolPerRow; } private void handle(ActionEvent event){ final ToggleButton button = (ToggleButton) event.getSource(); if(button.isSelected()){ final EditionTool tool = (EditionTool) button.getUserData(); this.map.setHandler(tool); } } private void updateGrid(){ grid.getChildren().clear(); grid.getColumnConstraints().clear(); final ColumnConstraints colFirst = new ColumnConstraints(1, 1, Double.MAX_VALUE,Priority.ALWAYS,HPos.CENTER,true); final ColumnConstraints colLast = new ColumnConstraints(1, 1, Double.MAX_VALUE,Priority.ALWAYS,HPos.CENTER,true); final MapLayer layer = combo.getValue(); int nbCol = toolPerRow.intValue(); if(nbCol==-1) nbCol = dynamicNbCol; final FilteredList<EditionTool.Spi> validTools = tools.filtered((EditionTool.Spi t) -> t.canHandle(layer)); final int nbRow = (int)Math.max(1, Math.ceil((float)validTools.size()/nbCol)); //create column constraints grid.getColumnConstraints().add(colFirst); for(int i=0;i<nbCol;i++){ grid.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE,Priority.NEVER,HPos.CENTER,true)); } grid.getColumnConstraints().add(colLast); //first column constraint, resizable int toolIdx = 0; for(int y=0;y<nbRow;y++){ for(int x=0;x<nbCol;x++,toolIdx++){ //get style String styleName = "buttongroup-"; if(nbRow==1){ styleName += ((x==0)?"left":(x==nbCol-1)?"right":"center"); }else{ styleName += ((y==0)?"top-":(y==nbRow-1)?"bottom-":"center-"); styleName += ((x==0)?"left":(x==nbCol-1)?"right":"center"); } final ToggleButton button = new ToggleButton(); button.getStyleClass().add(styleName); button.setMaxHeight(Double.MAX_VALUE); button.setMaxWidth(Double.MAX_VALUE); button.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); button.setAlignment(Pos.CENTER); if(toolIdx<validTools.size()){ final EditionTool tool = validTools.get(toolIdx).create(map,layer); button.setGraphic(new ImageView(tool.getSpi().getIcon())); final InternationalString title = tool.getSpi().getTitle(); if(title!=null){ button.setTooltip(new Tooltip(title.toString())); } button.setUserData(tool); button.setOnAction(this::handle); }else{ button.setGraphic(new ImageView(GeotkFX.ICON_EMPTY)); button.setDisable(true); } grid.add(button, x+1, y, 1, 1); } } } }