/*
* Copyright (C) 2015 by Array Systems Computing Inc. http://www.array.ca
*
* 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.landcover.rcp;
import org.esa.snap.landcover.dataio.LandCoverFactory;
import org.esa.snap.landcover.gpf.AddLandCoverOp;
import com.bc.ceres.binding.Property;
import com.bc.ceres.binding.PropertyContainer;
import com.bc.ceres.binding.PropertyDescriptor;
import com.bc.ceres.binding.PropertySet;
import com.bc.ceres.binding.ValidationException;
import com.bc.ceres.binding.Validator;
import com.bc.ceres.binding.ValueSet;
import com.bc.ceres.swing.TableLayout;
import com.bc.ceres.swing.binding.BindingContext;
import com.bc.ceres.swing.binding.ComponentAdapter;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.dataop.resamp.ResamplingFactory;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.ui.ModalDialog;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import static org.esa.snap.landcover.gpf.AddLandCoverOp.AddLandCover;
@ActionID(
category = "Tools",
id = "AddLandCoverAction"
)
@ActionRegistration(
displayName = "#CTL_AddLandCoverAction_MenuText",
popupText = "#CTL_AddLandCoverAction_MenuText"
)
@ActionReferences({
@ActionReference(
path = "Context/Product/Product",
position = 21
),
@ActionReference(
path = "Context/Product/RasterDataNode",
position = 251
),
})
@NbBundle.Messages({
"CTL_AddLandCoverAction_MenuText=Add Land Cover Band",
"CTL_AddLandCoverAction_ShortDescription=Create a new land cover band from a selected land cover model"
})
public class AddLandCoverAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {
private static final String HELP_ID = "addLandCoverBand";
private final Lookup lkp;
private Product product;
public static final String DIALOG_TITLE = "Add Land Cover Band";
public AddLandCoverAction() {
this(Utilities.actionsGlobalContext());
}
public AddLandCoverAction(Lookup lkp) {
super(Bundle.CTL_AddLandCoverAction_MenuText());
this.lkp = lkp;
Lookup.Result<ProductNode> lkpContext = lkp.lookupResult(ProductNode.class);
lkpContext.addLookupListener(WeakListeners.create(LookupListener.class, this, lkpContext));
setEnableState();
putValue(Action.SHORT_DESCRIPTION, Bundle.CTL_AddLandCoverAction_ShortDescription());
}
@Override
public Action createContextAwareInstance(Lookup actionContext) {
return new AddLandCoverAction(actionContext);
}
@Override
public void resultChanged(LookupEvent ev) {
setEnableState();
}
private void setEnableState() {
ProductNode productNode = lkp.lookup(ProductNode.class);
boolean state = false;
if (productNode != null) {
product = productNode.getProduct();
state = product.getSceneGeoCoding() != null;
}
setEnabled(state);
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(HELP_ID);
}
@Override
public void actionPerformed(final ActionEvent event) {
try {
final AddLandCoverOp.LandCoverParameters param = requestDialogData(product);
if (param == null) {
return;
}
AddLandCover(product, param);
} catch (Exception e) {
SnapApp.getDefault().handleError(e.getMessage(), e);
}
}
private AddLandCoverOp.LandCoverParameters requestDialogData(final Product product) {
String[] Names = LandCoverFactory.getNameList();
// sort the list
final List<String> sortedNames = Arrays.asList(Names);
java.util.Collections.sort(sortedNames);
Names = sortedNames.toArray(new String[sortedNames.size()]);
final AddLandCoverOp.LandCoverParameters dialogData = new AddLandCoverOp.LandCoverParameters(Names[0], ResamplingFactory.NEAREST_NEIGHBOUR_NAME);
final PropertySet propertySet = PropertyContainer.createObjectBacked(dialogData);
configureNameProperty(propertySet, "name", Names, Names[0]);
configureNameProperty(propertySet, "resamplingMethod", ResamplingFactory.resamplingNames,
ResamplingFactory.NEAREST_NEIGHBOUR_NAME);
configureBandNameProperty(propertySet, "bandName", product);
final BindingContext ctx = new BindingContext(propertySet);
final JList landCoverList = new JList();
landCoverList.setVisibleRowCount(10);
ctx.bind("name", new SingleSelectionListComponentAdapter(landCoverList, product));
final JTextField bandNameField = new JTextField();
bandNameField.setColumns(30);
ctx.bind("bandName", bandNameField);
final TableLayout tableLayout = new TableLayout(2);
tableLayout.setTableAnchor(TableLayout.Anchor.WEST);
tableLayout.setTableFill(TableLayout.Fill.HORIZONTAL);
tableLayout.setTablePadding(4, 4);
for (int i = 0; i < 8; i++) {
tableLayout.setCellColspan(i, 0, 2);
}
final JPanel parameterPanel = new JPanel(tableLayout);
/*row 0*/
parameterPanel.add(new JLabel("Land Cover Model:"));
parameterPanel.add(new JScrollPane(landCoverList));
/*row 1*/
parameterPanel.add(new JLabel("Resampling method:"));
final JComboBox resamplingCombo = new JComboBox(ResamplingFactory.resamplingNames);
parameterPanel.add(resamplingCombo);
ctx.bind("resamplingMethod", resamplingCombo);
parameterPanel.add(new JLabel("Integer data types will use nearest neighbour"));
parameterPanel.add(new JLabel("Land cover band name:"));
parameterPanel.add(bandNameField);
final ModalDialog dialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), DIALOG_TITLE, ModalDialog.ID_OK_CANCEL, HELP_ID);
dialog.setContent(parameterPanel);
if (dialog.show() == ModalDialog.ID_OK) {
return dialogData;
}
return null;
}
private static void configureNameProperty(PropertySet propertySet, String propertyName, String[] names, String defaultValue) {
final PropertyDescriptor descriptor = propertySet.getProperty(propertyName).getDescriptor();
descriptor.setValueSet(new ValueSet(names));
descriptor.setDefaultValue(defaultValue);
descriptor.setNotNull(true);
descriptor.setNotEmpty(true);
}
private static void configureBandNameProperty(PropertySet propertySet, String propertyName, Product product) {
final Property property = propertySet.getProperty(propertyName);
final PropertyDescriptor descriptor = property.getDescriptor();
descriptor.setNotNull(true);
descriptor.setNotEmpty(true);
descriptor.setValidator(new BandNameValidator(product));
}
private static class SingleSelectionListComponentAdapter extends ComponentAdapter implements ListSelectionListener, PropertyChangeListener {
private final JList list;
private final Product product;
public SingleSelectionListComponentAdapter(final JList list, final Product product) {
this.list = list;
this.product = product;
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
@Override
public JComponent[] getComponents() {
return new JComponent[]{list};
}
@Override
public void bindComponents() {
updateListModel();
getPropertyDescriptor().addAttributeChangeListener(this);
list.addListSelectionListener(this);
}
@Override
public void unbindComponents() {
getPropertyDescriptor().removeAttributeChangeListener(this);
list.removeListSelectionListener(this);
}
@Override
public void adjustComponents() {
Object value = getBinding().getPropertyValue();
if (value != null) {
list.setSelectedValue(value, true);
} else {
list.clearSelection();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getSource() == getPropertyDescriptor() && evt.getPropertyName().equals("valueSet")) {
updateListModel();
}
}
private PropertyDescriptor getPropertyDescriptor() {
return getBinding().getContext().getPropertySet().getDescriptor(getBinding().getPropertyName());
}
private void updateListModel() {
ValueSet valueSet = getPropertyDescriptor().getValueSet();
if (valueSet != null) {
list.setListData(valueSet.getItems());
adjustComponents();
}
}
@Override
public void valueChanged(ListSelectionEvent event) {
if (event.getValueIsAdjusting()) {
return;
}
if (getBinding().isAdjustingComponents()) {
return;
}
final Property property = getBinding().getContext().getPropertySet().getProperty(getBinding().getPropertyName());
Object selectedValue = list.getSelectedValue();
try {
property.setValue(selectedValue);
final Property bandNameProperty = getBinding().getContext().getPropertySet().getProperty("bandName");
bandNameProperty.setValueFromText(AddLandCoverOp.getValidBandName((String) selectedValue, product));
// Now model is in sync with UI
getBinding().clearProblem();
} catch (ValidationException e) {
getBinding().reportProblem(e);
}
}
}
private static class BandNameValidator implements Validator {
private final Product product;
public BandNameValidator(Product product) {
this.product = product;
}
@Override
public void validateValue(Property property, Object value) throws ValidationException {
final String bandName = value.toString().trim();
if (!ProductNode.isValidNodeName(bandName)) {
throw new ValidationException(MessageFormat.format("The band name ''{0}'' appears not to be valid.\n" +
"Please choose another one.",
bandName));
} else if (product.containsBand(bandName)) {
throw new ValidationException(MessageFormat.format("The selected product already contains a band named ''{0}''.\n" +
"Please choose another one.",
bandName));
}
}
}
}