package org.openstreetmap.josm.plugins.photoadjust; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Cursor; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.gui.IconToggleButton; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer; import org.openstreetmap.josm.tools.Shortcut; /** * Map mode version of the plug-in. The associated button is shown * only if there is at least one GeoImageLayer. Otherwise the button * is hidden to make room for other buttons. This map mode is * inactive if the active layer is a GeoImageLayer as the main plug-in * handles it. */ public class PhotoAdjustMapMode extends MapMode implements LayerChangeListener, ActiveLayerChangeListener { /** True if this map mode waits for mouse events. */ private boolean modeActive = false; /** True if the map mode button is selected and active. */ private boolean modeSelected = false; private MouseAdapter mouseAdapter; private MouseMotionAdapter mouseMotionAdapter; private IconToggleButton mmButton; private PhotoAdjustWorker worker; /** True if one existing GeoImageLayer is to be ignored. */ private boolean ignoreOneGILayer = false; public PhotoAdjustMapMode(PhotoAdjustWorker worker) { super(tr("Adjust photos"), "photoadjust.png", tr("Move and position photos"), // It is almost impossible to find an unused shortcut. Shortcut.registerShortcut("mapmode:photoadjust", tr("Mode: {0}", tr("Adjust photos")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); GeoImageLayer.registerSupportedMapMode(this); initAdapters(); this.worker = worker; Main.getLayerManager().addLayerChangeListener(this); Main.getLayerManager().addActiveLayerChangeListener(this); } /** * The main plugin does nothing but to call this method, which * connects this map mode with a button that is registered as map * mode. * * @param mapFrame Map frame the map mode button is added to. */ public void installMapMode(MapFrame mapFrame) { mmButton = new IconToggleButton(this); mmButton.setAutoHideDisabledButton(true); mapFrame.addMapMode(mmButton); } @Override public String getModeHelpText() { if (hasLayersToAdjust()) { return tr("Click+drag photo, shift+click to position photo, control+click to set direction."); } else { return tr("Please load some photos."); } } @Override public boolean layerIsSupported(Layer layer) { return hasLayersToAdjust(); } @Override protected void updateEnabledState() { setEnabled(hasLayersToAdjust()); } /** * Update the button state. We do this more frequently than the * JOSM core would do it. */ public void updateButtonState() { if (mmButton != null) { final boolean enabled = hasLayersToAdjust(); setEnabled(enabled); if (enabled) { mmButton.showButton(); } else { mmButton.hideButton(); } } } /** * Activate the map mode, i.e. wait for mouse events. */ private void activateMode() { if (modeSelected && !modeActive) { Main.map.mapView.addMouseListener(mouseAdapter); Main.map.mapView.addMouseMotionListener(mouseMotionAdapter); modeActive = true; updateStatusLine(); } } /** * Deactivate map mode. */ private void deactivateMode() { if (modeActive) { Main.map.mapView.removeMouseListener(mouseAdapter); Main.map.mapView.removeMouseMotionListener(mouseMotionAdapter); modeActive = false; } } @Override public void enterMode() { super.enterMode(); modeSelected = true; // Activate the mode only if the current layer is not a GeoImageLayer. // GeoImageLayer's are handled by the plug-in directly. if (!(Main.getLayerManager().getActiveLayer() instanceof GeoImageLayer)) { activateMode(); } } @Override public void exitMode() { super.exitMode(); deactivateMode(); modeSelected = false; } @Override public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { // The main part of the plugin takes care of all operations if a GeoImageLayer is active. if (Main.getLayerManager().getActiveLayer() instanceof GeoImageLayer) { deactivateMode(); } else { activateMode(); } } @Override public void layerAdded(LayerAddEvent e) { if (modeActive) updateStatusLine(); updateButtonState(); } @Override public void layerRemoving(LayerRemoveEvent lre) { if (lre.getRemovedLayer() instanceof GeoImageLayer) { // A GeoImageLayer is about to be removed. We ignore this layer // in the following update methods to get the correct number of // future GeoImageLayers. ignoreOneGILayer = true; } if (modeActive) updateStatusLine(); updateButtonState(); ignoreOneGILayer = false; } @Override public void layerOrderChanged(LayerOrderChangeEvent e) { } /** * Create mouse adapters similar to the main plug-in. */ private void initAdapters() { // The MapMode implements a MouseAdapter and a // MouseMotionAdapter, but we cannot use them because they get // activated with the first layer that is created. This is // not what we want, we want to activate them if this map mode // gets activated. mouseAdapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { worker.doMousePressed(evt, getVisibleGeoImageLayers()); } @Override public void mouseReleased(MouseEvent evt) { worker.doMouseReleased(evt); } }; mouseMotionAdapter = new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent evt) { worker.doMouseDragged(evt); } }; } /** * Replies true if there is at least one geo image layer. * * @return {@code true} if there is at least one geo image layer */ private boolean hasLayersToAdjust() { if (Main.map == null || Main.map.mapView == null) return false; int giLayerNum = Main.getLayerManager().getLayersOfType(GeoImageLayer.class).size(); if (ignoreOneGILayer) { giLayerNum--; } return giLayerNum > 0; } /** * Prepare list of visible GeoImageLayer's whose photos can be adjusted. * * @return list of visible GeoImageLayer's */ private List<GeoImageLayer> getVisibleGeoImageLayers() { List<GeoImageLayer> all = new ArrayList<>(Main.getLayerManager().getLayersOfType(GeoImageLayer.class)); Iterator<GeoImageLayer> it = all.iterator(); while (it.hasNext()) { if (!it.next().isVisible()) it.remove(); } return all; } @Override public void destroy() { super.destroy(); Main.getLayerManager().removeActiveLayerChangeListener(this); Main.getLayerManager().removeLayerChangeListener(this); } }