Index: trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 11432)
+++ trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 11434)
@@ -17,4 +17,5 @@
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
@@ -98,5 +99,17 @@
 
     private MouseAdapter mouseAdapter;
+    private MouseMotionAdapter mouseMotionAdapter;
     private MapModeChangeListener mapModeListener;
+
+    /** Mouse position where the last image was selected. */
+    private Point lastSelPos;
+
+    /**
+     * Image cycle mode flag.
+     * It is possible that a mouse button release triggers multiple mouseReleased() events.
+     * To prevent the cycling in such a case we wait for the next mouse button press event
+     * before it is cycled to the next image.
+     */
+    private boolean cycleModeArmed;
 
     /**
@@ -468,4 +481,31 @@
     }
 
+    /**
+     * Paint one image.
+     * @param e Image to be painted
+     * @param mv Map view
+     * @param clip Bounding rectangle of the current clipping area
+     * @param tempG Temporary offscreen buffer
+     */
+    private void paintImage(ImageEntry e, MapView mv, Rectangle clip, Graphics2D tempG) {
+        if (e.getPos() == null) {
+            return;
+        }
+        Point p = mv.getPoint(e.getPos());
+        if (e.hasThumbnail()) {
+            Dimension d = scaledDimension(e.getThumbnail());
+            if (d != null) {
+                Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
+                if (clip.intersects(target)) {
+                    tempG.drawImage(e.getThumbnail(), target.x, target.y, target.width, target.height, null);
+                }
+            }
+        } else { // thumbnail not loaded yet
+            icon.paintIcon(mv, tempG,
+                p.x - icon.getIconWidth() / 2,
+                p.y - icon.getIconHeight() / 2);
+        }
+    }
+
     @Override
     public void paint(Graphics2D g, MapView mv, Bounds bounds) {
@@ -495,21 +535,9 @@
                 if (data != null) {
                     for (ImageEntry e : data) {
-                        if (e.getPos() == null) {
-                            continue;
-                        }
-                        Point p = mv.getPoint(e.getPos());
-                        if (e.hasThumbnail()) {
-                            Dimension d = scaledDimension(e.getThumbnail());
-                            if (d != null) {
-                                Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
-                                if (clip.intersects(target)) {
-                                    tempG.drawImage(e.getThumbnail(), target.x, target.y, target.width, target.height, null);
-                                }
-                            }
-                        } else { // thumbnail not loaded yet
-                            icon.paintIcon(mv, tempG,
-                                    p.x - icon.getIconWidth() / 2,
-                                    p.y - icon.getIconHeight() / 2);
-                        }
+                        paintImage(e, mv, clip, tempG);
+                    }
+                    if (currentPhoto >= 0 && currentPhoto < data.size()) {
+                        // Make sure the selected image is on top in case multiple images overlap.
+                        paintImage(data.get(currentPhoto), mv, clip, tempG);
                     }
                 }
@@ -602,4 +630,18 @@
 
     /**
+     * Show current photo on map and in image viewer.
+     */
+    public void showCurrentPhoto() {
+        clearOtherCurrentPhotos();
+        if (currentPhoto >= 0) {
+            ImageViewerDialog.showImage(this, data.get(currentPhoto));
+        } else {
+            ImageViewerDialog.showImage(this, null);
+        }
+        updateOffscreenBuffer = true;
+        Main.map.repaint();
+    }
+
+    /**
      * Shows next photo.
      */
@@ -610,9 +652,8 @@
                 currentPhoto = data.size() - 1;
             }
-            ImageViewerDialog.showImage(this, data.get(currentPhoto));
         } else {
             currentPhoto = -1;
         }
-        Main.map.repaint();
+        showCurrentPhoto();
     }
 
@@ -626,9 +667,8 @@
                 currentPhoto = 0;
             }
-            ImageViewerDialog.showImage(this, data.get(currentPhoto));
         } else {
             currentPhoto = -1;
         }
-        Main.map.repaint();
+        showCurrentPhoto();
     }
 
@@ -639,9 +679,8 @@
         if (data != null && !data.isEmpty()) {
             currentPhoto = 0;
-            ImageViewerDialog.showImage(this, data.get(currentPhoto));
         } else {
             currentPhoto = -1;
         }
-        Main.map.repaint();
+        showCurrentPhoto();
     }
 
@@ -652,9 +691,8 @@
         if (data != null && !data.isEmpty()) {
             currentPhoto = data.size() - 1;
-            ImageViewerDialog.showImage(this, data.get(currentPhoto));
         } else {
             currentPhoto = -1;
         }
-        Main.map.repaint();
+        showCurrentPhoto();
     }
 
@@ -670,11 +708,5 @@
                 currentPhoto = data.size() - 1;
             }
-            if (currentPhoto >= 0) {
-                ImageViewerDialog.showImage(this, data.get(currentPhoto));
-            } else {
-                ImageViewerDialog.showImage(this, null);
-            }
-            updateOffscreenBuffer = true;
-            Main.map.repaint();
+            showCurrentPhoto();
         }
     }
@@ -703,9 +735,4 @@
                     currentPhoto = data.size() - 1;
                 }
-                if (currentPhoto >= 0) {
-                    ImageViewerDialog.showImage(this, data.get(currentPhoto));
-                } else {
-                    ImageViewerDialog.showImage(this, null);
-                }
 
                 if (Utils.deleteFile(toDelete.getFile())) {
@@ -720,6 +747,5 @@
                 }
 
-                updateOffscreenBuffer = true;
-                Main.map.repaint();
+                showCurrentPhoto();
             }
         }
@@ -744,5 +770,92 @@
 
     /**
+     * Check if the position of the mouse event is within the rectangle of the photo icon or thumbnail.
+     * @param idx Image index, range 0 .. size-1
+     * @param evt Mouse event
+     * @return {@code true} if the photo matches the mouse position, {@code false} otherwise
+     */
+    private boolean isPhotoIdxUnderMouse(int idx, MouseEvent evt) {
+        if (idx >= 0 && data != null && idx < data.size()) {
+            ImageEntry img = data.get(idx);
+            if (img.getPos() != null) {
+                Point imgCenter = Main.map.mapView.getPoint(img.getPos());
+                Rectangle imgRect;
+                if (useThumbs && img.hasThumbnail()) {
+                    Dimension imgDim = scaledDimension(img.getThumbnail());
+                    if (imgDim != null) {
+                        imgRect = new Rectangle(imgCenter.x - imgDim.width / 2,
+                                                imgCenter.y - imgDim.height / 2,
+                                                imgDim.width, imgDim.height);
+                    } else {
+                        imgRect = null;
+                    }
+                } else {
+                    imgRect = new Rectangle(imgCenter.x - icon.getIconWidth() / 2,
+                                            imgCenter.y - icon.getIconHeight() / 2,
+                                            icon.getIconWidth(), icon.getIconHeight());
+                }
+                if (imgRect != null && imgRect.contains(evt.getPoint())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns index of the image that matches the position of the mouse event.
+     * @param evt    Mouse event
+     * @param cycle  Set to {@code true} to cycle through the photos at the
+     *               current mouse position if multiple icons or thumbnails overlap.
+     *               If set to {@code false} the topmost photo will be used.
+     * @return       Image index at mouse position, range 0 .. size-1,
+     *               or {@code -1} if there is no image at the mouse position
+     */
+    private int getPhotoIdxUnderMouse(MouseEvent evt, boolean cycle) {
+        if (data != null) {
+            if (cycle && currentPhoto >= 0) {
+                // Cycle loop is forward as that is the natural order.
+                // Loop 1: One after current photo up to last one.
+                for (int idx = currentPhoto + 1; idx < data.size(); ++idx) {
+                    if (isPhotoIdxUnderMouse(idx, evt)) {
+                        return idx;
+                    }
+                }
+                // Loop 2: First photo up to current one.
+                for (int idx = 0; idx <= currentPhoto; ++idx) {
+                    if (isPhotoIdxUnderMouse(idx, evt)) {
+                        return idx;
+                    }
+                }
+            } else {
+                // Check for current photo first, i.e. keep it selected if it is under the mouse.
+                if (currentPhoto >= 0 && isPhotoIdxUnderMouse(currentPhoto, evt)) {
+                    return currentPhoto;
+                }
+                // Loop from last to first to prefer topmost image.
+                for (int idx = data.size() - 1; idx >= 0; --idx) {
+                    if (isPhotoIdxUnderMouse(idx, evt)) {
+                        return idx;
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns index of the image that matches the position of the mouse event.
+     * The topmost photo is picked if multiple icons or thumbnails overlap.
+     * @param evt Mouse event
+     * @return Image index at mouse position, range 0 .. size-1,
+     *         or {@code -1} if there is no image at the mouse position
+     */
+    private int getPhotoIdxUnderMouse(MouseEvent evt) {
+        return getPhotoIdxUnderMouse(evt, false);
+    }
+
+    /**
      * Returns the image that matches the position of the mouse event.
+     * The topmost photo is picked of multiple icons or thumbnails overlap.
      * @param evt Mouse event
      * @return Image at mouse position, or {@code null} if there is no image at the mouse position
@@ -750,30 +863,10 @@
      */
     public ImageEntry getPhotoUnderMouse(MouseEvent evt) {
-        if (data != null) {
-            for (int idx = data.size() - 1; idx >= 0; --idx) {
-                ImageEntry img = data.get(idx);
-                if (img.getPos() == null) {
-                    continue;
-                }
-                Point p = Main.map.mapView.getPoint(img.getPos());
-                Rectangle r;
-                if (useThumbs && img.hasThumbnail()) {
-                    Dimension d = scaledDimension(img.getThumbnail());
-                    if (d != null)
-                        r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
-                    else
-                        r = null;
-                } else {
-                    r = new Rectangle(p.x - icon.getIconWidth() / 2,
-                                      p.y - icon.getIconHeight() / 2,
-                                      icon.getIconWidth(),
-                                      icon.getIconHeight());
-                }
-                if (r != null && r.contains(evt.getPoint())) {
-                    return img;
-                }
-            }
-        }
-        return null;
+        int idx = getPhotoIdxUnderMouse(evt);
+        if (idx >= 0) {
+            return data.get(idx);
+        } else {
+            return null;
+        }
     }
 
@@ -849,4 +942,5 @@
                 if (isVisible() && isMapModeOk()) {
                     Main.map.mapView.repaint();
+                    cycleModeArmed = true;
                 }
             }
@@ -859,31 +953,25 @@
                     return;
 
-                for (int i = data.size() - 1; i >= 0; --i) {
-                    ImageEntry e = data.get(i);
-                    if (e.getPos() == null) {
-                        continue;
-                    }
-                    Point p = Main.map.mapView.getPoint(e.getPos());
-                    Rectangle r;
-                    if (useThumbs && e.hasThumbnail()) {
-                        Dimension d = scaledDimension(e.getThumbnail());
-                        if (d != null)
-                            r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
-                        else
-                            r = null;
-                    } else {
-                        r = new Rectangle(p.x - icon.getIconWidth() / 2,
-                                p.y - icon.getIconHeight() / 2,
-                                icon.getIconWidth(),
-                                icon.getIconHeight());
-                    }
-                    if (r != null && r.contains(ev.getPoint())) {
-                        clearOtherCurrentPhotos();
-                        currentPhoto = i;
-                        ImageViewerDialog.showImage(GeoImageLayer.this, e);
-                        Main.map.repaint();
-                        break;
-                    }
-                }
+                Point mousePos = ev.getPoint();
+                boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
+                int idx = getPhotoIdxUnderMouse(ev, cycle);
+                if (idx >= 0) {
+                    lastSelPos = mousePos;
+                    cycleModeArmed = false;
+                    currentPhoto = idx;
+                    showCurrentPhoto();
+                }
+            }
+        };
+
+        mouseMotionAdapter = new MouseMotionAdapter() {
+            @Override
+            public void mouseMoved(MouseEvent evt) {
+                lastSelPos = null;
+            }
+
+            @Override
+            public void mouseDragged(MouseEvent evt) {
+                lastSelPos = null;
             }
         };
@@ -892,6 +980,8 @@
             if (newMapMode == null || isSupportedMapMode(newMapMode)) {
                 Main.map.mapView.addMouseListener(mouseAdapter);
+                Main.map.mapView.addMouseMotionListener(mouseMotionAdapter);
             } else {
                 Main.map.mapView.removeMouseListener(mouseAdapter);
+                Main.map.mapView.removeMouseMotionListener(mouseMotionAdapter);
             }
         };
@@ -918,4 +1008,5 @@
                     stopLoadThumbs();
                     Main.map.mapView.removeMouseListener(mouseAdapter);
+                    Main.map.mapView.removeMouseMotionListener(mouseMotionAdapter);
                     MapFrame.removeMapModeChangeListener(mapModeListener);
                     currentPhoto = -1;
