diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
index 48873a20c..e749ed44c 100644
--- a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
+++ b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
@@ -539,7 +539,7 @@ public class SelectAction extends MapMode implements ModifierExListener, KeyPres
             int dp = (int) lastMousePos.distance(e.getX(), e.getY());
             if (dp < initialMoveThreshold)
                 return; // ignore small drags
-            initialMoveThresholdExceeded = true; //no more ingnoring uintil nex mouse press
+            initialMoveThresholdExceeded = true; //no more ignoring until next mouse press
         }
         if (e.getPoint().equals(lastMousePos))
             return;
diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java
index f84bf3ed7..00a65420f 100644
--- a/src/org/openstreetmap/josm/data/ImageData.java
+++ b/src/org/openstreetmap/josm/data/ImageData.java
@@ -1,6 +1,8 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data;
 
+import static org.openstreetmap.josm.tools.I18n.tr;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -28,12 +30,12 @@ public class ImageData {
          * Called when the selection change
          * @param data the image data
          */
-        void selectedImageChanged(ImageData data);
+        void selectedImagesChanged(ImageData data);
     }
 
     private final List<ImageEntry> data;
 
-    private int selectedImageIndex = -1;
+    private List<Integer> selectedImagesIndex = new ArrayList<>();
 
     private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
 
@@ -55,6 +57,7 @@ public class ImageData {
         } else {
             this.data = new ArrayList<>();
         }
+        selectedImagesIndex.add(-1);
     }
 
     /**
@@ -86,8 +89,6 @@ public class ImageData {
         data.addAll(otherData.getImages());
         Collections.sort(data);
 
-        final ImageEntry selected = otherData.getSelectedImage();
-
         // Suppress the double photos.
         if (data.size() > 1) {
             ImageEntry prev = data.get(data.size() - 1);
@@ -100,20 +101,25 @@ public class ImageData {
                 }
             }
         }
-        if (selected != null) {
-            setSelectedImageIndex(data.indexOf(selected));
+
+        final List<ImageEntry> selected = otherData.getSelectedImages();
+        if (!selected.isEmpty()) {
+            setSelectedImageIndex(data.indexOf(selected.get(0)));
         }
     }
 
     /**
-     * Return the current selected image
-     * @return the selected image as {@link ImageEntry} or null
+     * Return the current selected images
+     * @return the selected images as list {@link ImageEntry}
      */
-    public ImageEntry getSelectedImage() {
-        if (selectedImageIndex > -1) {
-            return data.get(selectedImageIndex);
+    public List<ImageEntry> getSelectedImages() {
+        List<ImageEntry> selected = new ArrayList<>(selectedImagesIndex.size());
+        for (Integer i: selectedImagesIndex) {
+            if (i != -1) {
+                selected.add(data.get(i));
+            }
         }
-        return null;
+        return selected;
     }
 
     /**
@@ -137,7 +143,7 @@ public class ImageData {
      * @return {@code true} is there is a next image, {@code false} otherwise
      */
     public boolean hasNextImage() {
-        return selectedImageIndex != data.size() - 1;
+        return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) != data.size() - 1);
     }
 
     /**
@@ -145,7 +151,7 @@ public class ImageData {
      */
     public void selectNextImage() {
         if (hasNextImage()) {
-            setSelectedImageIndex(selectedImageIndex + 1);
+            setSelectedImageIndex(selectedImagesIndex.get(0) + 1);
         }
     }
 
@@ -154,7 +160,7 @@ public class ImageData {
      * @return {@code true} is there is a previous image, {@code false} otherwise
      */
     public boolean hasPreviousImage() {
-        return selectedImageIndex - 1 > -1;
+        return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) - 1 > -1);
     }
 
     /**
@@ -164,7 +170,7 @@ public class ImageData {
         if (data.isEmpty()) {
             return;
         }
-        setSelectedImageIndex(Integer.max(0, selectedImageIndex - 1));
+        setSelectedImageIndex(Integer.max(0, selectedImagesIndex.get(0) - 1));
     }
 
     /**
@@ -175,10 +181,37 @@ public class ImageData {
         setSelectedImageIndex(data.indexOf(image));
     }
 
+    /**
+     * Add image to the list of selected images
+     * @param image {@link ImageEntry} the image to add
+     */
+    public void addImageToSelection(ImageEntry image) {
+        int index = data.indexOf(image);
+        if (selectedImagesIndex.get(0) == -1) {
+            setSelectedImage(image);
+        } else if (!selectedImagesIndex.contains(index)) {
+            selectedImagesIndex.add(index);
+            listeners.fireEvent(l -> l.selectedImagesChanged(this));
+        }
+    }
+
+    /**
+     * Remove the image from the list of selected images
+     * @param image {@link ImageEntry} the image to remove
+     */
+    public void removeImageToSelection(ImageEntry image) {
+        int index = data.indexOf(image);
+        selectedImagesIndex.remove(selectedImagesIndex.indexOf(index));
+        if (selectedImagesIndex.isEmpty()) {
+            selectedImagesIndex.add(-1);
+        }
+        listeners.fireEvent(l -> l.selectedImagesChanged(this));
+    }
+
     /**
      * Clear the selected image
      */
-    public void clearSelectedImage() {
+    public void clearSelectedImages() {
         setSelectedImageIndex(-1);
     }
 
@@ -187,25 +220,46 @@ public class ImageData {
     }
 
     private void setSelectedImageIndex(int index, boolean forceTrigger) {
-        if (index == selectedImageIndex && !forceTrigger) {
+        if (selectedImagesIndex.size() > 1) {
+            selectedImagesIndex.clear();
+            selectedImagesIndex.add(-1);
+        }
+        if (index == selectedImagesIndex.get(0) && !forceTrigger) {
             return;
         }
-        selectedImageIndex = index;
-        listeners.fireEvent(l -> l.selectedImageChanged(this));
+        selectedImagesIndex.set(0, index);
+        listeners.fireEvent(l -> l.selectedImagesChanged(this));
     }
 
     /**
      * Remove the current selected image from the list
      */
     public void removeSelectedImage() {
-        data.remove(getSelectedImage());
-        if (selectedImageIndex == data.size()) {
+        List<ImageEntry> selected = getSelectedImages();
+        if (selected.size() > 1) {
+            throw new IllegalStateException(tr("Multiple images have been selected"));
+        }
+        if (selected.isEmpty()) {
+            return;
+        }
+        data.remove(getSelectedImages().get(0));
+        if (selectedImagesIndex.get(0) == data.size()) {
             setSelectedImageIndex(data.size() - 1);
         } else {
-            setSelectedImageIndex(selectedImageIndex, true);
+            setSelectedImageIndex(selectedImagesIndex.get(0), true);
         }
     }
 
+    /**
+     * Determines if the image is selected
+     * @param image the {@link ImageEntry} image
+     * @return {@code true} is the image is selected, {@code false} otherwise
+     */
+    public boolean isImageSelected(ImageEntry image) {
+        int index = data.indexOf(image);
+        return selectedImagesIndex.contains(index);
+    }
+
     /**
      * Remove the image from the list and trigger update listener
      * @param img the {@link ImageEntry} to remove
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
index e8ab7fbb9..e9f4710a7 100644
--- a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
+++ b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
@@ -98,6 +98,8 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
 
     /** Mouse position where the last image was selected. */
     private Point lastSelPos;
+    /** The mouse point */
+    private Point startPoint;
 
     /**
      * Image cycle mode flag.
@@ -154,6 +156,69 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
         this.data.addImageDataUpdateListener(this);
     }
 
+    private final class MouseListener extends MouseAdapter {
+        private boolean isMapModeOk() {
+            MapMode mapMode = MainApplication.getMap().mapMode;
+            return mapMode == null || isSupportedMapMode(mapMode);
+        }
+
+        @Override
+        public void mousePressed(MouseEvent e) {
+            if (e.getButton() != MouseEvent.BUTTON1)
+                return;
+            if (isVisible() && isMapModeOk()) {
+                cycleModeArmed = true;
+                invalidate();
+                startPoint = e.getPoint();
+            }
+        }
+
+        @Override
+        public void mouseReleased(MouseEvent ev) {
+            if (ev.getButton() != MouseEvent.BUTTON1)
+                return;
+            if (!isVisible() || !isMapModeOk())
+                return;
+            if (!cycleModeArmed) {
+                return;
+            }
+
+            Rectangle hitBoxClick = new Rectangle((int) startPoint.getX() - 10, (int) startPoint.getY() - 10, 15, 15);
+            if (!hitBoxClick.contains(ev.getPoint())) {
+                return;
+            }
+
+            Point mousePos = ev.getPoint();
+            boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
+            final boolean isShift = (ev.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK;
+            final boolean isCtrl = (ev.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == MouseEvent.CTRL_DOWN_MASK;
+            int idx = getPhotoIdxUnderMouse(ev, cycle);
+            if (idx >= 0) {
+                lastSelPos = mousePos;
+                cycleModeArmed = false;
+                ImageEntry img = data.getImages().get(idx);
+                if (isShift) {
+                    if (isCtrl && !data.getSelectedImages().isEmpty()) {
+                        int idx2 = data.getImages().indexOf(data.getSelectedImages().get(data.getSelectedImages().size() - 1));
+                        int startIndex = Math.min(idx, idx2);
+                        int endIndex = Math.max(idx, idx2);
+                        for (int i = startIndex; i <= endIndex; i++) {
+                            data.addImageToSelection(data.getImages().get(i));
+                        }
+                    } else {
+                        if (data.isImageSelected(img)) {
+                            data.removeImageToSelection(img);
+                        } else {
+                            data.addImageToSelection(img);
+                        }
+                    }
+                } else {
+                    data.setSelectedImage(img);
+                }
+            }
+        }
+    }
+
     /**
      * Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
      * In facts, this object is instantiated with a list of files. These files may be JPEG files or
@@ -496,9 +561,9 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
                 for (ImageEntry e : data.getImages()) {
                     paintImage(e, mv, clip, tempG);
                 }
-                if (data.getSelectedImage() != null) {
+                for (ImageEntry img: this.data.getSelectedImages()) {
                     // Make sure the selected image is on top in case multiple images overlap.
-                    paintImage(data.getSelectedImage(), mv, clip, tempG);
+                    paintImage(img, mv, clip, tempG);
                 }
                 updateOffscreenBuffer = false;
             }
@@ -515,63 +580,64 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
             }
         }
 
-        ImageEntry e = data.getSelectedImage();
-        if (e != null && e.getPos() != null) {
-            Point p = mv.getPoint(e.getPos());
+        for (ImageEntry e: data.getSelectedImages()) {
+            if (e != null && e.getPos() != null) {
+                Point p = mv.getPoint(e.getPos());
 
-            int imgWidth;
-            int imgHeight;
-            if (useThumbs && e.hasThumbnail()) {
-                Dimension d = scaledDimension(e.getThumbnail());
-                if (d != null) {
-                    imgWidth = d.width;
-                    imgHeight = d.height;
+                int imgWidth;
+                int imgHeight;
+                if (useThumbs && e.hasThumbnail()) {
+                    Dimension d = scaledDimension(e.getThumbnail());
+                    if (d != null) {
+                        imgWidth = d.width;
+                        imgHeight = d.height;
+                    } else {
+                        imgWidth = -1;
+                        imgHeight = -1;
+                    }
                 } else {
-                    imgWidth = -1;
-                    imgHeight = -1;
+                    imgWidth = selectedIcon.getIconWidth();
+                    imgHeight = selectedIcon.getIconHeight();
                 }
-            } else {
-                imgWidth = selectedIcon.getIconWidth();
-                imgHeight = selectedIcon.getIconHeight();
-            }
-
-            if (e.getExifImgDir() != null) {
-                // Multiplier must be larger than sqrt(2)/2=0.71.
-                double arrowlength = Math.max(25, Math.max(imgWidth, imgHeight) * 0.85);
-                double arrowwidth = arrowlength / 1.4;
-
-                double dir = e.getExifImgDir();
-                // Rotate 90 degrees CCW
-                double headdir = (dir < 90) ? dir + 270 : dir - 90;
-                double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90;
-                double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90;
 
-                double ptx = p.x + Math.cos(Utils.toRadians(headdir)) * arrowlength;
-                double pty = p.y + Math.sin(Utils.toRadians(headdir)) * arrowlength;
-
-                double ltx = p.x + Math.cos(Utils.toRadians(leftdir)) * arrowwidth/2;
-                double lty = p.y + Math.sin(Utils.toRadians(leftdir)) * arrowwidth/2;
-
-                double rtx = p.x + Math.cos(Utils.toRadians(rightdir)) * arrowwidth/2;
-                double rty = p.y + Math.sin(Utils.toRadians(rightdir)) * arrowwidth/2;
-
-                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-                g.setColor(new Color(255, 255, 255, 192));
-                int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
-                int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
-                g.fillPolygon(xar, yar, 4);
-                g.setColor(Color.black);
-                g.setStroke(new BasicStroke(1.2f));
-                g.drawPolyline(xar, yar, 3);
-            }
+                if (e.getExifImgDir() != null) {
+                    // Multiplier must be larger than sqrt(2)/2=0.71.
+                    double arrowlength = Math.max(25, Math.max(imgWidth, imgHeight) * 0.85);
+                    double arrowwidth = arrowlength / 1.4;
+
+                    double dir = e.getExifImgDir();
+                    // Rotate 90 degrees CCW
+                    double headdir = (dir < 90) ? dir + 270 : dir - 90;
+                    double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90;
+                    double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90;
+
+                    double ptx = p.x + Math.cos(Utils.toRadians(headdir)) * arrowlength;
+                    double pty = p.y + Math.sin(Utils.toRadians(headdir)) * arrowlength;
+
+                    double ltx = p.x + Math.cos(Utils.toRadians(leftdir)) * arrowwidth/2;
+                    double lty = p.y + Math.sin(Utils.toRadians(leftdir)) * arrowwidth/2;
+
+                    double rtx = p.x + Math.cos(Utils.toRadians(rightdir)) * arrowwidth/2;
+                    double rty = p.y + Math.sin(Utils.toRadians(rightdir)) * arrowwidth/2;
+
+                    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                    g.setColor(new Color(255, 255, 255, 192));
+                    int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
+                    int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
+                    g.fillPolygon(xar, yar, 4);
+                    g.setColor(Color.black);
+                    g.setStroke(new BasicStroke(1.2f));
+                    g.drawPolyline(xar, yar, 3);
+                }
 
-            if (useThumbs && e.hasThumbnail()) {
-                g.setColor(new Color(128, 0, 0, 122));
-                g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight);
-            } else {
-                selectedIcon.paintIcon(mv, g,
-                        p.x - imgWidth / 2,
-                        p.y - imgHeight / 2);
+                if (useThumbs && e.hasThumbnail()) {
+                    g.setColor(new Color(128, 0, 0, 122));
+                    g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight);
+                } else {
+                    selectedIcon.paintIcon(mv, g,
+                            p.x - imgWidth / 2,
+                            p.y - imgHeight / 2);
+                }
             }
         }
     }
@@ -587,7 +653,7 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
      * Show current photo on map and in image viewer.
      */
     public void showCurrentPhoto() {
-        if (data.getSelectedImage() != null) {
+        if (!data.getSelectedImages().isEmpty()) {
             clearOtherCurrentPhotos();
         }
         updateBufferAndRepaint();
@@ -635,8 +701,8 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
      *               or {@code -1} if there is no image at the mouse position
      */
     private int getPhotoIdxUnderMouse(MouseEvent evt, boolean cycle) {
-        ImageEntry selectedImage = data.getSelectedImage();
-        int selectedIndex = data.getImages().indexOf(selectedImage);
+        ImageEntry selectedImage = this.data.getSelectedImages().isEmpty() ? null : this.data.getSelectedImages().get(0);
+        int selectedIndex = this.data.getImages().indexOf(selectedImage);
 
         if (cycle && selectedImage != null) {
             // Cycle loop is forward as that is the natural order.
@@ -701,7 +767,7 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
         for (GeoImageLayer layer:
                  MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) {
             if (layer != this) {
-                layer.getImageData().clearSelectedImage();
+                layer.getImageData().clearSelectedImages();
             }
         }
     }
@@ -742,39 +808,7 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
 
     @Override
     public void hookUpMapView() {
-        mouseAdapter = new MouseAdapter() {
-            private boolean isMapModeOk() {
-                MapMode mapMode = MainApplication.getMap().mapMode;
-                return mapMode == null || isSupportedMapMode(mapMode);
-            }
-
-            @Override
-            public void mousePressed(MouseEvent e) {
-                if (e.getButton() != MouseEvent.BUTTON1)
-                    return;
-                if (isVisible() && isMapModeOk()) {
-                    cycleModeArmed = true;
-                    invalidate();
-                }
-            }
-
-            @Override
-            public void mouseReleased(MouseEvent ev) {
-                if (ev.getButton() != MouseEvent.BUTTON1)
-                    return;
-                if (!isVisible() || !isMapModeOk())
-                    return;
-
-                Point mousePos = ev.getPoint();
-                boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
-                int idx = getPhotoIdxUnderMouse(ev, cycle);
-                if (idx >= 0) {
-                    lastSelPos = mousePos;
-                    cycleModeArmed = false;
-                    data.setSelectedImage(data.getImages().get(idx));
-                }
-            }
-        };
+        mouseAdapter = new MouseListener();
 
         mouseMotionAdapter = new MouseMotionAdapter() {
             @Override
@@ -968,7 +1002,7 @@ public class GeoImageLayer extends AbstractModifiableLayer implements
     }
 
     @Override
-    public void selectedImageChanged(ImageData data) {
+    public void selectedImagesChanged(ImageData data) {
         showCurrentPhoto();
     }
 
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
index cba56207b..80982233f 100644
--- a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
+++ b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
@@ -66,6 +66,8 @@ public class ImageDisplay extends JComponent implements Destroyable, PreferenceC
 
     private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener();
 
+    private String emptyText;
+
     private String osdText;
 
     private static final BooleanProperty AGPIFO_STYLE =
@@ -697,6 +699,16 @@ public class ImageDisplay extends JComponent implements Destroyable, PreferenceC
         }
     }
 
+    /**
+     * Set the message displayed when there is no image to display
+     * By default it display a simple No image
+     * @param emptyText the string to display
+     * @since XXX
+     */
+    public void setEmptyText(String emptyText) {
+        this.emptyText = emptyText;
+    }
+
     /**
      * Sets the On-Screen-Display text.
      * @param text text to display on top of the image
@@ -729,7 +741,10 @@ public class ImageDisplay extends JComponent implements Destroyable, PreferenceC
         Dimension size = getSize();
         if (entry == null) {
             g.setColor(Color.black);
-            String noImageStr = tr("No image");
+            if (this.emptyText == null) {
+                this.emptyText = tr("No image");
+            }
+            String noImageStr = this.emptyText;
             Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
             g.drawString(noImageStr,
                     (int) ((size.width - noImageSize.getWidth()) / 2),
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
index aa3c74fe2..837036aac 100644
--- a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
+++ b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
@@ -13,6 +13,7 @@ import java.awt.event.KeyEvent;
 import java.awt.event.WindowEvent;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.List;
 
 import javax.swing.Box;
 import javax.swing.JButton;
@@ -308,8 +309,8 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
 
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentData != null && currentData.getSelectedImage() != null) {
-                ImageEntry toDelete = currentData.getSelectedImage();
+            if (currentData != null && !currentData.getSelectedImages().isEmpty()) {
+                ImageEntry toDelete = currentData.getSelectedImages().get(0);
 
                 int result = new ExtendedDialog(
                         MainApplication.getMainFrame(),
@@ -353,7 +354,7 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
         @Override
         public void actionPerformed(ActionEvent e) {
             if (currentData != null) {
-                ClipboardUtils.copyString(currentData.getSelectedImage().getFile().toString());
+                ClipboardUtils.copyString(currentData.getSelectedImages().get(0).getFile().toString());
             }
         }
     }
@@ -371,15 +372,6 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
         }
     }
 
-    /**
-     * Displays image for the given data.
-     * @param data geo image data
-     * @param entry image entry
-     */
-    public static void showImage(ImageData data, ImageEntry entry) {
-        getInstance().displayImage(data, entry);
-    }
-
     /**
      * Enables (or disables) the "Previous" button.
      * @param value {@code true} to enable the button, {@code false} otherwise
@@ -417,10 +409,15 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
     /**
      * Displays image for the given layer.
      * @param data the image data
-     * @param entry image entry
+     * @param entries the list of {@link ImageEntry}
      */
-    public void displayImage(ImageData data, ImageEntry entry) {
+    public void displayImage(ImageData data, List<ImageEntry> entries) {
         boolean imageChanged;
+        ImageEntry entry = null;
+
+        if (entries != null && entries.size() == 1) {
+            entry = entries.get(0);
+        }
 
         synchronized (this) {
             // TODO: pop up image dialog but don't load image again
@@ -478,13 +475,19 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
             // if this method is called to reinitialize dialog content with a blank image,
             // do not actually show the dialog again with a blank image if currently hidden (fix #10672)
             setTitle(tr("Geotagged Images"));
-            imgDisplay.setImage(null);
-            imgDisplay.setOsdText("");
+            imgDisplay.setEmptyText(null);
             setNextEnabled(false);
             setPreviousEnabled(false);
             btnDelete.setEnabled(false);
             btnDeleteFromDisk.setEnabled(false);
             btnCopyPath.setEnabled(false);
+            if (entries != null && entries.size() > 1) {
+                imgDisplay.setEmptyText(tr("Multiple images selected"));
+                btnFirst.setEnabled(!isFirstImageSelected(data));
+                btnLast.setEnabled(!isLastImageSelected(data));
+            }
+            imgDisplay.setImage(null);
+            imgDisplay.setOsdText("");
             return;
         }
         if (!isDialogShowing()) {
@@ -498,6 +501,14 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
         }
     }
 
+    private boolean isLastImageSelected(ImageData data) {
+        return data.isImageSelected(data.getImages().get(data.getImages().size() - 1));
+    }
+
+    private boolean isFirstImageSelected(ImageData data) {
+        return data.isImageSelected(data.getImages().get(0));
+    }
+
     /**
      * When an image is closed, really close it and do not pop
      * up the side dialog.
@@ -585,12 +596,12 @@ public final class ImageViewerDialog extends ToggleDialog implements LayerChange
     }
 
     @Override
-    public void selectedImageChanged(ImageData data) {
-        showImage(data, data.getSelectedImage());
+    public void selectedImagesChanged(ImageData data) {
+        displayImage(data, data.getSelectedImages());
     }
 
     @Override
     public void imageDataUpdated(ImageData data) {
-        showImage(data, data.getSelectedImage());
+        displayImage(data, data.getSelectedImages());
     }
 }
diff --git a/test/unit/org/openstreetmap/josm/data/ImageDataTest.java b/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
index 9fcc7c5e9..2f1a6f90c 100644
--- a/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
+++ b/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
@@ -3,7 +3,6 @@ package org.openstreetmap.josm.data;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -32,18 +31,18 @@ public class ImageDataTest {
     }
 
     @Test
-    public void testWithullData() {
+    public void testWithNullData() {
         ImageData data = new ImageData();
         assertEquals(0, data.getImages().size());
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
         data.selectFirstImage();
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
         data.selectLastImage();
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
         data.selectFirstImage();
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
         data.selectPreviousImage();
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
         assertFalse(data.hasNextImage());
         assertFalse(data.hasPreviousImage());
         data.removeSelectedImage();
@@ -87,7 +86,8 @@ public class ImageDataTest {
 
         ImageData data = new ImageData(list);
         data.selectFirstImage();
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
     }
 
     @Test
@@ -97,7 +97,8 @@ public class ImageDataTest {
 
         ImageData data = new ImageData(list);
         data.selectLastImage();
-        assertEquals(list.get(1), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list.get(1), data.getSelectedImages().get(0));
     }
 
     @Test
@@ -107,10 +108,11 @@ public class ImageDataTest {
         ImageData data = new ImageData(list);
         assertTrue(data.hasNextImage());
         data.selectNextImage();
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
         assertFalse(data.hasNextImage());
         data.selectNextImage();
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
     }
 
     @Test
@@ -123,9 +125,10 @@ public class ImageDataTest {
         data.selectLastImage();
         assertTrue(data.hasPreviousImage());
         data.selectPreviousImage();
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
         data.selectPreviousImage();
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
     }
 
     @Test
@@ -134,17 +137,18 @@ public class ImageDataTest {
 
         ImageData data = new ImageData(list);
         data.setSelectedImage(list.get(0));
-        assertEquals(list.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list.get(0), data.getSelectedImages().get(0));
     }
 
     @Test
-    public void testClearSelectedImage() {
+    public void testClearSelectedImages() {
         List<ImageEntry> list = getOneImage();
 
         ImageData data = new ImageData(list);
         data.setSelectedImage(list.get(0));
-        data.clearSelectedImage();
-        assertNull(data.getSelectedImage());
+        data.clearSelectedImages();
+        assertTrue(data.getSelectedImages().isEmpty());
     }
 
     @Test
@@ -153,13 +157,13 @@ public class ImageDataTest {
         ImageData data = new ImageData(list);
         ImageDataUpdateListener listener = new ImageDataUpdateListener() {
             @Override
-            public void selectedImageChanged(ImageData data) {}
+            public void selectedImagesChanged(ImageData data) {}
 
             @Override
             public void imageDataUpdated(ImageData data) {}
         };
         new Expectations(listener) {{
-            listener.selectedImageChanged(data); times = 1;
+            listener.selectedImagesChanged(data); times = 1;
         }};
         data.addImageDataUpdateListener(listener);
         data.selectFirstImage();
@@ -173,7 +177,7 @@ public class ImageDataTest {
         data.selectFirstImage();
         data.removeSelectedImage();
         assertEquals(0, data.getImages().size());
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
     }
 
     @Test
@@ -183,13 +187,13 @@ public class ImageDataTest {
         ImageData data = new ImageData(list);
         ImageDataUpdateListener listener = new ImageDataUpdateListener() {
             @Override
-            public void selectedImageChanged(ImageData data) {}
+            public void selectedImagesChanged(ImageData data) {}
 
             @Override
             public void imageDataUpdated(ImageData data) {}
         };
         new Expectations(listener) {{
-            listener.selectedImageChanged(data); times = 2;
+            listener.selectedImagesChanged(data); times = 2;
         }};
         data.addImageDataUpdateListener(listener);
         data.selectFirstImage();
@@ -202,7 +206,7 @@ public class ImageDataTest {
         ImageData data = new ImageData(list);
         ImageDataUpdateListener listener = new ImageDataUpdateListener() {
             @Override
-            public void selectedImageChanged(ImageData data) {}
+            public void selectedImagesChanged(ImageData data) {}
 
             @Override
             public void imageDataUpdated(ImageData data) {}
@@ -237,7 +241,8 @@ public class ImageDataTest {
 
         data.mergeFrom(data2);
         assertEquals(3, data.getImages().size());
-        assertEquals(list1.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list1.get(0), data.getSelectedImages().get(0));
     }
 
     @Test
@@ -253,7 +258,92 @@ public class ImageDataTest {
 
         data.mergeFrom(data2);
         assertEquals(3, data.getImages().size());
-        assertEquals(list2.get(0), data.getSelectedImage());
+        assertEquals(1, data.getSelectedImages().size());
+        assertEquals(list2.get(0), data.getSelectedImages().get(0));
+    }
+
+    @Test
+    public void testAddImageToSelection() {
+        List<ImageEntry> list = getOneImage();
+        list.add(new ImageEntry(new File("test2")));
+
+        ImageData data = new ImageData(list);
+        data.addImageToSelection(list.get(0));
+        data.addImageToSelection(list.get(0));
+        assertEquals(1, data.getSelectedImages().size());
+        data.addImageToSelection(list.get(1));
+        assertEquals(2, data.getSelectedImages().size());
+    }
+
+    @Test
+    public void testRemoveImageToSelection() {
+        List<ImageEntry> list = getOneImage();
+        list.add(new ImageEntry());
+
+        ImageData data = new ImageData(list);
+        data.selectLastImage();
+        data.removeImageToSelection(list.get(1));
+        assertEquals(0, data.getSelectedImages().size());
+        data.selectFirstImage();
+        assertEquals(1, data.getSelectedImages().size());
+
+    }
+
+    @Test
+    public void testIsSelected() {
+        List<ImageEntry> list = getOneImage();
+        list.add(new ImageEntry(new File("test2")));
+
+        ImageData data = new ImageData(list);
+        assertFalse(data.isImageSelected(list.get(0)));
+        data.selectFirstImage();
+        assertTrue(data.isImageSelected(list.get(0)));
+        data.addImageToSelection(list.get(1));
+        assertTrue(data.isImageSelected(list.get(0)));
+        assertTrue(data.isImageSelected(list.get(1)));
+        assertFalse(data.isImageSelected(new ImageEntry()));
+    }
+
+    @Test
+    public void testActionsWithMultipleImagesSelected() {
+        List<ImageEntry> list = this.getOneImage();
+        list.add(new ImageEntry(new File("test2")));
+        list.add(new ImageEntry(new File("test3")));
+        list.add(new ImageEntry(new File("test3")));
+
+        ImageData data = new ImageData(list);
+        data.addImageToSelection(list.get(1));
+        data.addImageToSelection(list.get(2));
+
+        assertFalse(data.hasNextImage());
+        assertFalse(data.hasPreviousImage());
+
+        data.clearSelectedImages();
+        assertEquals(0, data.getSelectedImages().size());
+        data.addImageToSelection(list.get(1));
+        data.selectFirstImage();
+        assertEquals(1, data.getSelectedImages().size());
+    }
+
+    @Test
+    public void testTriggerListenerWhenNewImageIsSelectedAndRemoved() {
+        List<ImageEntry> list = this.getOneImage();
+        list.add(new ImageEntry());
+        ImageData data = new ImageData(list);
+        ImageDataUpdateListener listener = new ImageDataUpdateListener() {
+            @Override
+            public void selectedImagesChanged(ImageData data) {}
+
+            @Override
+            public void imageDataUpdated(ImageData data) {}
+        };
+        new Expectations(listener) {{
+            listener.selectedImagesChanged(data); times = 3;
+        }};
+        data.addImageDataUpdateListener(listener);
+        data.selectFirstImage();
+        data.addImageToSelection(list.get(1));
+        data.removeImageToSelection(list.get(0));
     }
 
     @Test
@@ -287,7 +377,7 @@ public class ImageDataTest {
 
         ImageDataUpdateListener listener = new ImageDataUpdateListener() {
             @Override
-            public void selectedImageChanged(ImageData data) {}
+            public void selectedImagesChanged(ImageData data) {}
 
             @Override
             public void imageDataUpdated(ImageData data) {}
@@ -307,7 +397,7 @@ public class ImageDataTest {
 
         ImageDataUpdateListener listener = new ImageDataUpdateListener() {
             @Override
-            public void selectedImageChanged(ImageData data) {}
+            public void selectedImagesChanged(ImageData data) {}
 
             @Override
             public void imageDataUpdated(ImageData data) {}
