/* * Copyright (C) 2012 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 org.esa.snap.rcp.layermanager.editors; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.ValueSet; import com.bc.ceres.glayer.Layer; import com.bc.ceres.swing.binding.BindingContext; import com.bc.ceres.swing.figure.Figure; import com.bc.ceres.swing.figure.FigureEditor; import com.bc.ceres.swing.figure.FigureStyle; import com.bc.ceres.swing.figure.support.DefaultFigureStyle; import com.bc.ceres.swing.figure.support.NamedSymbol; import com.bc.ceres.swing.selection.AbstractSelectionChangeListener; import com.bc.ceres.swing.selection.SelectionChangeEvent; import org.esa.snap.core.datamodel.Placemark; import org.esa.snap.core.datamodel.VectorDataNode; import org.esa.snap.core.util.Debug; import org.esa.snap.core.util.ObjectUtils; import org.esa.snap.ui.layer.AbstractLayerConfigurationEditor; import org.esa.snap.ui.layer.LayerEditor; import org.esa.snap.ui.product.ProductSceneView; import org.esa.snap.ui.product.SimpleFeatureFigure; import org.esa.snap.ui.product.VectorDataFigureEditor; import org.esa.snap.ui.product.VectorDataLayer; import org.openide.util.Utilities; import java.awt.Color; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.concurrent.atomic.AtomicBoolean; /** * Customizable {@link LayerEditor} for {@link VectorDataNode}s. * * @author Marco Peters * @author Norman Fomferra */ public class VectorDataLayerEditor extends AbstractLayerConfigurationEditor { private static final String FILL_COLOR_NAME = DefaultFigureStyle.FILL_COLOR.getName(); private static final String FILL_OPACITY_NAME = DefaultFigureStyle.FILL_OPACITY.getName(); private static final String STROKE_COLOR_NAME = DefaultFigureStyle.STROKE_COLOR.getName(); private static final String STROKE_OPACITY_NAME = DefaultFigureStyle.STROKE_OPACITY.getName(); private static final String STROKE_WIDTH_NAME = DefaultFigureStyle.STROKE_WIDTH.getName(); private static final String SYMBOL_NAME_NAME = DefaultFigureStyle.SYMBOL_NAME.getName(); private static final SimpleFeatureFigure[] NO_SIMPLE_FEATURE_FIGURES = new SimpleFeatureFigure[0]; private static final ValueSet SYMBOL_VALUE_SET = new ValueSet(new String[]{ NamedSymbol.PLUS.getName(), NamedSymbol.CROSS.getName(), NamedSymbol.STAR.getName(), NamedSymbol.SQUARE.getName(), NamedSymbol.CIRCLE.getName(), NamedSymbol.PIN.getName() }); private final SelectionChangeHandler selectionChangeHandler; private final StyleUpdater styleUpdater; private final AtomicBoolean isAdjusting; public VectorDataLayerEditor() { selectionChangeHandler = new SelectionChangeHandler(); styleUpdater = new StyleUpdater(); isAdjusting = new AtomicBoolean(false); } @Override protected void addEditablePropertyDescriptors() { final Figure[] figures = getFigures(false); final PropertyDescriptor fillColor = new PropertyDescriptor(DefaultFigureStyle.FILL_COLOR); fillColor.setDefaultValue(getCommonStylePropertyValue(figures, FILL_COLOR_NAME)); addPropertyDescriptor(fillColor); final PropertyDescriptor fillOpacity = new PropertyDescriptor(DefaultFigureStyle.FILL_OPACITY); fillOpacity.setDefaultValue(getCommonStylePropertyValue(figures, FILL_OPACITY_NAME)); addPropertyDescriptor(fillOpacity); final PropertyDescriptor strokeColor = new PropertyDescriptor(DefaultFigureStyle.STROKE_COLOR); strokeColor.setDefaultValue(getCommonStylePropertyValue(figures, STROKE_COLOR_NAME)); addPropertyDescriptor(strokeColor); final PropertyDescriptor strokeOpacity = new PropertyDescriptor(DefaultFigureStyle.STROKE_OPACITY); strokeOpacity.setDefaultValue(getCommonStylePropertyValue(figures, STROKE_OPACITY_NAME)); addPropertyDescriptor(strokeOpacity); final PropertyDescriptor strokeWidth = new PropertyDescriptor(DefaultFigureStyle.STROKE_WIDTH); strokeWidth.setDefaultValue(getCommonStylePropertyValue(figures, STROKE_WIDTH_NAME)); addPropertyDescriptor(strokeWidth); final PropertyDescriptor symbolName = new PropertyDescriptor(DefaultFigureStyle.SYMBOL_NAME); symbolName.setDefaultValue(getCommonStylePropertyValue(figures, SYMBOL_NAME_NAME)); symbolName.setValueSet(SYMBOL_VALUE_SET); symbolName.setNotNull(false); addPropertyDescriptor(symbolName); getBindingContext().bindEnabledState(SYMBOL_NAME_NAME, false, SYMBOL_NAME_NAME, null); } @Override public void handleLayerContentChanged() { if (isAdjusting.compareAndSet(false, true)) { try { updateProperties(getFigures(false), getBindingContext()); } finally { isAdjusting.set(false); } } } @Override public void handleEditorAttached() { FigureEditor figureEditor = getFigureEditor(); if (figureEditor != null) { figureEditor.addSelectionChangeListener(selectionChangeHandler); } getBindingContext().addPropertyChangeListener(styleUpdater); } @Override public void handleEditorDetached() { FigureEditor figureEditor = getFigureEditor(); if (figureEditor != null) { figureEditor.removeSelectionChangeListener(selectionChangeHandler); } getBindingContext().removePropertyChangeListener(styleUpdater); } private VectorDataNode getVectorDataNode() { final FigureEditor figureEditor = getFigureEditor(); if (figureEditor instanceof VectorDataFigureEditor) { VectorDataFigureEditor editor = (VectorDataFigureEditor) figureEditor; return editor.getVectorDataNode(); } else { return null; } } protected void updateProperties(SimpleFeatureFigure[] selectedFigures, BindingContext bindingContext) { updateProperty(bindingContext, FILL_COLOR_NAME, getCommonStylePropertyValue(selectedFigures, FILL_COLOR_NAME)); updateProperty(bindingContext, FILL_OPACITY_NAME, getCommonStylePropertyValue(selectedFigures, FILL_OPACITY_NAME)); updateProperty(bindingContext, STROKE_COLOR_NAME, getCommonStylePropertyValue(selectedFigures, STROKE_COLOR_NAME)); updateProperty(bindingContext, STROKE_OPACITY_NAME, getCommonStylePropertyValue(selectedFigures, STROKE_OPACITY_NAME)); updateProperty(bindingContext, STROKE_WIDTH_NAME, getCommonStylePropertyValue(selectedFigures, STROKE_WIDTH_NAME)); final Object styleProperty = getCommonStylePropertyValue(selectedFigures, SYMBOL_NAME_NAME); if (styleProperty != null) { updateProperty(bindingContext, SYMBOL_NAME_NAME, styleProperty); } } protected void updateProperty(BindingContext bindingContext, String propertyName, Object styleValue) { PropertySet propertySet = bindingContext.getPropertySet(); if (propertySet.isPropertyDefined(propertyName)) { final Object oldValue = propertySet.getValue(propertyName); if (!ObjectUtils.equalObjects(oldValue, styleValue)) { propertySet.setValue(propertyName, styleValue); } } } protected void updateStyle(BindingContext bindingContext, String propertyName, FigureStyle style) { final Object value = bindingContext.getPropertySet().getValue(propertyName); if (value != null) { style.setValue(propertyName, value); } } private void updateColorAndOpacity(String colorPropertyName, String opacityPropertyName) { PropertySet propertySet = getBindingContext().getPropertySet(); Color color = propertySet.getValue(colorPropertyName); boolean isTransparent = color != null && color.getAlpha() == 0; if (isTransparent) { propertySet.setValue(opacityPropertyName, 0.0); } else { Double transparency = propertySet.getValue(opacityPropertyName); if (transparency != null && transparency == 0.0) { propertySet.setValue(opacityPropertyName, 0.5); } } getBindingContext().setComponentsEnabled(opacityPropertyName, !isTransparent); } private Object getCommonStylePropertyValue(Figure[] figures, String propertyName) { Object commonValue = null; for (Figure figure : figures) { final Object value = figure.getNormalStyle().getValue(propertyName); if (commonValue == null) { commonValue = value; } else { if (!commonValue.equals(value)) { return null; } } } return commonValue; } private VectorDataLayer getVectorDataLayer() { Layer selectedLayer = getSelectedLayer(); if (selectedLayer instanceof VectorDataLayer) { return (VectorDataLayer) selectedLayer; } else { return null; } } private ProductSceneView getSelectedProductSceneView() { return Utilities.actionsGlobalContext().lookup(ProductSceneView.class); } private FigureEditor getFigureEditor() { final ProductSceneView view = getSelectedProductSceneView(); return view != null ? view.getFigureEditor() : null; } private Layer getSelectedLayer() { final ProductSceneView view = getSelectedProductSceneView(); return view != null ? view.getSelectedLayer() : null; } private SimpleFeatureFigure[] getFigures(boolean selectedOnly) { final ProductSceneView sceneView = getSelectedProductSceneView(); final SimpleFeatureFigure[] featureFigures = sceneView.getFeatureFigures(selectedOnly); if (featureFigures.length > 0) { return featureFigures; } return NO_SIMPLE_FEATURE_FIGURES; } private boolean areFiguresSelected() { final ProductSceneView sceneView = getSelectedProductSceneView(); return sceneView.getFigureEditor().getFigureSelection().isEmpty(); } private class SelectionChangeHandler extends AbstractSelectionChangeListener { @Override public void selectionChanged(SelectionChangeEvent event) { handleLayerContentChanged(); } } /** * Used to update the figure style, whenever users change style values using the editor. */ private class StyleUpdater implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { Debug.trace(String.format("VectorDataLayerEditor$StyleUpdater (1): property change: name=%s, oldValue=%s, newValue=%s", evt.getPropertyName(), evt.getOldValue(), evt.getNewValue())); if (evt.getNewValue() == null) { return; } String propertyName = evt.getPropertyName(); if (FILL_COLOR_NAME.equals(propertyName)) { updateColorAndOpacity(FILL_COLOR_NAME, FILL_OPACITY_NAME); } else if (STROKE_COLOR_NAME.equals(propertyName)) { updateColorAndOpacity(STROKE_COLOR_NAME, STROKE_OPACITY_NAME); } if (isAdjusting.compareAndSet(false, true)) { try { SimpleFeatureFigure[] figures = getFigures(true); if (figures.length == 0) { figures = getFigures(false); // todo - implement the following (nf) // DefaultFigureStyle figureStyle = new DefaultFigureStyle(""); // default values are the common style values of all features // figureStyle.setValue(FILL_COLOR_NAME, getCommonStylePropertyValue(figures, FILL_COLOR_NAME)); // figureStyle.setValue(FILL_OPACITY_NAME, getCommonStylePropertyValue(figures, FILL_OPACITY_NAME)); // figureStyle.setValue(STROKE_COLOR_NAME, getCommonStylePropertyValue(figures, STROKE_COLOR_NAME)); // ... // figureStyle.setValue(evt.getPropertyName(), evt.getNewValue()); // String styleCss = figureStyle.toCssString(); // this will fire a "styleCss" product node change, which VectorDataLayer will receive // in order to set the layer style. // final VectorDataNode vectorDataNode = getVectorDataNode(); // if (vectorDataNode != null) { // vectorDataNode.setStyleCss(styleCss); // } } for (SimpleFeatureFigure figure : figures) { final Object oldFigureValue = figure.getNormalStyle().getValue(propertyName); final Object newValue = evt.getNewValue(); if (!newValue.equals(oldFigureValue)) { Debug.trace(String.format("VectorDataLayerEditor$StyleUpdater (2): about to apply change: name=%s, oldValue=%s, newValue=%s", propertyName, oldFigureValue, evt.getNewValue())); // Transfer new style to affected figure. final FigureStyle origStyle = figure.getNormalStyle(); final FigureStyle style = new DefaultFigureStyle(); style.fromCssString(origStyle.toCssString()); updateStyle(getBindingContext(), evt.getPropertyName(), style); figure.setNormalStyle(style); // todo - Actually figure.setNormalStyle(style); --> should fire event, so that associated // placemark can save the new style. (nf 2011-11-23) setFeatureStyleCss(figure, style); } } } finally { isAdjusting.set(false); } } } private void setFeatureStyleCss(SimpleFeatureFigure selectedFigure, FigureStyle style) { final VectorDataNode vectorDataNode = getVectorDataNode(); if (vectorDataNode != null) { // Transfer new style to associated placemark. Awful code :-( final Placemark placemark = vectorDataNode.getPlacemarkGroup().getPlacemark(selectedFigure.getSimpleFeature()); if (placemark != null) { placemark.setStyleCss(style.toCssString()); } else { final int index = selectedFigure.getSimpleFeature().getFeatureType().indexOf(Placemark.PROPERTY_NAME_STYLE_CSS); if (index != -1) { selectedFigure.getSimpleFeature().setAttribute(index, style.toCssString()); } } } } } }