/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.esa.snap.rcp.windows;
import com.bc.ceres.glayer.support.ImageLayer;
import com.vividsolutions.jts.geom.Point;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeListener;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.pixelinfo.PixelInfoView;
import org.esa.snap.ui.PixelPositionListener;
import org.esa.snap.ui.product.ProductSceneView;
import org.netbeans.api.annotations.common.NonNull;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import javax.swing.JCheckBox;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* Experimental top component which displays information about selected pixel.
*/
@TopComponent.Description(
preferredID = "PixelInfoTopComponent",
iconBase = "org/esa/snap/rcp/icons/PixelInfo.png",
persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "explorer",
openAtStartup = true,
position = 20
)
@ActionID(category = "Window", id = "org.esa.snap.rcp.window.PixelInfoTopComponent")
@ActionReference(path = "Menu/View/Tool Windows", position = 0)
@TopComponent.OpenActionRegistration(
displayName = "#CTL_PixelInfoTopComponentName",
preferredID = "PixelInfoTopComponent"
)
@NbBundle.Messages({
"CTL_PixelInfoTopComponentName=Pixel Info",
"CTL_PixelInfoTopComponentDescription=Displays information about current pixel",
})
public final class PixelInfoTopComponent extends ToolTopComponent {
private ProductSceneView currentView;
private PixelPositionListener pixelPositionListener;
private final PixelInfoView pixelInfoView;
private final PinSelectionChangeListener pinSelectionChangeListener;
private final PinChangedListener pinChangedListener;
private final JCheckBox pinCheckbox;
public PixelInfoTopComponent() {
setName(Bundle.CTL_PixelInfoTopComponentName());
setToolTipText(Bundle.CTL_PixelInfoTopComponentDescription());
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, Boolean.TRUE);
putClientProperty(TopComponent.PROP_KEEP_PREFERRED_SIZE_WHEN_SLIDED_IN, Boolean.TRUE);
pixelPositionListener = new MyPixelPositionListener();
pinSelectionChangeListener = new PinSelectionChangeListener();
pinChangedListener = new PinChangedListener();
pixelInfoView = new PixelInfoView();
pinCheckbox = new JCheckBox("Snap to selected pin");
pinCheckbox.setName("pinCheckbox");
pinCheckbox.setSelected(false);
pinCheckbox.addActionListener(e -> updatePixelInfo());
setLayout(new BorderLayout());
add(pixelInfoView, BorderLayout.CENTER);
add(pinCheckbox, BorderLayout.SOUTH);
setCurrentView(SnapApp.getDefault().getSelectedProductSceneView());
}
@Override
protected void productSceneViewSelected(@NonNull ProductSceneView view) {
setCurrentView(view);
if (isSnapToSelectedPin()) {
snapToSelectedPin();
}
}
@Override
protected void productSceneViewDeselected(@NonNull ProductSceneView view) {
setCurrentView(null);
if (isSnapToSelectedPin()) {
snapToSelectedPin();
}
}
private void setCurrentView(ProductSceneView view) {
if (currentView == view) {
return;
}
if (currentView != null) {
currentView.removePixelPositionListener(pixelPositionListener);
currentView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN,
pinSelectionChangeListener);
Product product = currentView.getProduct();
if (product != null) {
product.removeProductNodeListener(pinChangedListener);
}
} else {
pixelInfoView.clearProductNodeRefs();
}
currentView = view;
if (currentView != null) {
currentView.addPixelPositionListener(pixelPositionListener);
currentView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN,
pinSelectionChangeListener);
Product product = currentView.getProduct();
if (product != null) {
product.addProductNodeListener(pinChangedListener);
}
}
}
private void updatePixelInfo() {
if (isSnapToSelectedPin()) {
SwingUtilities.invokeLater(this::snapToSelectedPin);
} else {
pixelInfoView.updatePixelValues(currentView, -1, -1, 0, false);
}
}
private boolean isSnapToSelectedPin() {
return pinCheckbox.isSelected();
}
private void snapToSelectedPin() {
final Placemark pin = currentView != null ? currentView.getSelectedPin() : null;
if (pin != null) {
//todo [multisize_products] replace this very ugly code by using the scene raster transformer - tf 20151113
PixelPos rasterPos = new PixelPos();
final Point pinSceneCoords = (Point) pin.getFeature().getDefaultGeometry();
final Point2D.Double pinSceneCoordsDouble = new Point2D.Double(pinSceneCoords.getX(), pinSceneCoords.getY());
try {
currentView.getRaster().getImageToModelTransform().createInverse().transform(pinSceneCoordsDouble, rasterPos);
} catch (NoninvertibleTransformException e) {
rasterPos = pin.getPixelPos();
}
final int x = MathUtils.floorInt(rasterPos.x);
final int y = MathUtils.floorInt(rasterPos.y);
pixelInfoView.updatePixelValues(currentView, x, y, 0, true);
} else {
pixelInfoView.updatePixelValues(currentView, -1, -1, 0, false);
}
}
private class PinSelectionChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (isVisible()) {
updatePixelInfo();
}
}
}
private class PinChangedListener implements ProductNodeListener {
@Override
public void nodeChanged(ProductNodeEvent event) {
if (Placemark.PROPERTY_NAME_PIXELPOS.equals(event.getPropertyName())) {
handlePinEvent(event);
}
}
@Override
public void nodeDataChanged(ProductNodeEvent event) {
handlePinEvent(event);
}
@Override
public void nodeAdded(ProductNodeEvent event) {
handlePinEvent(event);
}
@Override
public void nodeRemoved(ProductNodeEvent event) {
handlePinEvent(event);
}
private void handlePinEvent(ProductNodeEvent event) {
if (currentView != null
&& event.getSourceNode() == currentView.getSelectedPin()) {
updatePixelInfo();
}
}
}
private class MyPixelPositionListener implements PixelPositionListener {
@Override
public void pixelPosChanged(ImageLayer baseImageLayer, int pixelX, int pixelY, int currentLevel, boolean pixelPosValid, MouseEvent e) {
if (isActive()) {
pixelInfoView.updatePixelValues(currentView, pixelX, pixelY, currentLevel, pixelPosValid);
}
}
@Override
public void pixelPosNotAvailable() {
//do nothing
}
private boolean isActive() {
return isVisible() && !isSnapToSelectedPin();
}
}
}