/*
* Copyright (C) 2010 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.sync;
import com.bc.ceres.glayer.support.ImageLayer;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.netbeans.docwin.DocumentWindowManager;
import org.esa.snap.netbeans.docwin.DocumentWindowManager.Predicate;
import org.esa.snap.netbeans.docwin.WindowUtilities;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.actions.tools.SyncImageCursorsAction;
import org.esa.snap.rcp.windows.ProductSceneViewTopComponent;
import org.esa.snap.ui.PixelPositionListener;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.windows.OnShowing;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
/**
* @author Marco Peters, Norman Fomferra
*/
@OnShowing
public class ImageCursorSynchronizer implements Runnable {
public static final String PROPERTY_KEY_AUTO_SYNC_CURSORS = SyncImageCursorsAction.PREFERENCE_KEY;
private static final GeoPos INVALID_GEO_POS = new GeoPos(Float.NaN, Float.NaN);
private static final Predicate<Object, ProductSceneView> SCENE_VIEW_PREDICATE = Predicate.view(ProductSceneView.class);
private Map<ProductSceneView, ImageCursorOverlay> psvOverlayMap;
private Map<ProductSceneView, MyPixelPositionListener> viewPplMap;
private PsvListUpdater psvOverlayMapUpdater;
@Override
public void run() {
psvOverlayMap = new WeakHashMap<>();
viewPplMap = new WeakHashMap<>();
psvOverlayMapUpdater = new PsvListUpdater();
Preferences preferences = SnapApp.getDefault().getPreferences();
preferences.addPreferenceChangeListener(new ImageCursorSynchronizerPreferenceChangeListener());
}
private boolean isActive() {
return SnapApp.getDefault().getPreferences().getBoolean(PROPERTY_KEY_AUTO_SYNC_CURSORS,
SyncImageCursorsAction.PREFERENCE_DEFAULT_VALUE);
}
public void updateCursorOverlays(GeoPos geoPos, ProductSceneView sourceView) {
if (!isActive()) {
return;
}
for (Map.Entry<ProductSceneView, ImageCursorOverlay> entry : psvOverlayMap.entrySet()) {
final ProductSceneView view = entry.getKey();
ImageCursorOverlay overlay = entry.getValue();
if (overlay == null) {
if (view != sourceView) {
overlay = new ImageCursorOverlay(view, geoPos);
psvOverlayMap.put(view, overlay);
view.getLayerCanvas().addOverlay(overlay);
}
} else {
if (view != sourceView) {
overlay.setGeoPosition(geoPos);
view.getLayerCanvas().repaint();
} else {
view.getLayerCanvas().removeOverlay(overlay);
psvOverlayMap.put(view, null);
}
}
}
}
private void initPsvOverlayMap() {
WindowUtilities.getOpened(ProductSceneViewTopComponent.class)
.map(ProductSceneViewTopComponent::getView)
.forEach(this::addPPL);
}
private void clearPsvOverlayMap() {
for (Map.Entry<ProductSceneView, ImageCursorOverlay> entry : psvOverlayMap.entrySet()) {
final ProductSceneView view = entry.getKey();
removePPL(view);
view.getLayerCanvas().removeOverlay(entry.getValue());
}
psvOverlayMap.clear();
}
private void addPPL(ProductSceneView view) {
GeoCoding geoCoding = view.getProduct().getSceneGeoCoding();
if (geoCoding != null && geoCoding.canGetPixelPos()) {
psvOverlayMap.put(view, null);
MyPixelPositionListener ppl = new MyPixelPositionListener(view);
viewPplMap.put(view, ppl);
view.addPixelPositionListener(ppl);
}
}
private void removePPL(ProductSceneView view) {
MyPixelPositionListener ppl = viewPplMap.get(view);
if (ppl != null) {
viewPplMap.remove(view);
view.removePixelPositionListener(ppl);
}
}
private class PsvListUpdater implements DocumentWindowManager.Listener<Object, ProductSceneView> {
@Override
public void windowOpened(DocumentWindowManager.Event<Object, ProductSceneView> e) {
addPPL(e.getWindow().getView());
}
@Override
public void windowClosed(DocumentWindowManager.Event<Object, ProductSceneView> e) {
removePPL(e.getWindow().getView());
}
}
private class MyPixelPositionListener implements PixelPositionListener {
private final ProductSceneView view;
private MyPixelPositionListener(ProductSceneView view) {
this.view = view;
}
@Override
public void pixelPosChanged(ImageLayer baseImageLayer, int pixelX, int pixelY, int currentLevel,
boolean pixelPosValid, MouseEvent e) {
PixelPos pixelPos = computeLevelZeroPixelPos(baseImageLayer, pixelX, pixelY, currentLevel);
GeoPos geoPos = view.getRaster().getGeoCoding().getGeoPos(pixelPos, null);
updateCursorOverlays(geoPos, view);
}
private PixelPos computeLevelZeroPixelPos(ImageLayer imageLayer, int pixelX, int pixelY, int currentLevel) {
if (currentLevel != 0) {
AffineTransform i2mTransform = imageLayer.getImageToModelTransform(currentLevel);
Point2D modelP = i2mTransform.transform(new Point2D.Double(pixelX + 0.5, pixelY + 0.5), null);
AffineTransform m2iTransform = imageLayer.getModelToImageTransform();
Point2D imageP = m2iTransform.transform(modelP, null);
return new PixelPos(new Float(imageP.getX()), new Float(imageP.getY()));
} else {
return new PixelPos(pixelX + 0.5, pixelY + 0.5);
}
}
@Override
public void pixelPosNotAvailable() {
updateCursorOverlays(INVALID_GEO_POS, null);
}
}
private class ImageCursorSynchronizerPreferenceChangeListener implements PreferenceChangeListener {
@Override
public void preferenceChange(PreferenceChangeEvent evt) {
if (PROPERTY_KEY_AUTO_SYNC_CURSORS.equals(evt.getKey())) {
if (isActive()) {
initPsvOverlayMap();
DocumentWindowManager.getDefault().addListener(SCENE_VIEW_PREDICATE, psvOverlayMapUpdater);
} else {
DocumentWindowManager.getDefault().removeListener(SCENE_VIEW_PREDICATE, psvOverlayMapUpdater);
clearPsvOverlayMap();
}
}
}
}
}