Index: /trunk/src/org/openstreetmap/josm/data/ImageData.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/ImageData.java	(revision 14590)
+++ /trunk/src/org/openstreetmap/josm/data/ImageData.java	(revision 14590)
@@ -0,0 +1,265 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
+import org.openstreetmap.josm.tools.ListenerList;
+
+/**
+ * Class to hold {@link ImageEntry} and the current selection
+ * @since 14590
+ */
+public class ImageData {
+    /**
+     * A listener that is informed when the current selection change
+     */
+    public interface ImageDataUpdateListener {
+        /**
+         * Called when the data change
+         * @param data the image data
+         */
+        void imageDataUpdated(ImageData data);
+
+        /**
+         * Called when the selection change
+         * @param data the image data
+         */
+        void selectedImageChanged(ImageData data);
+    }
+
+    private final List<ImageEntry> data;
+
+    private int selectedImageIndex = -1;
+
+    private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
+
+    /**
+     * Construct a new image container without images
+     */
+    public ImageData() {
+        this(null);
+    }
+
+    /**
+     * Construct a new image container with a list of images
+     * @param data the list of {@link ImageEntry}
+     */
+    public ImageData(List<ImageEntry> data) {
+        if (data != null) {
+            Collections.sort(data);
+            this.data = data;
+        } else {
+            this.data = new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns the images
+     * @return the images
+     */
+    public List<ImageEntry> getImages() {
+        return data;
+    }
+
+    /**
+     * Determines if one image has modified GPS data.
+     * @return {@code true} if data has been modified; {@code false}, otherwise
+     */
+    public boolean isModified() {
+        for (ImageEntry e : data) {
+            if (e.hasNewGpsData()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Merge 2 ImageData
+     * @param otherData {@link ImageData} to merge
+     */
+    public void mergeFrom(ImageData otherData) {
+        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);
+            for (int i = data.size() - 2; i >= 0; i--) {
+                ImageEntry cur = data.get(i);
+                if (cur.getFile().equals(prev.getFile())) {
+                    data.remove(i);
+                } else {
+                    prev = cur;
+                }
+            }
+        }
+        if (selected != null) {
+            setSelectedImageIndex(data.indexOf(selected));
+        }
+    }
+
+    /**
+     * Return the current selected image
+     * @return the selected image as {@link ImageEntry} or null
+     */
+    public ImageEntry getSelectedImage() {
+        if (selectedImageIndex > -1) {
+            return data.get(selectedImageIndex);
+        }
+        return null;
+    }
+
+    /**
+     * Select the first image of the sequence
+     */
+    public void selectFirstImage() {
+        if (!data.isEmpty()) {
+            setSelectedImageIndex(0);
+        }
+    }
+
+    /**
+     * Select the last image of the sequence
+     */
+    public void selectLastImage() {
+        setSelectedImageIndex(data.size() - 1);
+    }
+
+    /**
+     * Check if there is a next image in the sequence
+     * @return {@code true} is there is a next image, {@code false} otherwise
+     */
+    public boolean hasNextImage() {
+        return selectedImageIndex != data.size() - 1;
+    }
+
+    /**
+     * Select the next image of the sequence
+     */
+    public void selectNextImage() {
+        if (hasNextImage()) {
+            setSelectedImageIndex(selectedImageIndex + 1);
+        }
+    }
+
+    /**
+     *  Check if there is a previous image in the sequence
+     * @return {@code true} is there is a previous image, {@code false} otherwise
+     */
+    public boolean hasPreviousImage() {
+        return selectedImageIndex - 1 > -1;
+    }
+
+    /**
+     * Select the previous image of the sequence
+     */
+    public void selectPreviousImage() {
+        if (data.isEmpty()) {
+            return;
+        }
+        setSelectedImageIndex(Integer.max(0, selectedImageIndex - 1));
+    }
+
+    /**
+     * Select as the selected the given image
+     * @param image the selected image
+     */
+    public void setSelectedImage(ImageEntry image) {
+        setSelectedImageIndex(data.indexOf(image));
+    }
+
+    /**
+     * Clear the selected image
+     */
+    public void clearSelectedImage() {
+        setSelectedImageIndex(-1);
+    }
+
+    private void setSelectedImageIndex(int index) {
+        setSelectedImageIndex(index, false);
+    }
+
+    private void setSelectedImageIndex(int index, boolean forceTrigger) {
+        if (index == selectedImageIndex && !forceTrigger) {
+            return;
+        }
+        selectedImageIndex = index;
+        listeners.fireEvent(l -> l.selectedImageChanged(this));
+    }
+
+    /**
+     * Remove the current selected image from the list
+     */
+    public void removeSelectedImage() {
+        data.remove(getSelectedImage());
+        if (selectedImageIndex == data.size()) {
+            setSelectedImageIndex(data.size() - 1);
+        } else {
+            setSelectedImageIndex(selectedImageIndex, true);
+        }
+    }
+
+    /**
+     * Remove the image from the list and trigger update listener
+     * @param img the {@link ImageEntry} to remove
+     */
+    public void removeImage(ImageEntry img) {
+        data.remove(img);
+        notifyImageUpdate();
+    }
+
+    /**
+     * Update the position of the image and trigger update
+     * @param img the image to update
+     * @param newPos the new position
+     */
+    public void updateImagePosition(ImageEntry img, LatLon newPos) {
+        img.setPos(newPos);
+        afterImageUpdated(img);
+    }
+
+    /**
+     * Update the image direction of the image and trigger update
+     * @param img the image to update
+     * @param direction the new direction
+     */
+    public void updateImageDirection(ImageEntry img, double direction) {
+        img.setExifImgDir(direction);
+        afterImageUpdated(img);
+    }
+
+    /**
+     * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)}
+     */
+    public void notifyImageUpdate() {
+        listeners.fireEvent(l -> l.imageDataUpdated(this));
+    }
+
+    private void afterImageUpdated(ImageEntry img) {
+        img.flagNewGpsData();
+        notifyImageUpdate();
+    }
+
+    /**
+     * Add a listener that listens to image data changes
+     * @param listener the {@link ImageDataUpdateListener}
+     */
+    public void addImageDataUpdateListener(ImageDataUpdateListener listener) {
+        listeners.addListener(listener);
+    }
+
+    /**
+     * Removes a listener that listens to image data changes
+     * @param listener The listener
+     */
+    public void removeImageDataUpdateListener(ImageDataUpdateListener listener) {
+        listeners.removeListener(listener);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 14590)
@@ -179,8 +179,6 @@
             case CANCEL:
                 if (yLayer != null) {
-                    if (yLayer.data != null) {
-                        for (ImageEntry ie : yLayer.data) {
-                            ie.discardTmp();
-                        }
+                    for (ImageEntry ie : yLayer.getImageData().getImages()) {
+                        ie.discardTmp();
                     }
                     yLayer.updateBufferAndRepaint();
@@ -217,8 +215,6 @@
                 }
 
-                if (yLayer.data != null) {
-                    for (ImageEntry ie : yLayer.data) {
-                        ie.applyTmp();
-                    }
+                for (ImageEntry ie : yLayer.getImageData().getImages()) {
+                    ie.applyTmp();
                 }
 
@@ -646,10 +642,10 @@
                 @Override
                 public String getElementAt(int i) {
-                    return yLayer.data.get(i).getFile().getName();
+                    return yLayer.getImageData().getImages().get(i).getFile().getName();
                 }
 
                 @Override
                 public int getSize() {
-                    return yLayer.data != null ? yLayer.data.size() : 0;
+                    return yLayer.getImageData().getImages().size();
                 }
             });
@@ -657,6 +653,7 @@
             imgList.getSelectionModel().addListSelectionListener(evt -> {
                 int index = imgList.getSelectedIndex();
-                imgDisp.setImage(yLayer.data.get(index));
-                Date date = yLayer.data.get(index).getExifTime();
+                ImageEntry img = yLayer.getImageData().getImages().get(index);
+                imgDisp.setImage(img);
+                Date date = img.getExifTime();
                 if (date != null) {
                     DateFormat df = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
@@ -1042,8 +1039,6 @@
             // The selection of images we are about to correlate may have changed.
             // So reset all images.
-            if (yLayer.data != null) {
-                for (ImageEntry ie: yLayer.data) {
-                    ie.discardTmp();
-                }
+            for (ImageEntry ie: yLayer.getImageData().getImages()) {
+                ie.discardTmp();
             }
 
@@ -1301,9 +1296,6 @@
      */
     private List<ImageEntry> getSortedImgList(boolean exif, boolean tagged) {
-        if (yLayer.data == null) {
-            return Collections.emptyList();
-        }
-        List<ImageEntry> dateImgLst = new ArrayList<>(yLayer.data.size());
-        for (ImageEntry e : yLayer.data) {
+        List<ImageEntry> dateImgLst = new ArrayList<>(yLayer.getImageData().getImages().size());
+        for (ImageEntry e : yLayer.getImageData().getImages()) {
             if (!e.hasExifTime()) {
                 continue;
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 14590)
@@ -24,5 +24,4 @@
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -35,7 +34,5 @@
 import javax.swing.Action;
 import javax.swing.Icon;
-import javax.swing.JLabel;
 import javax.swing.JOptionPane;
-import javax.swing.SwingConstants;
 
 import org.openstreetmap.josm.actions.LassoModeAction;
@@ -44,6 +41,7 @@
 import org.openstreetmap.josm.actions.mapmode.SelectAction;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.ImageData;
+import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapFrame;
@@ -52,5 +50,4 @@
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
@@ -63,5 +60,4 @@
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
-import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Logging;
@@ -72,5 +68,5 @@
  */
 public class GeoImageLayer extends AbstractModifiableLayer implements
-        JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener {
+        JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener {
 
     private static List<Action> menuAdditions = new LinkedList<>();
@@ -78,11 +74,9 @@
     private static volatile List<MapMode> supportedMapModes;
 
-    List<ImageEntry> data;
+    private final ImageData data;
     GpxLayer gpxLayer;
 
     private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
     private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
-
-    private int currentPhoto = -1;
 
     boolean useThumbs;
@@ -152,10 +146,8 @@
     public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer, final String name, boolean useThumbs) {
         super(name != null ? name : tr("Geotagged Images"));
-        if (data != null) {
-            Collections.sort(data);
-        }
-        this.data = data;
+        this.data = new ImageData(data);
         this.gpxLayer = gpxLayer;
         this.useThumbs = useThumbs;
+        this.data.addImageDataUpdateListener(this);
     }
 
@@ -293,7 +285,7 @@
                 MainApplication.getLayerManager().addLayer(layer);
 
-                if (!canceled && layer.data != null && !layer.data.isEmpty()) {
+                if (!canceled && !layer.getImageData().getImages().isEmpty()) {
                     boolean noGeotagFound = true;
-                    for (ImageEntry e : layer.data) {
+                    for (ImageEntry e : layer.getImageData().getImages()) {
                         if (e.getPos() != null) {
                             noGeotagFound = false;
@@ -312,4 +304,9 @@
     }
 
+    /**
+     * Create a GeoImageLayer asynchronously
+     * @param files the list of image files to display
+     * @param gpxLayer the gpx layer
+     */
     public static void create(Collection<File> files, GpxLayer gpxLayer) {
         MainApplication.worker.execute(new Loader(files, gpxLayer));
@@ -321,4 +318,8 @@
     }
 
+    /**
+     * Register actions on the layer
+     * @param addition the action to be added
+     */
     public static void registerMenuAddition(Action addition) {
         menuAdditions.add(addition);
@@ -357,14 +358,11 @@
         int tagged = 0;
         int newdata = 0;
-        int n = 0;
-        if (data != null) {
-            n = data.size();
-            for (ImageEntry e : data) {
-                if (e.getPos() != null) {
-                    tagged++;
-                }
-                if (e.hasNewGpsData()) {
-                    newdata++;
-                }
+        int n = data.getImages().size();
+        for (ImageEntry e : data.getImages()) {
+            if (e.getPos() != null) {
+                tagged++;
+            }
+            if (e.hasNewGpsData()) {
+                newdata++;
             }
         }
@@ -392,12 +390,5 @@
     @Override
     public boolean isModified() {
-        if (data != null) {
-            for (ImageEntry e : data) {
-                if (e.hasNewGpsData()) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return this.data.isModified();
     }
 
@@ -418,36 +409,5 @@
         l.stopLoadThumbs();
 
-        final ImageEntry selected = l.data != null && l.currentPhoto >= 0 ? l.data.get(l.currentPhoto) : null;
-
-        if (l.data != null) {
-            data.addAll(l.data);
-        }
-        Collections.sort(data);
-
-        // Suppress the double photos.
-        if (data.size() > 1) {
-            ImageEntry cur;
-            ImageEntry prev = data.get(data.size() - 1);
-            for (int i = data.size() - 2; i >= 0; i--) {
-                cur = data.get(i);
-                if (cur.getFile().equals(prev.getFile())) {
-                    data.remove(i);
-                } else {
-                    prev = cur;
-                }
-            }
-        }
-
-        if (selected != null && !data.isEmpty()) {
-            GuiHelper.runInEDTAndWait(() -> {
-                for (int i = 0; i < data.size(); i++) {
-                    if (selected.equals(data.get(i))) {
-                        currentPhoto = i;
-                        ImageViewerDialog.showImage(this, data.get(i));
-                        break;
-                    }
-                }
-            });
-        }
+        this.data.mergeFrom(l.getImageData());
 
         setName(l.getName());
@@ -531,18 +491,16 @@
                 tempG.setComposite(saveComp);
 
-                if (data != null) {
-                    for (ImageEntry e : data) {
-                        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);
-                    }
+                for (ImageEntry e : data.getImages()) {
+                    paintImage(e, mv, clip, tempG);
+                }
+                if (data.getSelectedImage() != null) {
+                    // Make sure the selected image is on top in case multiple images overlap.
+                    paintImage(data.getSelectedImage(), mv, clip, tempG);
                 }
                 updateOffscreenBuffer = false;
             }
             g.drawImage(offscreenBuffer, 0, 0, null);
-        } else if (data != null) {
-            for (ImageEntry e : data) {
+        } else {
+            for (ImageEntry e : data.getImages()) {
                 if (e.getPos() == null) {
                     continue;
@@ -555,7 +513,6 @@
         }
 
-        if (currentPhoto >= 0 && currentPhoto < data.size()) {
-            ImageEntry e = data.get(currentPhoto);
-
+        ImageEntry e = data.getSelectedImage();
+        if (e != null) {
             if (e.getPos() != null) {
                 Point p = mv.getPoint(e.getPos());
@@ -622,5 +579,5 @@
     @Override
     public void visitBoundingBox(BoundingXYVisitor v) {
-        for (ImageEntry e : data) {
+        for (ImageEntry e : data.getImages()) {
             v.visit(e.getPos());
         }
@@ -631,9 +588,6 @@
      */
     public void showCurrentPhoto() {
-        clearOtherCurrentPhotos();
-        if (currentPhoto >= 0) {
-            ImageViewerDialog.showImage(this, data.get(currentPhoto));
-        } else {
-            ImageViewerDialog.showImage(this, null);
+        if (data.getSelectedImage() != null) {
+            clearOtherCurrentPhotos();
         }
         updateBufferAndRepaint();
@@ -641,158 +595,30 @@
 
     /**
-     * Shows next photo.
-     */
-    public void showNextPhoto() {
-        if (data != null && !data.isEmpty()) {
-            currentPhoto++;
-            if (currentPhoto >= data.size()) {
-                currentPhoto = data.size() - 1;
-            }
-        } else {
-            currentPhoto = -1;
-        }
-        showCurrentPhoto();
-    }
-
-    /**
-     * Shows previous photo.
-     */
-    public void showPreviousPhoto() {
-        if (data != null && !data.isEmpty()) {
-            currentPhoto--;
-            if (currentPhoto < 0) {
-                currentPhoto = 0;
-            }
-        } else {
-            currentPhoto = -1;
-        }
-        showCurrentPhoto();
-    }
-
-    /**
-     * Shows first photo.
-     */
-    public void showFirstPhoto() {
-        if (data != null && !data.isEmpty()) {
-            currentPhoto = 0;
-        } else {
-            currentPhoto = -1;
-        }
-        showCurrentPhoto();
-    }
-
-    /**
-     * Shows last photo.
-     */
-    public void showLastPhoto() {
-        if (data != null && !data.isEmpty()) {
-            currentPhoto = data.size() - 1;
-        } else {
-            currentPhoto = -1;
-        }
-        showCurrentPhoto();
-    }
-
-    public void checkPreviousNextButtons() {
-        ImageViewerDialog.setNextEnabled(data != null && currentPhoto < data.size() - 1);
-        ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);
-    }
-
-    public void removeCurrentPhoto() {
-        if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {
-            data.remove(currentPhoto);
-            if (currentPhoto >= data.size()) {
-                currentPhoto = data.size() - 1;
-            }
-            showCurrentPhoto();
-        }
-    }
-
-    public void removeCurrentPhotoFromDisk() {
-        ImageEntry toDelete;
-        if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {
-            toDelete = data.get(currentPhoto);
-
-            int result = new ExtendedDialog(
-                    MainApplication.getMainFrame(),
-                    tr("Delete image file from disk"),
-                    tr("Cancel"), tr("Delete"))
-            .setButtonIcons("cancel", "dialogs/delete")
-            .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>",
-                    toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"), SwingConstants.LEFT))
-                    .toggleEnable("geoimage.deleteimagefromdisk")
-                    .setCancelButton(1)
-                    .setDefaultButton(2)
-                    .showDialog()
-                    .getValue();
-
-            if (result == 2) {
-                data.remove(currentPhoto);
-                if (currentPhoto >= data.size()) {
-                    currentPhoto = data.size() - 1;
-                }
-
-                if (Utils.deleteFile(toDelete.getFile())) {
-                    Logging.info("File "+toDelete.getFile()+" deleted. ");
-                } else {
-                    JOptionPane.showMessageDialog(
-                            MainApplication.getMainFrame(),
-                            tr("Image file could not be deleted."),
-                            tr("Error"),
-                            JOptionPane.ERROR_MESSAGE
-                            );
-                }
-
-                showCurrentPhoto();
-            }
-        }
-    }
-
-    public void copyCurrentPhotoPath() {
-        if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {
-            ClipboardUtils.copyString(data.get(currentPhoto).getFile().toString());
-        }
-    }
-
-    /**
-     * Removes a photo from the list of images by index.
-     * @param idx Image index
-     * @since 6392
-     */
-    public void removePhotoByIdx(int idx) {
-        if (idx >= 0 && data != null && idx < data.size()) {
-            data.remove(idx);
-        }
-    }
-
-    /**
      * 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 idx the image index
      * @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 = MainApplication.getMap().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;
-                    }
+        ImageEntry img = data.getImages().get(idx);
+        if (img.getPos() != null) {
+            Point imgCenter = MainApplication.getMap().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 = 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;
-                }
+                    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;
             }
         }
@@ -810,29 +636,30 @@
      */
     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;
-                    }
+        ImageEntry selectedImage = data.getSelectedImage();
+        int selectedIndex = data.getImages().indexOf(selectedImage);
+
+        if (cycle && selectedImage != null) {
+            // Cycle loop is forward as that is the natural order.
+            // Loop 1: One after current photo up to last one.
+            for (int idx = selectedIndex + 1; idx < data.getImages().size(); ++idx) {
+                if (isPhotoIdxUnderMouse(idx, evt)) {
+                    return idx;
+                }
+            }
+            // Loop 2: First photo up to current one.
+            for (int idx = 0; idx <= selectedIndex; ++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 (selectedImage != null && isPhotoIdxUnderMouse(selectedIndex, evt)) {
+                return selectedIndex;
+            }
+            // Loop from last to first to prefer topmost image.
+            for (int idx = data.getImages().size() - 1; idx >= 0; --idx) {
+                if (isPhotoIdxUnderMouse(idx, evt)) {
+                    return idx;
                 }
             }
@@ -862,5 +689,5 @@
         int idx = getPhotoIdxUnderMouse(evt);
         if (idx >= 0) {
-            return data.get(idx);
+            return data.getImages().get(idx);
         } else {
             return null;
@@ -871,8 +698,10 @@
      * Clears the currentPhoto, i.e. remove select marker, and optionally repaint.
      * @param repaint Repaint flag
+     * @deprecated Use {@link ImageData#clearSelectedImage}
      * @since 6392
      */
+    @Deprecated
     public void clearCurrentPhoto(boolean repaint) {
-        currentPhoto = -1;
+        data.clearSelectedImage();
         if (repaint) {
             updateBufferAndRepaint();
@@ -887,5 +716,5 @@
                  MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) {
             if (layer != this) {
-                layer.clearCurrentPhoto(false);
+                layer.getImageData().clearSelectedImage();
             }
         }
@@ -948,5 +777,5 @@
                 if (ev.getButton() != MouseEvent.BUTTON1)
                     return;
-                if (data == null || !isVisible() || !isMapModeOk())
+                if (!isVisible() || !isMapModeOk())
                     return;
 
@@ -957,6 +786,5 @@
                     lastSelPos = mousePos;
                     cycleModeArmed = false;
-                    currentPhoto = idx;
-                    showCurrentPhoto();
+                    data.setSelectedImage(data.getImages().get(idx));
                 }
             }
@@ -1014,9 +842,5 @@
         MapFrame.removeMapModeChangeListener(mapModeListener);
         MainApplication.getLayerManager().removeActiveLayerChangeListener(activeLayerChangeListener);
-        currentPhoto = -1;
-        if (data != null) {
-            data.clear();
-        }
-        data = null;
+        data.removeImageDataUpdateListener(this);
     }
 
@@ -1084,5 +908,14 @@
      */
     public List<ImageEntry> getImages() {
-        return data == null ? Collections.<ImageEntry>emptyList() : new ArrayList<>(data);
+        return new ArrayList<>(data.getImages());
+    }
+
+    /**
+     * Returns the image data store being used by this layer
+     * @return imageData
+     * @since 14590
+     */
+    public ImageData getImageData() {
+        return data;
     }
 
@@ -1097,10 +930,10 @@
     @Override
     public void jumpToNextMarker() {
-        showNextPhoto();
+        data.selectNextImage();
     }
 
     @Override
     public void jumpToPreviousMarker() {
-        showPreviousPhoto();
+        data.selectPreviousImage();
     }
 
@@ -1129,3 +962,13 @@
         invalidate();
     }
+
+    @Override
+    public void selectedImageChanged(ImageData data) {
+        showCurrentPhoto();
+    }
+
+    @Override
+    public void imageDataUpdated(ImageData data) {
+        updateBufferAndRepaint();
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 14590)
@@ -703,6 +703,8 @@
      */
     public void setOsdText(String text) {
-        this.osdText = text;
-        repaint();
+        if (!text.equals(this.osdText)) {
+            this.osdText = text;
+            repaint();
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 14590)
@@ -14,12 +14,20 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Objects;
 
 import javax.swing.Box;
 import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JToggleButton;
+import javax.swing.SwingConstants;
 
 import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.ImageData;
+import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
+import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
 import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
@@ -32,5 +40,7 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.date.DateUtils;
 
@@ -38,5 +48,5 @@
  * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}.
  */
-public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener {
+public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener {
 
     private final ImageZoomAction imageZoomAction = new ImageZoomAction();
@@ -80,4 +90,7 @@
     private JButton btnFirst;
     private JButton btnCollapse;
+    private JButton btnDelete;
+    private JButton btnCopyPath;
+    private JButton btnDeleteFromDisk;
     private JToggleButton tbCentre;
 
@@ -88,4 +101,7 @@
         MainApplication.getLayerManager().addActiveLayerChangeListener(this);
         MainApplication.getLayerManager().addLayerChangeListener(this);
+        for (Layer l: MainApplication.getLayerManager().getLayers()) {
+            registerOnLayer(l);
+        }
     }
 
@@ -107,11 +123,11 @@
         btnPrevious = createNavigationButton(imagePreviousAction, buttonDim);
 
-        JButton btnDelete = new JButton(imageRemoveAction);
+        btnDelete = new JButton(imageRemoveAction);
         btnDelete.setPreferredSize(buttonDim);
 
-        JButton btnDeleteFromDisk = new JButton(imageRemoveFromDiskAction);
+        btnDeleteFromDisk = new JButton(imageRemoveFromDiskAction);
         btnDeleteFromDisk.setPreferredSize(buttonDim);
 
-        JButton btnCopyPath = new JButton(imageCopyPathAction);
+        btnCopyPath = new JButton(imageCopyPathAction);
         btnCopyPath.setPreferredSize(buttonDim);
 
@@ -190,6 +206,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.showNextPhoto();
+            if (currentData != null) {
+                currentData.selectNextImage();
             }
         }
@@ -205,6 +221,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.showPreviousPhoto();
+            if (currentData != null) {
+                currentData.selectPreviousImage();
             }
         }
@@ -220,6 +236,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.showFirstPhoto();
+            if (currentData != null) {
+                currentData.selectFirstImage();
             }
         }
@@ -235,6 +251,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.showLastPhoto();
+            if (currentData != null) {
+                currentData.selectLastImage();
             }
         }
@@ -278,6 +294,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.removeCurrentPhoto();
+            if (currentData != null) {
+                currentData.removeSelectedImage();
             }
         }
@@ -294,6 +310,35 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.removeCurrentPhotoFromDisk();
+            if (currentData != null && currentData.getSelectedImage() != null) {
+                ImageEntry toDelete = currentData.getSelectedImage();
+
+                int result = new ExtendedDialog(
+                        MainApplication.getMainFrame(),
+                        tr("Delete image file from disk"),
+                        tr("Cancel"), tr("Delete"))
+                        .setButtonIcons("cancel", "dialogs/delete")
+                        .setContent(new JLabel("<html><h3>" + tr("Delete the file {0} from disk?", toDelete.getFile().getName())
+                                + "<p>" + tr("The image file will be permanently lost!") + "</h3></html>",
+                                ImageProvider.get("dialogs/geoimage/deletefromdisk"), SwingConstants.LEFT))
+                        .toggleEnable("geoimage.deleteimagefromdisk")
+                        .setCancelButton(1)
+                        .setDefaultButton(2)
+                        .showDialog()
+                        .getValue();
+
+                if (result == 2) {
+                    currentData.removeSelectedImage();
+
+                    if (Utils.deleteFile(toDelete.getFile())) {
+                        Logging.info("File " + toDelete.getFile() + " deleted.");
+                    } else {
+                        JOptionPane.showMessageDialog(
+                                MainApplication.getMainFrame(),
+                                tr("Image file could not be deleted."),
+                                tr("Error"),
+                                JOptionPane.ERROR_MESSAGE
+                                );
+                    }
+                }
             }
         }
@@ -309,6 +354,6 @@
         @Override
         public void actionPerformed(ActionEvent e) {
-            if (currentLayer != null) {
-                currentLayer.copyCurrentPhotoPath();
+            if (currentData != null) {
+                ClipboardUtils.copyString(currentData.getSelectedImage().getFile().toString());
             }
         }
@@ -329,16 +374,10 @@
 
     /**
-     * Displays image for the given layer.
-     * @param layer geo image layer
+     * Displays image for the given data.
+     * @param data geo image data
      * @param entry image entry
      */
-    public static void showImage(GeoImageLayer layer, ImageEntry entry) {
-        getInstance().displayImage(layer, entry);
-        if (layer != null) {
-            layer.checkPreviousNextButtons();
-        } else {
-            setPreviousEnabled(false);
-            setNextEnabled(false);
-        }
+    public static void showImage(ImageData data, ImageEntry entry) {
+        getInstance().displayImage(data, entry);
     }
 
@@ -347,7 +386,7 @@
      * @param value {@code true} to enable the button, {@code false} otherwise
      */
-    public static void setPreviousEnabled(boolean value) {
-        getInstance().btnFirst.setEnabled(value);
-        getInstance().btnPrevious.setEnabled(value);
+    public void setPreviousEnabled(boolean value) {
+        btnFirst.setEnabled(value);
+        btnPrevious.setEnabled(value);
     }
 
@@ -356,7 +395,7 @@
      * @param value {@code true} to enable the button, {@code false} otherwise
      */
-    public static void setNextEnabled(boolean value) {
-        getInstance().btnNext.setEnabled(value);
-        getInstance().btnLast.setEnabled(value);
+    public void setNextEnabled(boolean value) {
+        btnNext.setEnabled(value);
+        btnLast.setEnabled(value);
     }
 
@@ -374,13 +413,13 @@
     }
 
-    private transient GeoImageLayer currentLayer;
+    private transient ImageData currentData;
     private transient ImageEntry currentEntry;
 
     /**
      * Displays image for the given layer.
-     * @param layer geo image layer
+     * @param data the image data
      * @param entry image entry
      */
-    public void displayImage(GeoImageLayer layer, ImageEntry entry) {
+    public void displayImage(ImageData data, ImageEntry entry) {
         boolean imageChanged;
 
@@ -394,9 +433,15 @@
             }
 
-            currentLayer = layer;
+            currentData = data;
             currentEntry = entry;
         }
 
         if (entry != null) {
+            setNextEnabled(data.hasNextImage());
+            setPreviousEnabled(data.hasPreviousImage());
+            btnDelete.setEnabled(true);
+            btnDeleteFromDisk.setEnabled(true);
+            btnCopyPath.setEnabled(true);
+
             if (imageChanged) {
                 // Set only if the image is new to preserve zoom and position if the same image is redisplayed
@@ -437,4 +482,9 @@
             imgDisplay.setImage(null);
             imgDisplay.setOsdText("");
+            setNextEnabled(false);
+            setPreviousEnabled(false);
+            btnDelete.setEnabled(false);
+            btnDeleteFromDisk.setEnabled(false);
+            btnCopyPath.setEnabled(false);
             return;
         }
@@ -489,13 +539,4 @@
 
     /**
-     * Returns the layer associated with the image.
-     * @return Layer associated with the image
-     * @since 6392
-     */
-    public static GeoImageLayer getCurrentLayer() {
-        return getInstance().currentLayer;
-    }
-
-    /**
      * Returns whether the center view is currently active.
      * @return {@code true} if the center view is active, {@code false} otherwise
@@ -508,4 +549,5 @@
     @Override
     public void layerAdded(LayerAddEvent e) {
+        registerOnLayer(e.getAddedLayer());
         showLayer(e.getAddedLayer());
     }
@@ -513,11 +555,10 @@
     @Override
     public void layerRemoving(LayerRemoveEvent e) {
-        // Clear current image and layer if current layer is deleted
-        if (currentLayer != null && currentLayer.equals(e.getRemovedLayer())) {
-            showImage(null, null);
-        }
-        // Check buttons state in case of layer merging
-        if (currentLayer != null && e.getRemovedLayer() instanceof GeoImageLayer) {
-            currentLayer.checkPreviousNextButtons();
+        if (e.getRemovedLayer() instanceof GeoImageLayer) {
+            ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData();
+            if (removedData == currentData) {
+                displayImage(null, null);
+            }
+            removedData.removeImageDataUpdateListener(this);
         }
     }
@@ -533,8 +574,24 @@
     }
 
+    private void registerOnLayer(Layer layer) {
+        if (layer instanceof GeoImageLayer) {
+            ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this);
+        }
+    }
+
     private void showLayer(Layer newLayer) {
-        if (currentLayer == null && newLayer instanceof GeoImageLayer) {
-            ((GeoImageLayer) newLayer).showFirstPhoto();
-        }
+        if (currentData == null && newLayer instanceof GeoImageLayer) {
+            ((GeoImageLayer) newLayer).getImageData().selectFirstImage();
+        }
+    }
+
+    @Override
+    public void selectedImageChanged(ImageData data) {
+        showImage(data, data.getSelectedImage());
+    }
+
+    @Override
+    public void imageDataUpdated(ImageData data) {
+        showImage(data, data.getSelectedImage());
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java	(revision 14590)
@@ -50,5 +50,5 @@
      */
     private static boolean enabled(GeoImageLayer layer) {
-        return layer.data != null && !layer.data.isEmpty();
+        return !layer.getImageData().getImages().isEmpty();
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java	(revision 14589)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java	(revision 14590)
@@ -52,5 +52,5 @@
      */
     public ThumbsLoader(GeoImageLayer layer) {
-        this(new ArrayList<>(layer.data), layer);
+        this(new ArrayList<>(layer.getImageData().getImages()), layer);
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(revision 14590)
+++ /trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(revision 14590)
@@ -0,0 +1,322 @@
+// License: GPL. For details, see LICENSE file.
+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;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
+
+import mockit.Expectations;
+import mockit.Mock;
+import mockit.MockUp;
+
+/**
+ * Unit tests for class {@link ImageData}.
+ */
+public class ImageDataTest {
+
+    private static List<ImageEntry> getOneImage() {
+        ArrayList<ImageEntry> list = new ArrayList<>();
+        list.add(new ImageEntry(new File("test")));
+        return list;
+    }
+
+    @Test
+    public void testWithullData() {
+        ImageData data = new ImageData();
+        assertEquals(0, data.getImages().size());
+        assertNull(data.getSelectedImage());
+        data.selectFirstImage();
+        assertNull(data.getSelectedImage());
+        data.selectLastImage();
+        assertNull(data.getSelectedImage());
+        data.selectFirstImage();
+        assertNull(data.getSelectedImage());
+        data.selectPreviousImage();
+        assertNull(data.getSelectedImage());
+        assertFalse(data.hasNextImage());
+        assertFalse(data.hasPreviousImage());
+        data.removeSelectedImage();
+    }
+
+    @Test
+    public void testmageEntryWithImages() {
+        assertEquals(1, new ImageData(this.getOneImage()).getImages().size());
+    }
+
+    @Test
+    public void testSortData() {
+        List<ImageEntry> list = this.getOneImage();
+
+        new Expectations(Collections.class) {{
+            Collections.sort(list);
+        }};
+
+        new ImageData(list);
+    }
+
+    @Test
+    public void testIsModifiedFalse() {
+        assertFalse(new ImageData(this.getOneImage()).isModified());
+    }
+
+    @Test
+    public void testIsModifiedTrue() {
+        List<ImageEntry> list = this.getOneImage();
+
+        new Expectations(list.get(0)) {{
+            list.get(0).hasNewGpsData(); result = true;
+        }};
+
+        assertTrue(new ImageData(list).isModified());
+    }
+
+    @Test
+    public void testSelectFirstImage() {
+        List<ImageEntry> list = this.getOneImage();
+
+        ImageData data = new ImageData(list);
+        data.selectFirstImage();
+        assertEquals(list.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testSelectLastImage() {
+        List<ImageEntry> list = this.getOneImage();
+        list.add(new ImageEntry());
+
+        ImageData data = new ImageData(list);
+        data.selectLastImage();
+        assertEquals(list.get(1), data.getSelectedImage());
+    }
+
+    @Test
+    public void testSelectNextImage() {
+        List<ImageEntry> list = this.getOneImage();
+
+        ImageData data = new ImageData(list);
+        assertTrue(data.hasNextImage());
+        data.selectNextImage();
+        assertEquals(list.get(0), data.getSelectedImage());
+        assertFalse(data.hasNextImage());
+        data.selectNextImage();
+        assertEquals(list.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testSelectPreviousImage() {
+        List<ImageEntry> list = this.getOneImage();
+        list.add(new ImageEntry());
+
+        ImageData data = new ImageData(list);
+        assertFalse(data.hasPreviousImage());
+        data.selectLastImage();
+        assertTrue(data.hasPreviousImage());
+        data.selectPreviousImage();
+        assertEquals(list.get(0), data.getSelectedImage());
+        data.selectPreviousImage();
+        assertEquals(list.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testSetSelectedImage() {
+        List<ImageEntry> list = this.getOneImage();
+
+        ImageData data = new ImageData(list);
+        data.setSelectedImage(list.get(0));
+        assertEquals(list.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testClearSelectedImage() {
+        List<ImageEntry> list = this.getOneImage();
+
+        ImageData data = new ImageData(list);
+        data.setSelectedImage(list.get(0));
+        data.clearSelectedImage();
+        assertNull(data.getSelectedImage());
+    }
+
+    @Test
+    public void testSelectionListener() {
+        List<ImageEntry> list = this.getOneImage();
+        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 = 1;
+        }};
+        data.addImageDataUpdateListener(listener);
+        data.selectFirstImage();
+        data.selectFirstImage();
+    }
+
+    @Test
+    public void testRemoveSelectedImage() {
+        List<ImageEntry> list = this.getOneImage();
+        ImageData data = new ImageData(list);
+        data.selectFirstImage();
+        data.removeSelectedImage();
+        assertEquals(0, data.getImages().size());
+        assertNull(data.getSelectedImage());
+    }
+
+    @Test
+    public void testRemoveSelectedWithImageTriggerListener() {
+        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 = 2;
+        }};
+        data.addImageDataUpdateListener(listener);
+        data.selectFirstImage();
+        data.removeSelectedImage();
+    }
+
+    @Test
+    public void testRemoveImageAndTriggerListener() {
+        List<ImageEntry> list = this.getOneImage();
+        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.imageDataUpdated(data); times = 1;
+        }};
+        data.addImageDataUpdateListener(listener);
+        data.removeImage(list.get(0));
+        assertEquals(0, data.getImages().size());
+    }
+
+    @Test
+    public void testMergeFrom() {
+        ImageEntry image = new ImageEntry(new File("test2"));
+        List<ImageEntry> list1 = this.getOneImage();
+        list1.add(image);
+        List<ImageEntry> list2 = this.getOneImage();
+        list2.add(new ImageEntry(new File("test3")));
+
+        ImageData data = new ImageData(list1);
+        data.setSelectedImage(list1.get(0));
+        ImageData data2 = new ImageData(list2);
+
+        new MockUp<Collections>() {
+            @Mock
+            public void sort(List<ImageEntry> o) {
+                list1.remove(image);
+                list1.add(image);
+            }
+        };
+
+        data.mergeFrom(data2);
+        assertEquals(3, data.getImages().size());
+        assertEquals(list1.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testMergeFromSelectedImage() {
+        ImageEntry image = new ImageEntry(new File("test2"));
+        List<ImageEntry> list1 = this.getOneImage();
+        list1.add(image);
+        List<ImageEntry> list2 = this.getOneImage();
+
+        ImageData data = new ImageData(list1);
+        ImageData data2 = new ImageData(list2);
+        data2.setSelectedImage(list2.get(0));
+
+        data.mergeFrom(data2);
+        assertEquals(3, data.getImages().size());
+        assertEquals(list2.get(0), data.getSelectedImage());
+    }
+
+    @Test
+    public void testUpdatePosition() {
+        List<ImageEntry> list = this.getOneImage();
+        ImageData data = new ImageData(list);
+
+        new Expectations(list.get(0)) {{
+            list.get(0).setPos((LatLon) any);
+            list.get(0).flagNewGpsData();
+        }};
+        data.updateImagePosition(list.get(0), new LatLon(0, 0));
+    }
+
+    @Test
+    public void testUpdateDirection() {
+        List<ImageEntry> list = this.getOneImage();
+        ImageData data = new ImageData(list);
+
+        new Expectations(list.get(0)) {{
+            list.get(0).setExifImgDir(0.0);
+            list.get(0).flagNewGpsData();
+        }};
+        data.updateImageDirection(list.get(0), 0);
+    }
+
+    @Test
+    public void testTriggerListenerOnUpdate() {
+        List<ImageEntry> list = this.getOneImage();
+        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.imageDataUpdated(data); times = 1;
+        }};
+
+        data.addImageDataUpdateListener(listener);
+        data.updateImageDirection(list.get(0), 0);
+    }
+
+    @Test
+    public void testManuallyTriggerUpdateListener() {
+        List<ImageEntry> list = this.getOneImage();
+        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.imageDataUpdated(data); times = 1;
+        }};
+
+        data.addImageDataUpdateListener(listener);
+        data.notifyImageUpdate();
+    }
+}
