Index: src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(revision 15245)
+++ src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(working copy)
@@ -539,7 +539,7 @@
             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;
Index: src/org/openstreetmap/josm/data/ImageData.java
===================================================================
--- src/org/openstreetmap/josm/data/ImageData.java	(revision 15245)
+++ src/org/openstreetmap/josm/data/ImageData.java	(working copy)
@@ -1,9 +1,12 @@
 // 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;
+import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
@@ -33,7 +36,7 @@
 
     private final List<ImageEntry> data;
 
-    private int selectedImageIndex = -1;
+    private final List<Integer> selectedImagesIndex = new ArrayList<>();
 
     private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
 
@@ -55,6 +58,7 @@
         } else {
             this.data = new ArrayList<>();
         }
+        selectedImagesIndex.add(-1);
     }
 
     /**
@@ -106,10 +110,12 @@
     }
 
     /**
-     * Return the current selected image
-     * @return the selected image as {@link ImageEntry} or null
+     * Return the first currently selected image
+     * @return the first selected image as {@link ImageEntry} or null
+     * @see #getSelectedImages
      */
     public ImageEntry getSelectedImage() {
+        int selectedImageIndex = selectedImagesIndex.isEmpty() ? -1 : selectedImagesIndex.get(0);
         if (selectedImageIndex > -1) {
             return data.get(selectedImageIndex);
         }
@@ -117,6 +123,15 @@
     }
 
     /**
+     * Return the current selected images
+     * @return the selected images as list {@link ImageEntry}
+     * @since xxx
+     */
+    public List<ImageEntry> getSelectedImages() {
+        return selectedImagesIndex.stream().filter(i -> i > -1).map(data::get).collect(Collectors.toList());
+    }
+
+    /**
      * Select the first image of the sequence
      */
     public void selectFirstImage() {
@@ -137,7 +152,7 @@
      * @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 +160,7 @@
      */
     public void selectNextImage() {
         if (hasNextImage()) {
-            setSelectedImageIndex(selectedImageIndex + 1);
+            setSelectedImageIndex(selectedImagesIndex.get(0) + 1);
         }
     }
 
@@ -154,7 +169,7 @@
      * @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 +179,7 @@
         if (data.isEmpty()) {
             return;
         }
-        setSelectedImageIndex(Integer.max(0, selectedImageIndex - 1));
+        setSelectedImageIndex(Integer.max(0, selectedImagesIndex.get(0) - 1));
     }
 
     /**
@@ -176,7 +191,36 @@
     }
 
     /**
-     * Clear the selected image
+     * Add image to the list of selected images
+     * @param image {@link ImageEntry} the image to add
+     * @since xxx
+     */
+    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.selectedImageChanged(this));
+        }
+    }
+
+    /**
+     * Remove the image from the list of selected images
+     * @param image {@link ImageEntry} the image to remove
+     * @since xxx
+     */
+    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.selectedImageChanged(this));
+    }
+
+    /**
+     * Clear the selected image(s)
      */
     public void clearSelectedImage() {
         setSelectedImageIndex(-1);
@@ -187,10 +231,14 @@
     }
 
     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;
+        selectedImagesIndex.set(0, index);
         listeners.fireEvent(l -> l.selectedImageChanged(this));
     }
 
@@ -198,15 +246,33 @@
      * 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
+     * @since xxx
+     */
+    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
      */
Index: src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 15245)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(working copy)
@@ -98,6 +98,8 @@
 
     /** 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 @@
         this.data.addImageDataUpdateListener(this);
     }
 
+    private final class ImageMouseListener 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 @@
                 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 @@
             }
         }
 
-        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;
+                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 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 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 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;
+                    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);
-            }
+                    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);
+                }
             }
         }
     }
@@ -742,39 +808,7 @@
 
     @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 ImageMouseListener();
 
         mouseMotionAdapter = new MouseMotionAdapter() {
             @Override
Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 15245)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(working copy)
@@ -66,6 +66,7 @@
 
     private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener();
 
+    private String emptyText;
     private String osdText;
 
     private static final BooleanProperty AGPIFO_STYLE =
@@ -698,6 +699,16 @@
     }
 
     /**
+     * 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 +740,10 @@
         Dimension size = getSize();
         if (entry == null) {
             g.setColor(Color.black);
-            String noImageStr = tr("No image");
+            if (emptyText == null) {
+                emptyText = tr("No image");
+            }
+            String noImageStr = emptyText;
             Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
             g.drawString(noImageStr,
                     (int) ((size.width - noImageSize.getWidth()) / 2),
Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 15245)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(working copy)
@@ -13,6 +13,8 @@
 import java.awt.event.WindowEvent;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
 import javax.swing.Box;
@@ -376,7 +378,9 @@
      * Displays image for the given data.
      * @param data geo image data
      * @param entry image entry
+     * @deprecated Use {@link #displayImage}
      */
+    @Deprecated
     public static void showImage(ImageData data, ImageEntry entry) {
         getInstance().displayImage(data, entry);
     }
@@ -416,12 +420,24 @@
     private transient ImageEntry currentEntry;
 
     /**
-     * Displays image for the given layer.
+     * Displays a single image for the given layer.
      * @param data the image data
      * @param entry image entry
+     * @see #displayImages
      */
     public void displayImage(ImageData data, ImageEntry entry) {
+        displayImages(data, Collections.singletonList(entry));
+    }
+
+    /**
+     * Displays images for the given layer.
+     * @param data the image data
+     * @param entries image entries
+     * @since xxx
+     */
+    public void displayImages(ImageData data, List<ImageEntry> entries) {
         boolean imageChanged;
+        ImageEntry entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
 
         synchronized (this) {
             // TODO: pop up image dialog but don't load image again
@@ -490,6 +506,13 @@
             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()) {
@@ -503,6 +526,14 @@
         }
     }
 
+    private static boolean isLastImageSelected(ImageData data) {
+        return data.isImageSelected(data.getImages().get(data.getImages().size() - 1));
+    }
+
+    private static 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.
@@ -561,7 +592,7 @@
         if (e.getRemovedLayer() instanceof GeoImageLayer) {
             ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData();
             if (removedData == currentData) {
-                displayImage(null, null);
+                displayImages(null, null);
             }
             removedData.removeImageDataUpdateListener(this);
         }
@@ -591,11 +622,11 @@
 
     @Override
     public void selectedImageChanged(ImageData data) {
-        showImage(data, data.getSelectedImage());
+        displayImages(data, data.getSelectedImages());
     }
 
     @Override
     public void imageDataUpdated(ImageData data) {
-        showImage(data, data.getSelectedImage());
+        displayImages(data, data.getSelectedImages());
     }
 }
Index: test/unit/org/openstreetmap/josm/data/ImageDataTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(revision 15245)
+++ test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(working copy)
@@ -32,7 +32,7 @@
     }
 
     @Test
-    public void testWithullData() {
+    public void testWithNullData() {
         ImageData data = new ImageData();
         assertEquals(0, data.getImages().size());
         assertNull(data.getSelectedImage());
@@ -87,7 +87,8 @@
 
         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 +98,8 @@
 
         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 +109,11 @@
         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 +126,10 @@
         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 +138,18 @@
 
         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());
+        assertTrue(data.getSelectedImages().isEmpty());
     }
 
     @Test
@@ -173,7 +178,7 @@
         data.selectFirstImage();
         data.removeSelectedImage();
         assertEquals(0, data.getImages().size());
-        assertNull(data.getSelectedImage());
+        assertEquals(0, data.getSelectedImages().size());
     }
 
     @Test
@@ -237,7 +242,8 @@
 
         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 +259,92 @@
 
         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.clearSelectedImage();
+        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 selectedImageChanged(ImageData data) {}
+
+            @Override
+            public void imageDataUpdated(ImageData data) {}
+        };
+        new Expectations(listener) {{
+            listener.selectedImageChanged(data); times = 3;
+        }};
+        data.addImageDataUpdateListener(listener);
+        data.selectFirstImage();
+        data.addImageToSelection(list.get(1));
+        data.removeImageToSelection(list.get(0));
     }
 
     @Test
