Index: /applications/editors/josm/plugins/imagery_offset_db/src/iodb/GetImageryOffsetAction.java
===================================================================
--- /applications/editors/josm/plugins/imagery_offset_db/src/iodb/GetImageryOffsetAction.java	(revision 29393)
+++ /applications/editors/josm/plugins/imagery_offset_db/src/iodb/GetImageryOffsetAction.java	(revision 29394)
@@ -7,4 +7,6 @@
 import java.net.*;
 import java.util.*;
+import javax.swing.Action;
+import javax.swing.Icon;
 import javax.swing.JOptionPane;
 import org.openstreetmap.josm.Main;
@@ -15,4 +17,5 @@
 import org.openstreetmap.josm.gui.layer.ImageryLayer;
 import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -24,7 +27,10 @@
  */
 public class GetImageryOffsetAction extends JosmAction {
+    private Icon iconOffsetOk;
+    private Icon iconOffsetBad;
     
     /**
      * Initialize the action. Sets "Ctrl+Alt+I" shortcut: the only shortcut in this plugin.
+     * Also registers itself with {@link ImageryOffsetWatcher}.
      */
     public GetImageryOffsetAction() {
@@ -32,4 +38,7 @@
                 Shortcut.registerShortcut("imageryoffset:get", tr("Imagery: {0}", tr("Get Imagery Offset...")),
                 KeyEvent.VK_I, Shortcut.ALT_CTRL), true);
+        iconOffsetOk = ImageProvider.get("getoffset");
+        iconOffsetBad = ImageProvider.get("getoffsetnow"); // todo: create icon
+        ImageryOffsetWatcher.getInstance().register(this);
     }
 
@@ -84,9 +93,25 @@
 
     /**
+     * Update action icon based on an offset state.
+     */
+    public void offsetStateChanged( boolean isOffsetGood ) {
+        putValue(Action.SMALL_ICON, isOffsetGood ? iconOffsetOk : iconOffsetBad);
+    }
+
+    /**
+     * Remove offset listener.
+     */
+    @Override
+    public void destroy() {
+        ImageryOffsetWatcher.getInstance().unregister(this);
+        super.destroy();
+    }
+
+    /**
      * A task that downloads offsets for a given position and imagery layer,
      * then parses resulting XML and calls
      * {@link #showOffsetDialog(java.util.List)} on success.
      */
-    class DownloadOffsetsTask extends SimpleOffsetQueryTask {
+    private class DownloadOffsetsTask extends SimpleOffsetQueryTask {
         private ImageryLayer layer;
         private List<ImageryOffsetBase> offsets;
Index: /applications/editors/josm/plugins/imagery_offset_db/src/iodb/ImageryOffsetWatcher.java
===================================================================
--- /applications/editors/josm/plugins/imagery_offset_db/src/iodb/ImageryOffsetWatcher.java	(revision 29394)
+++ /applications/editors/josm/plugins/imagery_offset_db/src/iodb/ImageryOffsetWatcher.java	(revision 29394)
@@ -0,0 +1,131 @@
+package iodb;
+
+import java.util.*;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.layer.ImageryLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+
+/**
+ * This class watches imagery layer offsets and notifies listeners when there's a need to update offset
+ * for the current layer.
+ *
+ * @author zverik
+ */
+public class ImageryOffsetWatcher implements MapView.ZoomChangeListener, MapView.LayerChangeListener {
+    private static final double THRESHOLD = 1e-8;
+    private static final double MAX_DISTANCE = Main.pref.getDouble("iodb.offset.radius", 15) * 1000;
+    private Map<Integer, ImageryLayerData> layers = new TreeMap<Integer, ImageryLayerData>();
+    private List<GetImageryOffsetAction> listeners = new ArrayList<GetImageryOffsetAction>();
+    private Timer time;
+    private static ImageryOffsetWatcher instance;
+    private boolean offsetGood;
+
+    private ImageryOffsetWatcher() {
+        if( MAX_DISTANCE <= 0 )
+            return;
+        MapView.addZoomChangeListener(this);
+        MapView.addLayerChangeListener(this);
+        checkOffset(); // we assume there's at the most one imagery layer at this moment
+        time = new Timer();
+        time.schedule(new IntervalOffsetChecker(), 0, 2000);
+    }
+
+    /**
+     * Unregister all events. This actually gets never called, but it's not a problem.
+     */
+    public void destroy() {
+        MapView.removeZoomChangeListener(this);
+        MapView.removeLayerChangeListener(this);
+        time.cancel();
+    }
+
+    public static ImageryOffsetWatcher getInstance() {
+        if( instance == null ) {
+            instance = new ImageryOffsetWatcher();
+        }
+        return instance;
+    }
+
+    public void register( GetImageryOffsetAction listener ) {
+        listeners.add(listener);
+        listener.offsetStateChanged(offsetGood);
+    }
+
+    public void unregister( GetImageryOffsetAction listener ) {
+        listeners.remove(listener);
+    }
+
+    private void setOffsetGood( boolean good ) {
+        if( good != offsetGood ) {
+            for( GetImageryOffsetAction listener : listeners )
+                listener.offsetStateChanged(good);
+        }
+        offsetGood = good;
+    }
+
+    private void checkOffset() {
+        ImageryLayer layer = ImageryOffsetTools.getTopImageryLayer();
+        if( layer == null ) {
+            setOffsetGood(true);
+            return;
+        }
+        LatLon center = ImageryOffsetTools.getMapCenter();
+        Integer hash = layer.hashCode();
+        ImageryLayerData data = layers.get(hash);
+        if( data == null ) {
+            // create entry for this layer and mark as needing alignment
+            data = new ImageryLayerData();
+            data.lastDx = layer.getDx();
+            data.lastDy = layer.getDy();
+            boolean r = false;
+            if( Math.abs(data.lastDx) + Math.abs(data.lastDy) > THRESHOLD ) {
+                data.lastChecked = center;
+                r = true;
+            }
+            layers.put(hash, data);
+            setOffsetGood(r);
+        } else {
+            // now, we have a returning layer.
+            if( Math.abs(data.lastDx - layer.getDx()) + Math.abs(data.lastDy - layer.getDy()) > THRESHOLD ) {
+                // offset has changed, record the current position
+                data.lastDx = layer.getDx();
+                data.lastDy = layer.getDy();
+                data.lastChecked = center;
+                setOffsetGood(true);
+            } else {
+                setOffsetGood(data.lastChecked != null && center.greatCircleDistance(data.lastChecked) <= MAX_DISTANCE);
+            }
+        }
+    }
+
+    private static class ImageryLayerData {
+        public double lastDx = 0.0;
+        public double lastDy = 0.0;
+        public LatLon lastChecked;
+    }
+
+    public void zoomChanged() {
+        checkOffset();
+    }
+
+    public void activeLayerChange( Layer oldLayer, Layer newLayer ) {
+        checkOffset();
+    }
+
+    public void layerAdded( Layer newLayer ) {
+        checkOffset();
+    }
+
+    public void layerRemoved( Layer oldLayer ) {
+        checkOffset();
+    }
+
+    private class IntervalOffsetChecker extends TimerTask {
+        @Override
+        public void run() {
+            checkOffset();
+        }
+    }
+}
Index: /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialog.java
===================================================================
--- /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialog.java	(revision 29393)
+++ /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialog.java	(revision 29394)
@@ -17,5 +17,4 @@
 import org.openstreetmap.josm.gui.JosmUserIdentityManager;
 import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.layer.ImageryLayer;
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
@@ -29,5 +28,5 @@
  * @license WTFPL
  */
-public class OffsetDialog extends JDialog implements ActionListener, NavigatableComponent.ZoomChangeListener, MapViewPaintable {
+public class OffsetDialog extends JDialog implements ActionListener, MapView.ZoomChangeListener, MapViewPaintable {
     protected static final String PREF_CALIBRATION = "iodb.show.calibration";
     protected static final String PREF_DEPRECATED = "iodb.show.deprecated";
@@ -55,5 +54,4 @@
         setResizable(false);
         this.offsets = offsets;
-        NavigatableComponent.addZoomChangeListener(this);
 
         // make this dialog close on "escape"
@@ -194,4 +192,5 @@
         selectedOffset = null;
         prepareDialog();
+        MapView.addZoomChangeListener(this);
         if( !MODAL ) {
             Main.map.mapView.addTemporaryLayer(this);
@@ -218,5 +217,5 @@
                 || Main.pref.getBoolean("iodb.close.on.select", true);
         if( closeDialog ) {
-            NavigatableComponent.removeZoomChangeListener(this);
+            MapView.removeZoomChangeListener(this);
             setVisible(false);
         }
Index: /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialogButton.java
===================================================================
--- /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialogButton.java	(revision 29393)
+++ /applications/editors/josm/plugins/imagery_offset_db/src/iodb/OffsetDialogButton.java	(revision 29394)
@@ -74,5 +74,5 @@
         description = description.replace("<", "&lt;").replace(">", "&gt;");
         JLabel descriptionLabel = new JLabel("<html><div style=\"width: 300px;\">"+description+"</div></html>");
-        Font descriptionFont = new Font(descriptionLabel.getFont().getName(), Font.BOLD, descriptionLabel.getFont().getSize() - 2);
+        Font descriptionFont = new Font(descriptionLabel.getFont().getName(), Font.BOLD, descriptionLabel.getFont().getSize());
         descriptionLabel.setFont(descriptionFont);
 
