Index: /trunk/src/org/openstreetmap/josm/gui/MapFrame.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 2565)
+++ /trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 2566)
@@ -80,5 +80,5 @@
      */
     private List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
-    private DialogsPanel dialogsPanel;
+    private final DialogsPanel dialogsPanel;
 
     public final ButtonGroup toolGroup = new ButtonGroup();
@@ -219,4 +219,7 @@
         toolBarToggle.add(button);
         allDialogs.add(dlg);
+        if (dialogsPanel.initialized) {
+            dialogsPanel.add(dlg);
+        }
         return button;
     }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/DialogsPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/DialogsPanel.java	(revision 2565)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/DialogsPanel.java	(revision 2566)
@@ -32,48 +32,57 @@
     }
 
-    private boolean initialized = false;
-    public void initialize(List<ToggleDialog> allDialogs) {
+    public boolean initialized = false; // read only from outside
+    
+    public void initialize(List<ToggleDialog> pAllDialogs) {
         if (initialized)
             throw new IllegalStateException();
         initialized = true;
-        this.allDialogs = allDialogs;
-
-        for (Integer i=0; i < allDialogs.size(); ++i) {
-            final ToggleDialog dlg = allDialogs.get(i);
-            dlg.setDialogsPanel(this);
-            dlg.setVisible(false);
-        }
-        for (int i=0; i < allDialogs.size() + 1; ++i) {
-            final JPanel p = new JPanel() {
-                /**
-                 * Honoured by the MultiSplitPaneLayout when the
-                 * entire Window is resized.
-                 */
-                @Override
-                public Dimension getMinimumSize() {
-                    return new Dimension(0, 40);
-                }
-            };
-            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
-            p.setVisible(false);
-
-            mSpltPane.add(p, "L"+i);
-            panels.add(p);
-        }
-
-        for (Integer i=0; i < allDialogs.size(); ++i) {
-            final ToggleDialog dlg = allDialogs.get(i);
-            if (dlg.isDialogShowing()) {
-                dlg.showDialog();
-                if (dlg.isDialogInCollapsedView()) {
-                    dlg.isCollapsed = false;    // pretend to be in Default view, this will be set back by collapse()
-                    dlg.collapse();
-                }
-            } else {
-                dlg.hideDialog();
-            }
-        }
+        allDialogs = new ArrayList<ToggleDialog>();
+
+        for (Integer i=0; i < pAllDialogs.size(); ++i) {
+            add(pAllDialogs.get(i), false);
+        }
+        
         this.add(mSpltPane);
         reconstruct(Action.ELEMENT_SHRINKS, null);
+    }
+
+    public void add(ToggleDialog dlg) {
+        add(dlg, true);
+    }
+    
+    public void add(ToggleDialog dlg, boolean doReconstruct) {
+        allDialogs.add(dlg);
+        int i = allDialogs.size() - 1;
+        dlg.setDialogsPanel(this);
+        dlg.setVisible(false);
+        final JPanel p = new JPanel() {
+            /**
+             * Honoured by the MultiSplitPaneLayout when the
+             * entire Window is resized.
+             */
+            @Override
+            public Dimension getMinimumSize() {
+                return new Dimension(0, 40);
+            }
+        };
+        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+        p.setVisible(false);
+
+        mSpltPane.add(p, "L"+i);
+        panels.add(p);
+
+        if (dlg.isDialogShowing()) {
+            dlg.showDialog();
+            if (dlg.isDialogInCollapsedView()) {
+                dlg.isCollapsed = false;    // pretend to be in Default view, this will be set back by collapse()
+                dlg.collapse();
+            }
+            if (doReconstruct) {
+                reconstruct(Action.INVISIBLE_TO_DEFAULT, dlg);
+            }
+        } else {
+            dlg.hideDialog();
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(revision 2565)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(revision 2566)
@@ -1,2 +1,3 @@
+// License: GPL. See LICENSE file for details.
 package org.openstreetmap.josm.gui.dialogs;
 
Index: unk/src/org/openstreetmap/josm/gui/layer/GeoImageLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/GeoImageLayer.java	(revision 2565)
+++ 	(revision )
@@ -1,816 +1,0 @@
-//License: GPL. Copyright 2007 by Immanuel Scholz and others
-package org.openstreetmap.josm.gui.layer;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Cursor;
-import java.awt.Graphics2D;
-import java.awt.GridBagLayout;
-import java.awt.Image;
-import java.awt.Insets;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Toolkit;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.image.BufferedImage;
-import java.awt.image.ImageObserver;
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.WeakHashMap;
-
-import javax.swing.BorderFactory;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
-import javax.swing.JTextField;
-import javax.swing.JToggleButton;
-import javax.swing.JViewport;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.border.BevelBorder;
-import javax.swing.border.Border;
-import javax.swing.filechooser.FileFilter;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.RenameLayerAction;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.CachedLatLon;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.gpx.GpxTrack;
-import org.openstreetmap.josm.data.gpx.WayPoint;
-import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
-import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
-import org.openstreetmap.josm.gui.layer.GeoImageLayer.ImageLoader.ImageLoadedListener;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.tools.DateParser;
-import org.openstreetmap.josm.tools.ExifReader;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.ImageProvider;
-
-/**
- * A layer which imports several photos from disk and read EXIF time information from them.
- *
- * @author Imi
- */
-public class GeoImageLayer extends Layer {
-
-    /**
-     * Allows to load and scale images. Loaded images are kept in cache (using soft reference). Both
-     * synchronous and asynchronous loading is supported
-     *
-     */
-    public static class ImageLoader implements ImageObserver {
-
-        public static class Entry {
-            final File file;
-            final int width;
-            final int height;
-            final int maxSize;
-            private final ImageLoadedListener listener;
-
-            volatile Image scaledImage;
-
-            public Entry(File file, int width, int height, int maxSize, ImageLoadedListener listener) {
-                this.file = file;
-                this.height = height;
-                this.width = width;
-                this.maxSize = maxSize;
-                this.listener = listener;
-            }
-        }
-
-        public interface ImageLoadedListener {
-            void imageLoaded();
-        }
-
-        private final List<ImageLoader.Entry> queue = new ArrayList<ImageLoader.Entry>();
-        private final WeakHashMap<File, SoftReference<Image>> loadedImageCache = new WeakHashMap<File, SoftReference<Image>>();
-        private ImageLoader.Entry currentEntry;
-
-        private Image getOrLoadImage(File file) {
-            SoftReference<Image> cachedImageRef = loadedImageCache.get(file);
-            if (cachedImageRef != null) {
-                Image cachedImage = cachedImageRef.get();
-                if (cachedImage != null)
-                    return cachedImage;
-            }
-            return Toolkit.getDefaultToolkit().createImage(currentEntry.file.getAbsolutePath());
-        }
-
-        private BufferedImage createResizedCopy(Image originalImage,
-                int scaledWidth, int scaledHeight)
-        {
-            BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
-            Graphics2D g = scaledBI.createGraphics();
-            while (!g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null))
-            {
-                try {
-                    Thread.sleep(10);
-                } catch(InterruptedException ie) {}
-            }
-            g.dispose();
-            return scaledBI;
-        }
-        private void loadImage() {
-            if (currentEntry != null)
-                return;
-            while (!queue.isEmpty()) {
-                currentEntry = queue.get(0);
-                queue.remove(0);
-
-                Image newImage = getOrLoadImage(currentEntry.file);
-                if (newImage.getWidth(this) == -1) {
-                    break;
-                } else {
-                    finishImage(newImage, currentEntry);
-                    currentEntry = null;
-                }
-            }
-        }
-
-        private void finishImage(Image img, ImageLoader.Entry entry) {
-            loadedImageCache.put(entry.file, new SoftReference<Image>(img));
-            if (entry.maxSize != -1) {
-                int w = img.getWidth(null);
-                int h = img.getHeight(null);
-                if (w>h) {
-                    h = Math.round(entry.maxSize*((float)h/w));
-                    w = entry.maxSize;
-                } else {
-                    w = Math.round(entry.maxSize*((float)w/h));
-                    h = entry.maxSize;
-                }
-                entry.scaledImage = createResizedCopy(img, w, h);
-            } else if (entry.width != -1 && entry.height != -1) {
-                entry.scaledImage = createResizedCopy(img, entry.width, entry.height);
-            } else {
-                entry.scaledImage = img;
-            }
-            if (entry.listener != null) {
-                entry.listener.imageLoaded();
-            }
-        }
-
-        public synchronized ImageLoader.Entry loadImage(File file, int width, int height, int maxSize, ImageLoadedListener listener) {
-            ImageLoader.Entry e = new Entry(file, width, height, maxSize, listener);
-            queue.add(e);
-            loadImage();
-            return e;
-        }
-
-        public Image waitForImage(File file, int width, int height) {
-            return waitForImage(file, width, height, -1);
-        }
-
-        public Image waitForImage(File file, int maxSize) {
-            return waitForImage(file, -1, -1, maxSize);
-        }
-
-        public Image waitForImage(File file) {
-            return waitForImage(file, -1, -1, -1);
-        }
-
-        private synchronized Image waitForImage(File file, int width, int height, int maxSize) {
-            ImageLoader.Entry entry;
-            if (currentEntry != null && currentEntry.file.equals(file)) {
-                entry = currentEntry;
-            } else {
-                entry = new Entry(file, width, height, maxSize, null);
-                queue.add(0, entry);
-            }
-            loadImage();
-
-            while (true) {
-                if (entry.scaledImage != null)
-                    return entry.scaledImage;
-                try {
-                    wait();
-                } catch (InterruptedException e) {}
-            }
-        }
-
-        public synchronized boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
-            if ((infoflags & ImageObserver.ALLBITS) != 0) {
-                finishImage(img, currentEntry);
-                currentEntry = null;
-                loadImage();
-                notifyAll();
-            } else if ((infoflags & ImageObserver.ERROR) != 0) {
-                currentEntry.scaledImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
-                currentEntry = null;
-            }
-            return true;
-        }
-    }
-
-    private static final int ICON_SIZE = 16;
-    private static ImageLoader imageLoader = new ImageLoader();
-
-    private static final class ImageEntry implements Comparable<ImageEntry>, ImageLoadedListener {
-
-        private static final Image EMPTY_IMAGE = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
-
-        final File image;
-        ImageLoader.Entry icon;
-        Date time;
-        CachedLatLon pos;
-
-        public ImageEntry(File image) {
-            this.image = image;
-            icon = imageLoader.loadImage(image, ICON_SIZE, ICON_SIZE, -1, this);
-        }
-
-        public int compareTo(ImageEntry image) {
-            return time.compareTo(image.time);
-        }
-
-        public Image getIcon() {
-            if (icon.scaledImage == null)
-                return EMPTY_IMAGE;
-            else
-                return icon.scaledImage;
-        }
-
-        public void imageLoaded() {
-            MapFrame frame = Main.map;
-            if (frame != null) {
-                frame.mapView.repaint();
-            }
-        }
-    }
-
-    private static final class Loader extends PleaseWaitRunnable {
-        private GeoImageLayer layer;
-        private final Collection<File> files;
-        private final GpxLayer gpxLayer;
-        private LinkedList<TimedPoint> gps;
-
-        public Loader(Collection<File> files, GpxLayer gpxLayer) {
-            super(tr("Images for {0}", gpxLayer.getName()));
-            this.files = files;
-            this.gpxLayer = gpxLayer;
-        }
-        @Override protected void realRun() throws IOException {
-            progressMonitor.subTask(tr("Read GPX..."));
-            progressMonitor.setTicksCount(10 + files.size());
-            gps = new LinkedList<TimedPoint>();
-
-            // Extract dates and locations from GPX input
-
-            ProgressMonitor gpxSubTask = progressMonitor.createSubTaskMonitor(10, true);
-            int size = 0;
-            for (GpxTrack trk:gpxLayer.data.tracks) {
-                for (Collection<WayPoint> segment : trk.trackSegs) {
-                    size += segment.size();
-                }
-            }
-            gpxSubTask.beginTask(null, size);
-
-            try {
-                for (GpxTrack trk : gpxLayer.data.tracks) {
-                    for (Collection<WayPoint> segment : trk.trackSegs) {
-                        for (WayPoint p : segment) {
-                            LatLon c = p.getCoor();
-                            if (!p.attr.containsKey("time"))
-                                throw new IOException(tr("No time for point {0} x {1}",c.lat(),c.lon()));
-                            Date d = null;
-                            try {
-                                d = DateParser.parse((String) p.attr.get("time"));
-                            } catch (ParseException e) {
-                                throw new IOException(tr("Cannot read time \"{0}\" from point {1} x {2}",p.attr.get("time"),c.lat(),c.lon()));
-                            }
-                            gps.add(new TimedPoint(d, c));
-                            gpxSubTask.worked(1);
-                        }
-                    }
-                }
-            } finally {
-                gpxSubTask.finishTask();
-            }
-
-            if (gps.isEmpty())
-                return;
-
-            // read the image files
-            ArrayList<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
-            for (File f : files) {
-                if (progressMonitor.isCancelled()) {
-                    break;
-                }
-                progressMonitor.subTask(tr("Reading {0}...",f.getName()));
-
-                ImageEntry e = new ImageEntry(f);
-                try {
-                    e.time = ExifReader.readTime(f);
-                    progressMonitor.worked(1);
-                } catch (ParseException e1) {
-                    continue;
-                }
-                if (e.time == null) {
-                    continue;
-                }
-
-                data.add(e);
-            }
-            layer = new GeoImageLayer(data, gps);
-            layer.calculatePosition();
-        }
-        @Override protected void finish() {
-            if (gps.isEmpty()) {
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("No images with readable timestamps found."),
-                        tr("Warning"),
-                        JOptionPane.WARNING_MESSAGE
-                );
-                return;
-            }
-            if (layer != null) {
-                Main.main.addLayer(layer);
-            }
-        }
-
-        @Override
-        protected void cancel() {
-
-        }
-    }
-
-    public ArrayList<ImageEntry> data;
-    private LinkedList<TimedPoint> gps = new LinkedList<TimedPoint>();
-
-    /**
-     * The delta added to all timestamps in files from the camera
-     * to match to the timestamp from the gps receivers tracklog.
-     */
-    private long delta = Long.parseLong(Main.pref.get("tagimages.delta", "0"));
-    private long gpstimezone = Long.parseLong(Main.pref.get("tagimages.gpstimezone", "0"))*60*60*1000;
-    private boolean mousePressed = false;
-    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
-    private MouseAdapter mouseAdapter;
-    private ImageViewerDialog imageViewerDialog;
-
-    public static final class GpsTimeIncorrect extends Exception {
-        public GpsTimeIncorrect(String message, Throwable cause) {
-            super(message, cause);
-        }
-        public GpsTimeIncorrect(String message) {
-            super(message);
-        }
-    }
-
-    private static final class TimedPoint implements Comparable<TimedPoint> {
-        Date time;
-        CachedLatLon pos;
-
-        public TimedPoint(Date time, LatLon pos) {
-            this.time = time;
-            this.pos = new CachedLatLon(pos);
-        }
-        public int compareTo(TimedPoint point) {
-            return time.compareTo(point.time);
-        }
-    }
-
-    public static void create(Collection<File> files, GpxLayer gpxLayer) {
-        Loader loader = new Loader(files, gpxLayer);
-        Main.worker.execute(loader);
-    }
-
-    private GeoImageLayer(final ArrayList<ImageEntry> data, LinkedList<TimedPoint> gps) {
-        super(tr("Geotagged Images"));
-        Collections.sort(data);
-        Collections.sort(gps);
-        this.data = data;
-        this.gps = gps;
-        final Layer self = this;
-        mouseAdapter = new MouseAdapter(){
-            @Override public void mousePressed(MouseEvent e) {
-                if (e.getButton() != MouseEvent.BUTTON1)
-                    return;
-                mousePressed  = true;
-                if (isVisible()) {
-                    Main.map.mapView.repaint();
-                }
-            }
-            @Override public void mouseReleased(MouseEvent ev) {
-                if (ev.getButton() != MouseEvent.BUTTON1)
-                    return;
-                mousePressed = false;
-                if (!isVisible())
-                    return;
-                for (int i = data.size(); i > 0; --i) {
-                    ImageEntry e = data.get(i-1);
-                    if (e.pos == null) {
-                        continue;
-                    }
-                    Point p = Main.map.mapView.getPoint(e.pos);
-                    Rectangle r = new Rectangle(p.x-ICON_SIZE/2, p.y-ICON_SIZE/2, ICON_SIZE, ICON_SIZE);
-                    if (r.contains(ev.getPoint())) {
-                        showImage(i-1);
-                        break;
-                    }
-                }
-                Main.map.mapView.repaint();
-            }
-        };
-        Main.map.mapView.addMouseListener(mouseAdapter);
-        Layer.listeners.add(new LayerChangeListener(){
-            public void activeLayerChange(Layer oldLayer, Layer newLayer) {}
-            public void layerAdded(Layer newLayer) {}
-            public void layerRemoved(Layer oldLayer) {
-                if (oldLayer == self) {
-                    Main.map.mapView.removeMouseListener(mouseAdapter);
-                }
-            }
-        });
-    }
-
-    private class ImageViewerDialog {
-
-        private int currentImage;
-        private ImageEntry currentImageEntry;
-
-        private final JDialog dlg;
-        private final JButton nextButton;
-        private final JButton prevButton;
-        private final JToggleButton scaleToggle;
-        private final JToggleButton centerToggle;
-        private final JViewport imageViewport;
-        private final JLabel imageLabel;
-
-        private class ImageAction implements ActionListener {
-
-            private final int offset;
-
-            public ImageAction(int offset) {
-                this.offset = offset;
-            }
-
-            public void actionPerformed(ActionEvent e) {
-                showImage(currentImage + offset);
-            }
-
-        }
-
-        public ImageViewerDialog(ImageEntry firstImage) {
-            final JPanel p = new JPanel(new BorderLayout());
-            imageLabel = new JLabel(new ImageIcon(imageLoader.waitForImage(firstImage.image, 580)));
-            final JScrollPane scroll = new JScrollPane(imageLabel);
-            scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
-            scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
-            imageViewport = scroll.getViewport();
-            p.add(scroll, BorderLayout.CENTER);
-
-            scaleToggle = new JToggleButton(ImageProvider.get("dialogs", "zoom-best-fit"));
-            nextButton  = new JButton(ImageProvider.get("dialogs", "next"));
-            prevButton = new JButton(ImageProvider.get("dialogs", "previous"));
-            centerToggle = new JToggleButton(ImageProvider.get("dialogs", "centreview"));
-
-            JPanel p2 = new JPanel();
-            p2.add(prevButton);
-            p2.add(scaleToggle);
-            p2.add(centerToggle);
-            p2.add(nextButton);
-            p.add(p2, BorderLayout.SOUTH);
-            final JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE);
-            dlg = pane.createDialog(Main.parent, "");
-            scaleToggle.addActionListener(new ImageAction(0));
-            scaleToggle.setSelected(true);
-            centerToggle.addActionListener(new ImageAction(0));
-
-            nextButton.setActionCommand("Next");
-            prevButton.setActionCommand("Previous");
-            nextButton.setMnemonic(KeyEvent.VK_RIGHT);
-            prevButton.setMnemonic(KeyEvent.VK_LEFT);
-            scaleToggle.setMnemonic(KeyEvent.VK_F);
-            centerToggle.setMnemonic(KeyEvent.VK_C);
-            nextButton.setToolTipText("Show next image");
-            prevButton.setToolTipText("Show previous image");
-            centerToggle.setToolTipText("Centre image location in main display");
-            scaleToggle.setToolTipText("Scale image to fit");
-
-            prevButton.addActionListener(new ImageAction(-1));
-            nextButton.addActionListener(new ImageAction(1));
-            centerToggle.setSelected(false);
-
-            dlg.addComponentListener(new ComponentListener() {
-                boolean ignoreEvent = true;
-                public void componentHidden(ComponentEvent e) {}
-                public void componentMoved(ComponentEvent e) {}
-                public void componentResized(ComponentEvent ev) {
-                    // we ignore the first resize event, as the picture is scaled already on load:
-                    if (scaleToggle.getModel().isSelected() && !ignoreEvent) {
-                        imageLabel.setIcon(new ImageIcon(imageLoader.waitForImage(currentImageEntry.image,
-                                Math.max(imageViewport.getWidth(), imageViewport.getHeight()))));
-                    }
-                    ignoreEvent = false;
-                }
-                public void componentShown(ComponentEvent e) {}
-
-            });
-            dlg.setModal(false);
-            dlg.setResizable(true);
-            dlg.pack();
-        }
-
-        public void showImage(int index) {
-            dlg.setVisible(true);
-            dlg.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-
-            if (index < 0) {
-                index = 0;
-            } else if (index >= data.size() - 1) {
-                index = data.size() - 1;
-            }
-
-            currentImage = index;
-            currentImageEntry = data.get(currentImage);
-
-            prevButton.setEnabled(currentImage > 0);
-            nextButton.setEnabled(currentImage < data.size() - 1);
-
-            if (scaleToggle.getModel().isSelected()) {
-                imageLabel.setIcon(new ImageIcon(imageLoader.waitForImage(currentImageEntry.image,
-                        Math.max(imageViewport.getWidth(), imageViewport.getHeight()))));
-            } else {
-                imageLabel.setIcon(new ImageIcon(imageLoader.waitForImage(currentImageEntry.image)));
-            }
-
-            if (centerToggle.getModel().isSelected()) {
-                Main.map.mapView.zoomTo(currentImageEntry.pos);
-            }
-
-            dlg.setTitle(currentImageEntry.image +
-                    " (" + currentImageEntry.pos.toDisplayString() + ")");
-            dlg.setCursor(Cursor.getDefaultCursor());
-        }
-
-    }
-
-    private void showImage(int i) {
-        if (imageViewerDialog == null) {
-            imageViewerDialog = new ImageViewerDialog(data.get(i));
-        }
-        imageViewerDialog.showImage(i);
-    }
-
-    @Override public Icon getIcon() {
-        return ImageProvider.get("layer", "tagimages_small");
-    }
-
-    @Override public Object getInfoComponent() {
-        JPanel p = new JPanel(new GridBagLayout());
-        p.add(new JLabel(getToolTipText()), GBC.eop());
-
-        p.add(new JLabel(tr("GPS start: {0}",dateFormat.format(gps.getFirst().time))), GBC.eol());
-        p.add(new JLabel(tr("GPS end: {0}",dateFormat.format(gps.getLast().time))), GBC.eop());
-
-        p.add(new JLabel(tr("current delta: {0}s",(delta/1000.0))), GBC.eol());
-        p.add(new JLabel(tr("timezone difference: ")+(gpstimezone>0?"+":"")+(gpstimezone/1000/60/60)), GBC.eop());
-
-        JList img = new JList(data.toArray());
-        img.setCellRenderer(new DefaultListCellRenderer(){
-            @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-                ImageEntry e = (ImageEntry)value;
-                setIcon(new ImageIcon(e.getIcon()));
-                setText(e.image.getName()+" ("+dateFormat.format(new Date(e.time.getTime()+(delta+gpstimezone)))+")");
-                if (e.pos == null) {
-                    setForeground(Color.red);
-                }
-                return this;
-            }
-        });
-        img.setVisibleRowCount(5);
-        p.add(new JScrollPane(img), GBC.eop().fill(GBC.BOTH));
-        return p;
-    }
-
-    @Override public String getToolTipText() {
-        int i = 0;
-        for (ImageEntry e : data)
-            if (e.pos != null) {
-                i++;
-            }
-        return data.size()+" "+trn("image","images",data.size())+". "+tr("{0} within the track.",i);
-    }
-
-    @Override public boolean isMergable(Layer other) {
-        return other instanceof GeoImageLayer;
-    }
-
-    @Override public void mergeFrom(Layer from) {
-        GeoImageLayer l = (GeoImageLayer)from;
-        data.addAll(l.data);
-    }
-
-    @Override public void paint(Graphics2D g, MapView mv, Bounds box) {
-        int clickedIndex = -1;
-
-        // First select beveled icon (for cases where are more icons on the same spot)
-        Point mousePosition = mv.getMousePosition();
-        if (mousePosition != null  && mousePressed) {
-            for (int i = data.size() - 1; i >= 0; i--) {
-                ImageEntry e = data.get(i);
-                if (e.pos == null) {
-                    continue;
-                }
-
-                Point p = mv.getPoint(e.pos);
-                Rectangle r = new Rectangle(p.x-ICON_SIZE / 2, p.y-ICON_SIZE / 2, ICON_SIZE, ICON_SIZE);
-                if (r.contains(mousePosition)) {
-                    clickedIndex = i;
-                    break;
-                }
-            }
-        }
-
-        for (int i = 0; i < data.size(); i++) {
-            ImageEntry e = data.get(i);
-            if (e.pos != null) {
-                Point p = mv.getPoint(e.pos);
-                Rectangle r = new Rectangle(p.x-ICON_SIZE / 2, p.y-ICON_SIZE / 2, ICON_SIZE, ICON_SIZE);
-                g.drawImage(e.getIcon(), r.x, r.y, null);
-                Border b = null;
-                if (i == clickedIndex) {
-                    b = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
-                } else {
-                    b = BorderFactory.createBevelBorder(BevelBorder.RAISED);
-                }
-                Insets inset = b.getBorderInsets(mv);
-                r.grow((inset.top+inset.bottom)/2, (inset.left+inset.right)/2);
-                b.paintBorder(mv, g, r.x, r.y, r.width, r.height);
-            }
-        }
-    }
-
-    @Override public void visitBoundingBox(BoundingXYVisitor v) {
-        for (ImageEntry e : data) {
-            v.visit(e.pos);
-        }
-    }
-
-    @Override public Component[] getMenuEntries() {
-        JMenuItem sync = new JMenuItem(tr("Sync clock"), ImageProvider.get("clock"));
-        sync.addActionListener(new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
-                fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
-                fc.setAcceptAllFileFilterUsed(false);
-                fc.setFileFilter(new FileFilter(){
-                    @Override public boolean accept(File f) {
-                        return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
-                    }
-                    @Override public String getDescription() {
-                        return tr("JPEG images (*.jpg)");
-                    }
-                });
-                fc.showOpenDialog(Main.parent);
-                File sel = fc.getSelectedFile();
-                if (sel == null)
-                    return;
-                Main.pref.put("tagimages.lastdirectory", sel.getPath());
-                sync(sel);
-                Main.map.repaint();
-            }
-        });
-        return new Component[]{
-                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
-                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
-                new JSeparator(),
-                sync,
-                new JSeparator(),
-                new JMenuItem(new RenameLayerAction(null, this)),
-                new JSeparator(),
-                new JMenuItem(new LayerListPopup.InfoAction(this))};
-    }
-
-    private void calculatePosition() {
-        for (ImageEntry e : data) {
-            TimedPoint lastTP = null;
-            for (TimedPoint tp : gps) {
-                Date time = new Date(tp.time.getTime() - (delta+gpstimezone));
-                if (time.after(e.time) && lastTP != null) {
-                    e.pos = new CachedLatLon(lastTP.pos.getCenter(tp.pos));
-                    break;
-                }
-                lastTP = tp;
-            }
-            if (e.pos == null) {
-                e.pos = gps.getLast().pos;
-            }
-        }
-    }
-
-    private void sync(File f) {
-        Date exifDate;
-        try {
-            exifDate = ExifReader.readTime(f);
-        } catch (ParseException e) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("The date in file \"{0}\" could not be parsed.", f.getName()),
-                    tr("Error"),
-                    JOptionPane.ERROR_MESSAGE
-            );
-            return;
-        }
-        if (exifDate == null) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("There is no EXIF time within the file \"{0}\".", f.getName()),
-                    tr("Error"),
-                    JOptionPane.ERROR_MESSAGE
-            );
-            return;
-        }
-        JPanel p = new JPanel(new GridBagLayout());
-        p.add(new JLabel(tr("Image")), GBC.eol());
-        p.add(new JLabel(new ImageIcon(imageLoader.waitForImage(f, 300))), GBC.eop());
-        p.add(new JLabel(tr("Enter shown date (mm/dd/yyyy HH:MM:SS)")), GBC.eol());
-        JTextField gpsText = new JTextField(dateFormat.format(new Date(exifDate.getTime()+delta)));
-        p.add(gpsText, GBC.eol().fill(GBC.HORIZONTAL));
-        p.add(new JLabel(tr("GPS unit timezone (difference to photo)")), GBC.eol());
-        String t = Main.pref.get("tagimages.gpstimezone", "0");
-        if (t.charAt(0) != '-') {
-            t = "+"+t;
-        }
-        JTextField gpsTimezone = new JTextField(t);
-        p.add(gpsTimezone, GBC.eol().fill(GBC.HORIZONTAL));
-
-        while (true) {
-            int answer = JOptionPane.showConfirmDialog(
-                    Main.parent,
-                    p,
-                    tr("Synchronize Time with GPS Unit"),
-                    JOptionPane.OK_CANCEL_OPTION,
-                    JOptionPane.QUESTION_MESSAGE
-            );
-            if (answer != JOptionPane.OK_OPTION || gpsText.getText().equals(""))
-                return;
-            try {
-                delta = DateParser.parse(gpsText.getText()).getTime() - exifDate.getTime();
-                String time = gpsTimezone.getText();
-                if (!time.equals("") && time.charAt(0) == '+') {
-                    time = time.substring(1);
-                }
-                if (time.equals("")) {
-                    time = "0";
-                }
-                gpstimezone = Long.valueOf(time)*60*60*1000;
-                Main.pref.put("tagimages.delta", ""+delta);
-                Main.pref.put("tagimages.gpstimezone", time);
-                calculatePosition();
-                return;
-            } catch (NumberFormatException x) {
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("Time entered could not be parsed."),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                );
-            } catch (ParseException x) {
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("Time entered could not be parsed."),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                );
-            }
-        }
-    }
-
-}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 2565)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 2566)
@@ -64,4 +64,5 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
 import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
@@ -298,5 +299,5 @@
         });
 
-        JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
+        JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
         tagimage.putClientProperty("help", ht("/Action/ImportImages"));
         tagimage.addActionListener(new ActionListener() {
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 2566)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 2566)
@@ -0,0 +1,1175 @@
+// License: GPL. See LICENSE file for details.
+// Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+
+import javax.swing.AbstractListModel;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.io.GpxReader;
+import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.PrimaryDateParser;
+import org.xml.sax.SAXException;
+
+
+/** This class displays the window to select the GPX file and the offset (timezone + delta).
+ * Then it correlates the images of the layer with that GPX file.
+ */
+public class CorrelateGpxWithImages implements ActionListener {
+
+    private static List<GpxData> loadedGpxData = new ArrayList<GpxData>();
+
+    public static class CorrelateParameters {
+        GpxData gpxData;
+        float timezone;
+        long offset;
+    }
+
+    GeoImageLayer yLayer = null;
+
+    private static class GpxDataWrapper {
+        String name;
+        GpxData data;
+        File file;
+
+        public GpxDataWrapper(String name, GpxData data, File file) {
+            this.name = name;
+            this.data = data;
+            this.file = file;
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    Vector gpxLst = new Vector();
+    JPanel panel = null;
+    JComboBox cbGpx = null;
+    JTextField tfTimezone = null;
+    JTextField tfOffset = null;
+    JRadioButton rbAllImg = null;
+    JRadioButton rbUntaggedImg = null;
+    JRadioButton rbNoExifImg = null;
+
+    /** This class is called when the user doesn't find the GPX file he needs in the files that have
+     * been loaded yet. It displays a FileChooser dialog to select the GPX file to be loaded.
+     */
+    private class LoadGpxDataActionListener implements ActionListener {
+
+        public void actionPerformed(ActionEvent arg0) {
+            JFileChooser fc = new JFileChooser(Main.pref.get("lastDirectory"));
+            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+            fc.setAcceptAllFileFilterUsed(false);
+            fc.setMultiSelectionEnabled(false);
+            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+            fc.setFileFilter(new FileFilter(){
+                @Override public boolean accept(File f) {
+                    return (f.isDirectory()
+                            || f .getName().toLowerCase().endsWith(".gpx")
+                            || f.getName().toLowerCase().endsWith(".gpx.gz"));
+                }
+                @Override public String getDescription() {
+                    return tr("GPX Files (*.gpx *.gpx.gz)");
+                }
+            });
+            fc.showOpenDialog(Main.parent);
+            File sel = fc.getSelectedFile();
+            if (sel == null)
+                return;
+
+            try {
+                panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+                Main.pref.put("lastDirectory", sel.getPath());
+
+                for (int i = gpxLst.size() - 1 ; i >= 0 ; i--) {
+                    if (gpxLst.get(i) instanceof GpxDataWrapper) {
+                        GpxDataWrapper wrapper = (GpxDataWrapper) gpxLst.get(i);
+                        if (sel.equals(wrapper.file)) {
+                            cbGpx.setSelectedIndex(i);
+                            if (!sel.getName().equals(wrapper.name)) {
+                                JOptionPane.showMessageDialog(
+                                		Main.parent,
+                                        tr("File {0} is loaded yet under the name \"{1}\"", sel.getName(), wrapper.name),
+                                        tr("Error"),
+                                        JOptionPane.ERROR_MESSAGE
+                                        );
+                            }
+                            return;
+                        }
+                    }
+                }
+                GpxData data = null;
+                try {
+                    InputStream iStream;
+                    if (sel.getName().toLowerCase().endsWith(".gpx.gz")) {
+                        iStream = new GZIPInputStream(new FileInputStream(sel));
+                    } else {
+                        iStream = new FileInputStream(sel);
+                    }
+                    data = new GpxReader(iStream, sel).data;
+                    data.storageFile = sel;
+
+                } catch (SAXException x) {
+                    x.printStackTrace();
+                    JOptionPane.showMessageDialog(
+                    		Main.parent, 
+                    		tr("Error while parsing {0}",sel.getName())+": "+x.getMessage(),
+                    		tr("Error"),
+                    		JOptionPane.ERROR_MESSAGE
+                    		);
+                    return;
+                } catch (IOException x) {
+                    x.printStackTrace();
+                    JOptionPane.showMessageDialog(
+                    		Main.parent, 
+                    		tr("Could not read \"{0}\"",sel.getName())+"\n"+x.getMessage(),
+                    		tr("Error"),
+                    		JOptionPane.ERROR_MESSAGE
+                    		);
+                    return;
+                }
+
+                loadedGpxData.add(data);
+                if (gpxLst.get(0) instanceof String) {
+                    gpxLst.remove(0);
+                }
+                gpxLst.add(new GpxDataWrapper(sel.getName(), data, sel));
+                cbGpx.setSelectedIndex(cbGpx.getItemCount() - 1);
+            } finally {
+                panel.setCursor(Cursor.getDefaultCursor());
+            }
+        }
+    }
+
+    /** This action listener is called when the user has a photo of the time of his GPS receiver. It
+     * displays the list of photos of the layer, and upon selection displays the selected photo.
+     * From that photo, the user can key in the time of the GPS.
+     * Then values of timezone and delta are set.
+     * @author chris
+     *
+     */
+    private class SetOffsetActionListener implements ActionListener {
+        JPanel panel;
+        JLabel lbExifTime;
+        JTextField tfGpsTime;
+        JComboBox cbTimezones;
+        ImageDisplay imgDisp;
+        JList imgList;
+
+        public void actionPerformed(ActionEvent arg0) {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+
+            panel = new JPanel();
+            panel.setLayout(new BorderLayout());
+            panel.add(new JLabel(tr("<html>Take a photo of your GPS receiver while it displays the time.<br>"
+                                    + "Display that photo here.<br>"
+                                    + "And then, simply capture the time you read on the photo and select a timezone<hr></html>")),
+                                    BorderLayout.NORTH);
+
+            imgDisp = new ImageDisplay();
+            imgDisp.setPreferredSize(new Dimension(300, 225));
+            panel.add(imgDisp, BorderLayout.CENTER);
+
+            JPanel panelTf = new JPanel();
+            panelTf.setLayout(new GridBagLayout());
+
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.gridx = gc.gridy = 0;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("Photo time (from exif):")), gc);
+
+            lbExifTime = new JLabel();
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.gridwidth = 2;
+            panelTf.add(lbExifTime, gc);
+
+            gc.gridx = 0;
+            gc.gridy = 1;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("Gps time (read from the above photo): ")), gc);
+
+            tfGpsTime = new JTextField();
+            tfGpsTime.setEnabled(false);
+            tfGpsTime.setMinimumSize(new Dimension(150, tfGpsTime.getMinimumSize().height));
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            panelTf.add(tfGpsTime, gc);
+
+            gc.gridx = 2;
+            gc.weightx = 0.2;
+            panelTf.add(new JLabel(tr(" [dd/mm/yyyy hh:mm:ss]")), gc);
+
+            gc.gridx = 0;
+            gc.gridy = 2;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("I'm in the timezone of: ")), gc);
+
+            Vector vtTimezones = new Vector<String>();
+            String[] tmp = TimeZone.getAvailableIDs();
+
+            for (String tzStr : tmp) {
+                TimeZone tz = TimeZone.getTimeZone(tzStr);
+
+                String tzDesc = new StringBuffer(tzStr).append(" (")
+                                        .append(formatTimezone(tz.getRawOffset() / 3600000.0))
+                                        .append(')').toString();
+                vtTimezones.add(tzDesc);
+            }
+
+            Collections.sort(vtTimezones);
+
+            cbTimezones = new JComboBox(vtTimezones);
+
+            String tzId = Main.pref.get("tagimages.timezoneid", "");
+            TimeZone defaultTz;
+            if (tzId.length() == 0) {
+                defaultTz = TimeZone.getDefault();
+            } else {
+                defaultTz = TimeZone.getTimeZone(tzId);
+            }
+
+            cbTimezones.setSelectedItem(new StringBuffer(defaultTz.getID()).append(" (")
+                    .append(formatTimezone(defaultTz.getRawOffset() / 3600000.0))
+                    .append(')').toString());
+
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.gridwidth = 2;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            panelTf.add(cbTimezones, gc);
+
+            panel.add(panelTf, BorderLayout.SOUTH);
+
+            JPanel panelLst = new JPanel();
+            panelLst.setLayout(new BorderLayout());
+
+            imgList = new JList(new AbstractListModel() {
+                public Object getElementAt(int i) {
+                    return yLayer.data.get(i).file.getName();
+                }
+
+                public int getSize() {
+                    return yLayer.data.size();
+                }
+            });
+            imgList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+            imgList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+                public void valueChanged(ListSelectionEvent arg0) {
+                    int index = imgList.getSelectedIndex();
+                    imgDisp.setImage(yLayer.data.get(index).file);
+                    Date date = yLayer.data.get(index).time;
+                    if (date != null) {
+                        lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
+                        tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));
+                        tfGpsTime.setCaretPosition(tfGpsTime.getText().length());
+                        tfGpsTime.setEnabled(true);
+                    } else {
+                        lbExifTime.setText(tr("No date"));
+                        tfGpsTime.setText("");
+                        tfGpsTime.setEnabled(false);
+                    }
+                }
+
+            });
+            panelLst.add(new JScrollPane(imgList), BorderLayout.CENTER);
+
+            JButton openButton = new JButton(tr("Open an other photo"));
+            openButton.addActionListener(new ActionListener() {
+
+                public void actionPerformed(ActionEvent arg0) {
+                    JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
+                    fc.setAcceptAllFileFilterUsed(false);
+                    fc.setMultiSelectionEnabled(false);
+                    fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                    fc.setFileFilter(JpegFileFilter.getInstance());
+                    fc.showOpenDialog(Main.parent);
+                    File sel = fc.getSelectedFile();
+                    if (sel == null) {
+                        return;
+                    }
+
+                    imgDisp.setImage(sel);
+
+                    Date date = null;
+                    try {
+                        date = ExifReader.readTime(sel);
+                    } catch (Exception e) {
+                    }
+                    if (date != null) {
+                        lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
+                        tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));
+                        tfGpsTime.setEnabled(true);
+                    } else {
+                        lbExifTime.setText(tr("No date"));
+                        tfGpsTime.setText("");
+                        tfGpsTime.setEnabled(false);
+                    }
+                }
+            });
+            panelLst.add(openButton, BorderLayout.PAGE_END);
+
+            panel.add(panelLst, BorderLayout.LINE_START);
+
+            boolean isOk = false;
+            while (! isOk) {
+                int answer = JOptionPane.showConfirmDialog(
+                		Main.parent, panel, 
+                		tr("Synchronize time from a photo of the GPS receiver"), 
+                		JOptionPane.OK_CANCEL_OPTION,
+                		JOptionPane.QUESTION_MESSAGE
+                		);
+                if (answer == JOptionPane.CANCEL_OPTION) {
+                    return;
+                }
+
+                long delta;
+
+                try {
+                    delta = dateFormat.parse(lbExifTime.getText()).getTime()
+                            - dateFormat.parse(tfGpsTime.getText()).getTime();
+                } catch(ParseException e) {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing the date.\n"
+                            + "Please use the requested format"),
+                            tr("Invalid date"), JOptionPane.ERROR_MESSAGE );
+                    continue;
+                }
+
+                String selectedTz = (String) cbTimezones.getSelectedItem();
+                int pos = selectedTz.lastIndexOf('(');
+                tzId = selectedTz.substring(0, pos - 1);
+                String tzValue = selectedTz.substring(pos + 1, selectedTz.length() - 1);
+
+                Main.pref.put("tagimages.timezoneid", tzId);
+                tfOffset.setText(Long.toString(delta / 1000));
+                tfTimezone.setText(tzValue);
+
+                isOk = true;
+
+            }
+
+        }
+    }
+
+    public CorrelateGpxWithImages(GeoImageLayer layer) {
+        this.yLayer = layer;
+    }
+
+    public void actionPerformed(ActionEvent arg0) {
+        // Construct the list of loaded GPX tracks
+        Collection<Layer> layerLst = Main.main.map.mapView.getAllLayers();
+        Iterator<Layer> iterLayer = layerLst.iterator();
+        while (iterLayer.hasNext()) {
+            Layer cur = iterLayer.next();
+            if (cur instanceof GpxLayer) {
+                gpxLst.add(new GpxDataWrapper(((GpxLayer) cur).getName(),
+                                              ((GpxLayer) cur).data,
+                                              ((GpxLayer) cur).data.storageFile));
+            }
+        }
+        for (GpxData data : loadedGpxData) {
+            gpxLst.add(new GpxDataWrapper(data.storageFile.getName(),
+                                          data,
+                                          data.storageFile));
+        }
+
+        if (gpxLst.size() == 0) {
+            gpxLst.add(tr("<No GPX track loaded yet>"));
+        }
+
+        JPanel panelCb = new JPanel();
+        panelCb.setLayout(new FlowLayout());
+
+        panelCb.add(new JLabel(tr("GPX track: ")));
+
+        cbGpx = new JComboBox(gpxLst);
+        panelCb.add(cbGpx);
+
+        JButton buttonOpen = new JButton(tr("Open another GPX trace"));
+        buttonOpen.setIcon(ImageProvider.get("dialogs/geoimage/geoimage-open"));
+        buttonOpen.addActionListener(new LoadGpxDataActionListener());
+
+        panelCb.add(buttonOpen);
+
+        JPanel panelTf = new JPanel();
+        panelTf.setLayout(new GridBagLayout());
+
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.anchor = GridBagConstraints.WEST;
+
+        gc.gridx = gc.gridy = 0;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Timezone: ")), gc);
+
+        float gpstimezone = Float.parseFloat(Main.pref.get("tagimages.doublegpstimezone", "0.0"));
+        if (gpstimezone == 0.0) {
+            gpstimezone = - Long.parseLong(Main.pref.get("tagimages.gpstimezone", "0"));
+        }
+        tfTimezone = new JTextField();
+        tfTimezone.setText(formatTimezone(gpstimezone));
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        panelTf.add(tfTimezone, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Offset:")), gc);
+
+        long delta = Long.parseLong(Main.pref.get("tagimages.delta", "0")) / 1000;
+        tfOffset = new JTextField();
+        tfOffset.setText(Long.toString(delta));
+        gc.gridx = gc.gridy = 1;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        panelTf.add(tfOffset, gc);
+
+        JButton buttonViewGpsPhoto = new JButton(tr("<html>I can take a picture of my GPS receiver.<br>"
+                                                    + "Can this help?</html>"));
+        buttonViewGpsPhoto.addActionListener(new SetOffsetActionListener());
+        gc.gridx = 2;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 2;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        panelTf.add(buttonViewGpsPhoto, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Update position for: ")), gc);
+
+        gc.gridx = 1;
+        gc.gridy = 2;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbAllImg = new JRadioButton(tr("All images"));
+        panelTf.add(rbAllImg, gc);
+
+        gc.gridx = 1;
+        gc.gridy = 3;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbNoExifImg = new JRadioButton(tr("Images with no exif position"));
+        panelTf.add(rbNoExifImg, gc);
+
+        gc.gridx = 1;
+        gc.gridy = 4;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbUntaggedImg = new JRadioButton(tr("Not yet tagged images"));
+        panelTf.add(rbUntaggedImg, gc);
+
+        ButtonGroup group = new ButtonGroup();
+        group.add(rbAllImg);
+        group.add(rbNoExifImg);
+        group.add(rbUntaggedImg);
+
+        rbUntaggedImg.setSelected(true);
+
+        panel = new JPanel();
+        panel.setLayout(new BorderLayout());
+
+        panel.add(panelCb, BorderLayout.PAGE_START);
+        panel.add(panelTf, BorderLayout.CENTER);
+
+        boolean isOk = false;
+        GpxDataWrapper selectedGpx = null;
+        while (! isOk) {
+        	ExtendedDialog dialog = new ExtendedDialog(
+        			Main.parent,
+                tr("Correlate images with GPX track"),
+                new String[] { tr("Correlate"), tr("Auto-Guess"), tr("Cancel") }
+        			);
+
+        	dialog.setContent(panel);
+        	dialog.setButtonIcons(new String[] { "ok.png", "dialogs/geoimage/gpx2imgManual.png", "cancel.png" });
+        	dialog.showDialog();
+        	int answer = dialog.getValue();
+            if(answer != 1 && answer != 2)
+                return;
+
+            // Check the selected values
+            Object item = cbGpx.getSelectedItem();
+
+            if (item == null || ! (item instanceof GpxDataWrapper)) {
+                JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"),
+                                              tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE );
+                continue;
+            }
+            selectedGpx = ((GpxDataWrapper) item);
+
+            if (answer == 2) {
+                autoGuess(selectedGpx.data);
+                return;
+            }
+
+            Float timezoneValue = parseTimezone(tfTimezone.getText().trim());
+            if (timezoneValue == null) {
+                JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM"),
+                        tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE);
+                continue;
+            }
+            gpstimezone = timezoneValue.floatValue();
+
+            String deltaText = tfOffset.getText().trim();
+            if (deltaText.length() > 0) {
+                try {
+                    if(deltaText.startsWith("+"))
+                        deltaText = deltaText.substring(1);
+                    delta = Long.parseLong(deltaText);
+                } catch(NumberFormatException nfe) {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing offset.\nExpected format: {0}", "number"),
+                            tr("Invalid offset"), JOptionPane.ERROR_MESSAGE);
+                    continue;
+                }
+            } else {
+                delta = 0;
+            }
+
+            Main.pref.put("tagimages.doublegpstimezone", Double.toString(gpstimezone));
+            Main.pref.put("tagimages.gpstimezone", Long.toString(- ((long) gpstimezone)));
+            Main.pref.put("tagimages.delta", Long.toString(delta * 1000));
+
+            isOk = true;
+        }
+
+        // Construct a list of images that have a date, and sort them on the date.
+        ArrayList<ImageEntry> dateImgLst = getSortedImgList(rbAllImg.isSelected(), rbNoExifImg.isSelected());
+
+        int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600) + delta);
+
+        // Search whether an other layer has yet defined some bounding box.
+        // If none, we'll zoom to the bounding box of the layer with the photos.
+        boolean boundingBoxedLayerFound = false;
+        for (Layer l: Main.map.mapView.getAllLayers()) {
+            if (l != yLayer) {
+                BoundingXYVisitor bbox = new BoundingXYVisitor();
+                l.visitBoundingBox(bbox);
+                if (bbox.getBounds() != null) {
+                    boundingBoxedLayerFound = true;
+                    break;
+                }
+            }
+        }
+        if (! boundingBoxedLayerFound) {
+            BoundingXYVisitor bbox = new BoundingXYVisitor();
+            yLayer.visitBoundingBox(bbox);
+            Main.map.mapView.recalculateCenterScale(bbox);
+        }
+
+        Main.map.repaint();
+
+        JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matches of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name),
+                tr("GPX Track loaded"),
+                ((dateImgLst.size() > 0 && matched == 0) ? JOptionPane.WARNING_MESSAGE
+                                                         : JOptionPane.INFORMATION_MESSAGE));
+
+    }
+
+    // These variables all belong to "auto guess" but need to be accessible
+    // from the slider change listener
+    private int dayOffset;
+    private JLabel lblMatches;
+    private JLabel lblOffset;
+    private JLabel lblTimezone;
+    private JLabel lblMinutes;
+    private JLabel lblSeconds;
+    private JSlider sldTimezone;
+    private JSlider sldMinutes;
+    private JSlider sldSeconds;
+    private GpxData autoGpx;
+    private ArrayList<ImageEntry> autoImgs;
+    private long firstGPXDate = -1;
+    private long firstExifDate = -1;
+
+    /**
+     * Tries to automatically match opened photos to a given GPX track. Changes are applied
+     * immediately. Presents dialog with sliders for manual adjust.
+     * @param GpxData The GPX track to match against
+     */
+    private void autoGuess(GpxData gpx) {
+        autoGpx = gpx;
+        autoImgs = getSortedImgList(true, false);
+        PrimaryDateParser dateParser = new PrimaryDateParser();
+
+        // no images found, exit
+        if(autoImgs.size() <= 0) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("The selected photos don't contain time information."),
+                tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        ImageViewerDialog dialog = ImageViewerDialog.getInstance();
+        dialog.showDialog();
+        // Will show first photo if none is selected yet
+        if(!dialog.hasImage())
+            yLayer.showNextPhoto();
+        // FIXME: If the dialog is minimized it will not be maximized. ToggleDialog is
+        // in need of a complete re-write to allow this in a reasonable way.
+
+        // Init variables
+        firstExifDate = autoImgs.get(0).time.getTime()/1000;
+
+
+        // Finds first GPX point
+        outer: for (GpxTrack trk : gpx.tracks) {
+            for (Collection<WayPoint> segment : trk.trackSegs) {
+                for (WayPoint curWp : segment) {
+                    String curDateWpStr = (String) curWp.attr.get("time");
+                    if (curDateWpStr == null) continue;
+
+                    try {
+                        firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000;
+                        break outer;
+                    } catch(Exception e) {}
+                }
+            }
+        }
+
+        // No GPX timestamps found, exit
+        if(firstGPXDate < 0) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("The selected GPX track doesn't contain timestamps. Please select another one."),
+                tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        // seconds
+        long diff = (yLayer.hasTimeoffset)
+            ? yLayer.timeoffset
+            : firstExifDate - firstGPXDate;
+        yLayer.timeoffset = diff;
+        yLayer.hasTimeoffset = true;
+
+        double diffInH = (double)diff/(60*60);    // hours
+
+        // Find day difference
+        dayOffset = (int)Math.round(diffInH / 24); // days
+        double timezone = diff - dayOffset*24*60*60;  // seconds
+
+        // In hours, rounded to two decimal places
+        timezone = (double)Math.round(timezone*100/(60*60)) / 100;
+
+        // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
+        // -2 minutes offset. This determines the real timezone and finds offset.
+        double fixTimezone = (double)Math.round(timezone * 2)/2; // hours, rounded to one decimal place
+        int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds
+
+        /*System.out.println("phto " + firstExifDate);
+        System.out.println("gpx  " + firstGPXDate);
+        System.out.println("diff " + diff);
+        System.out.println("difh " + diffInH);
+        System.out.println("days " + dayOffset);
+        System.out.println("time " + timezone);
+        System.out.println("fix  " + fixTimezone);
+        System.out.println("offt " + offset);*/
+
+        // This is called whenever one of the sliders is moved.
+        // It updates the labels and also calls the "match photos" code
+        class sliderListener implements ChangeListener {
+            public void stateChanged(ChangeEvent e) {
+                // parse slider position into real timezone
+                double tz = Math.abs(sldTimezone.getValue());
+                String zone = tz % 2 == 0
+                    ? (int)Math.floor(tz/2) + ":00"
+                    : (int)Math.floor(tz/2) + ":30";
+                if(sldTimezone.getValue() < 0) zone = "-" + zone;
+
+                lblTimezone.setText(tr("Timezone: {0}", zone));
+                lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
+                lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue()));
+
+                float gpstimezone = parseTimezone(zone).floatValue();
+
+                // Reset previous position
+                for(ImageEntry x : autoImgs) {
+                    x.pos = null;
+                }
+
+                long timediff = (long) (gpstimezone * 3600)
+                        + dayOffset*24*60*60
+                        + sldMinutes.getValue()*60
+                        + sldSeconds.getValue();
+
+                int matched = matchGpxTrack(autoImgs, autoGpx, timediff);
+
+                lblMatches.setText(
+                    tr("Matched {0} of {1} photos to GPX track.", matched, autoImgs.size())
+                    + ((Math.abs(dayOffset) == 0)
+                        ? ""
+                        : " " + tr("(Time difference of {0} days)", Math.abs(dayOffset))
+                      )
+                );
+
+                int offset = (int)(firstGPXDate+timediff-firstExifDate);
+                int o = Math.abs(offset);
+                lblOffset.setText(
+                    tr("Offset between track and photos: {0}m {1}s",
+                          (offset < 0 ? "-" : "") + Long.toString(Math.round(o/60)),
+                          Long.toString(Math.round(o%60))
+                    )
+                );
+
+                yLayer.timeoffset = timediff;
+                Main.main.map.repaint();
+            }
+        }
+
+        // Info Labels
+        lblMatches = new JLabel();
+        lblOffset = new JLabel();
+
+        // Timezone Slider
+        // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes
+        // steps. Therefore the range is -24 to 24.
+        lblTimezone = new JLabel();
+        sldTimezone = new JSlider(-24, 24, 0);
+        sldTimezone.setPaintLabels(true);
+        Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>();
+        labelTable.put(-24, new JLabel("-12:00"));
+        labelTable.put(-12, new JLabel( "-6:00"));
+        labelTable.put(  0, new JLabel(  "0:00"));
+        labelTable.put( 12, new JLabel(  "6:00"));
+        labelTable.put( 24, new JLabel( "12:00"));
+        sldTimezone.setLabelTable(labelTable);
+
+        // Minutes Slider
+        lblMinutes = new JLabel();
+        sldMinutes = new JSlider(-15, 15, 0);
+        sldMinutes.setPaintLabels(true);
+        sldMinutes.setMajorTickSpacing(5);
+
+        // Seconds slider
+        lblSeconds = new JLabel();
+        sldSeconds = new JSlider(-60, 60, 0);
+        sldSeconds.setPaintLabels(true);
+        sldSeconds.setMajorTickSpacing(30);
+
+        // Put everything together
+        JPanel p = new JPanel(new GridBagLayout());
+        p.setPreferredSize(new Dimension(400, 230));
+        p.add(lblMatches, GBC.eol().fill());
+        p.add(lblOffset, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblTimezone, GBC.eol().fill());
+        p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblMinutes, GBC.eol().fill());
+        p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblSeconds, GBC.eol().fill());
+        p.add(sldSeconds, GBC.eol().fill());
+
+        // If there's an error in the calculation the found values
+        // will be off range for the sliders. Catch this error
+        // and inform the user about it.
+        try {
+            sldTimezone.setValue((int)(fixTimezone*2));
+            sldMinutes.setValue(offset/60);
+            sldSeconds.setValue(offset%60);
+        } catch(Exception e) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("An error occurred while trying to match the photos to the GPX track."
+                    +" You can adjust the sliders to manually match the photos."),
+                tr("Matching photos to track failed"),
+                JOptionPane.WARNING_MESSAGE);
+        }
+
+        // Call the sliderListener once manually so labels get adjusted
+        new sliderListener().stateChanged(null);
+        // Listeners added here, otherwise it tries to match three times
+        // (when setting the default values)
+        sldTimezone.addChangeListener(new sliderListener());
+        sldMinutes.addChangeListener(new sliderListener());
+        sldSeconds.addChangeListener(new sliderListener());
+
+        // There is no way to cancel this dialog, all changes get applied
+        // immediately. Therefore "Close" is marked with an "OK" icon.
+        // Settings are only saved temporarily to the layer.
+        ExtendedDialog d = new ExtendedDialog(Main.parent,
+            tr("Adjust timezone and offset"),
+            new String[] { tr("Close"),  tr("Default Values") }
+        );
+
+        d.setContent(p);
+        d.setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"});
+        d.showDialog();
+        int answer = d.getValue();
+        // User wants default values; discard old result and re-open dialog
+        if(answer == 2) {
+            yLayer.hasTimeoffset = false;
+            autoGuess(gpx);
+        }
+    }
+
+    /**
+     * Returns a list of images that fulfill the given criteria.
+     * Default setting is to return untagged images, but may be overwritten.
+     * @param boolean all -- returns all available images
+     * @param boolean noexif -- returns untagged images without EXIF-GPS coords
+     * @return ArrayList<ImageEntry> matching images
+     */
+    private ArrayList<ImageEntry> getSortedImgList(boolean all, boolean noexif) {
+        ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size());
+        if (all) {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null) {
+                    // Reset previous position
+                    e.pos = null;
+                    dateImgLst.add(e);
+                }
+            }
+
+        } else if (noexif) {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null && e.exifCoor == null) {
+                    dateImgLst.add(e);
+                }
+            }
+
+        } else {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null && e.pos == null) {
+                    dateImgLst.add(e);
+                }
+            }
+        }
+
+        Collections.sort(dateImgLst, new Comparator<ImageEntry>() {
+            public int compare(ImageEntry arg0, ImageEntry arg1) {
+                return arg0.time.compareTo(arg1.time);
+            }
+        });
+
+        return dateImgLst;
+    }
+
+    private int matchGpxTrack(ArrayList<ImageEntry> dateImgLst, GpxData selectedGpx, long offset) {
+        int ret = 0;
+
+        PrimaryDateParser dateParser = new PrimaryDateParser();
+
+        for (GpxTrack trk : selectedGpx.tracks) {
+            for (Collection<WayPoint> segment : trk.trackSegs) {
+
+                long prevDateWp = 0;
+                WayPoint prevWp = null;
+
+                for (WayPoint curWp : segment) {
+
+                    String curDateWpStr = (String) curWp.attr.get("time");
+                    if (curDateWpStr != null) {
+
+                        try {
+                            long curDateWp = dateParser.parse(curDateWpStr).getTime()/1000 + offset;
+                            ret += matchPoints(dateImgLst, prevWp, prevDateWp, curWp, curDateWp);
+
+                            prevWp = curWp;
+                            prevDateWp = curDateWp;
+
+                        } catch(ParseException e) {
+                            System.err.println("Error while parsing date \"" + curDateWpStr + '"');
+                            e.printStackTrace();
+                            prevWp = null;
+                            prevDateWp = 0;
+                        }
+                    } else {
+                        prevWp = null;
+                        prevDateWp = 0;
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
+    private int matchPoints(ArrayList<ImageEntry> dateImgLst, WayPoint prevWp, long prevDateWp,
+                                                                   WayPoint curWp, long curDateWp) {
+        // Time between the track point and the previous one, 5 sec if first point, i.e. photos take
+        // 5 sec before the first track point can be assumed to be take at the starting position
+        long interval = prevDateWp > 0 ? ((int)Math.abs(curDateWp - prevDateWp)) : 5;
+        int ret = 0;
+
+        // i is the index of the timewise last photo that has the same or earlier EXIF time
+        int i = getLastIndexOfListBefore(dateImgLst, curDateWp);
+
+        // no photos match
+        if (i < 0)
+            return 0;
+
+        Double speed = null;
+        Double prevElevation = null;
+        Double curElevation = null;
+
+        if (prevWp != null) {
+            double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
+            // This is in km/h, 3.6 * m/s
+            if (curDateWp > prevDateWp)
+                speed = 3.6 * distance / (curDateWp - prevDateWp);
+            try {
+                prevElevation = new Double((String) prevWp.attr.get("ele"));
+            } catch(Exception e) {}
+        }
+
+        try {
+            curElevation = new Double((String) curWp.attr.get("ele"));
+        } catch (Exception e) {}
+
+        // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds
+        // before the first point will be geotagged with the starting point
+        if(prevDateWp == 0 || curDateWp <= prevDateWp) {
+            while(i >= 0 && (dateImgLst.get(i).time.getTime()/1000) <= curDateWp
+                        && (dateImgLst.get(i).time.getTime()/1000) >= (curDateWp - interval)) {
+                if(dateImgLst.get(i).pos == null) {
+                    dateImgLst.get(i).setCoor(curWp.getCoor());
+                    dateImgLst.get(i).speed = speed;
+                    dateImgLst.get(i).elevation = curElevation;
+                    ret++;
+                }
+                i--;
+            }
+            return ret;
+        }
+
+        // This code gives a simple linear interpolation of the coordinates between current and
+        // previous track point assuming a constant speed in between
+        long imgDate;
+        while(i >= 0 && (imgDate = dateImgLst.get(i).time.getTime()/1000) >= prevDateWp) {
+
+            if(dateImgLst.get(i).pos == null) {
+                // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless
+                // variable
+                double timeDiff = (double)(imgDate - prevDateWp) / interval;
+                dateImgLst.get(i).setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
+                dateImgLst.get(i).speed = speed;
+
+                if (curElevation != null && prevElevation != null)
+                    dateImgLst.get(i).elevation = prevElevation + (curElevation - prevElevation) * timeDiff;
+
+                ret++;
+            }
+            i--;
+        }
+        return ret;
+    }
+
+    private int getLastIndexOfListBefore(ArrayList<ImageEntry> dateImgLst, long searchedDate) {
+        int lstSize= dateImgLst.size();
+
+        // No photos or the first photo taken is later than the search period
+        if(lstSize == 0 || searchedDate < dateImgLst.get(0).time.getTime()/1000)
+            return -1;
+
+        // The search period is later than the last photo
+        if (searchedDate > dateImgLst.get(lstSize - 1).time.getTime() / 1000)
+            return lstSize-1;
+
+        // The searched index is somewhere in the middle, do a binary search from the beginning
+        int curIndex= 0;
+        int startIndex= 0;
+        int endIndex= lstSize-1;
+        while (endIndex - startIndex > 1) {
+            curIndex= (int) Math.round((double)(endIndex + startIndex)/2);
+            if (searchedDate > dateImgLst.get(curIndex).time.getTime()/1000)
+                startIndex= curIndex;
+            else
+                endIndex= curIndex;
+        }
+        if (searchedDate < dateImgLst.get(endIndex).time.getTime()/1000)
+            return startIndex;
+
+        // This final loop is to check if photos with the exact same EXIF time follows
+        while ((endIndex < (lstSize-1)) && (dateImgLst.get(endIndex).time.getTime()
+                                                == dateImgLst.get(endIndex + 1).time.getTime()))
+            endIndex++;
+        return endIndex;
+    }
+
+
+    private String formatTimezone(double timezone) {
+        StringBuffer ret = new StringBuffer();
+
+        if (timezone < 0) {
+            ret.append('-');
+            timezone = -timezone;
+        } else {
+            ret.append('+');
+        }
+        ret.append((long) timezone).append(':');
+        int minutes = (int) ((timezone % 1) * 60);
+        if (minutes < 10) {
+            ret.append('0');
+        }
+        ret.append(minutes);
+
+        return ret.toString();
+    }
+
+    private Float parseTimezone(String timezone) {
+        if (timezone.length() == 0) {
+            return new Float(0);
+        }
+
+        char sgnTimezone = '+';
+        StringBuffer hTimezone = new StringBuffer();
+        StringBuffer mTimezone = new StringBuffer();
+        int state = 1; // 1=start/sign, 2=hours, 3=minutes.
+        for (int i = 0; i < timezone.length(); i++) {
+            char c = timezone.charAt(i);
+            switch (c) {
+            case ' ' :
+                if (state != 2 || hTimezone.length() != 0) {
+                    return null;
+                }
+                break;
+            case '+' :
+            case '-' :
+                if (state == 1) {
+                    sgnTimezone = c;
+                    state = 2;
+                } else {
+                    return null;
+                }
+                break;
+            case ':' :
+            case '.' :
+                if (state == 2) {
+                    state = 3;
+                } else {
+                    return null;
+                }
+                break;
+            case '0' : case '1' : case '2' : case '3' : case '4' :
+            case '5' : case '6' : case '7' : case '8' : case '9' :
+                switch(state) {
+                case 1 :
+                case 2 :
+                    state = 2;
+                    hTimezone.append(c);
+                    break;
+                case 3 :
+                    mTimezone.append(c);
+                    break;
+                default :
+                    return null;
+                }
+                break;
+            default :
+                return null;
+            }
+        }
+
+        int h = 0;
+        int m = 0;
+        try {
+            h = Integer.parseInt(hTimezone.toString());
+            if (mTimezone.length() > 0) {
+                m = Integer.parseInt(mTimezone.toString());
+            }
+        } catch (NumberFormatException nfe) {
+            // Invalid timezone
+            return null;
+        }
+
+        if (h > 12 || m > 59 ) {
+            return null;
+        } else {
+            return new Float((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 2566)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 2566)
@@ -0,0 +1,631 @@
+// License: GPL. See LICENSE file for details.
+// Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.CachedLatLon;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import com.drew.imaging.jpeg.JpegMetadataReader;
+import com.drew.lang.Rational;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.exif.GpsDirectory;
+
+public class GeoImageLayer extends Layer {
+
+    List<ImageEntry> data;
+
+    private Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
+    private Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
+
+    private int currentPhoto = -1;
+
+    // These are used by the auto-guess function to store the result,
+    // so when the dialig is re-opened the users modifications don't
+    // get overwritten
+    public boolean hasTimeoffset = false;
+    public long timeoffset = 0;
+
+    /*
+     * Stores info about each image
+     */
+
+    static final class ImageEntry implements Comparable<ImageEntry> {
+        File file;
+        Date time;
+        LatLon exifCoor;
+        CachedLatLon pos;
+        Image thumbnail;
+        /** Speed in kilometer per second */
+        Double speed;
+        /** Elevation (altitude) in meters */
+        Double elevation;
+
+        public void setCoor(LatLon latlon)
+        {
+            pos = new CachedLatLon(latlon);
+        }
+        public int compareTo(ImageEntry image) {
+            if (time != null && image.time != null) {
+                return time.compareTo(image.time);
+            } else if (time == null && image.time == null) {
+                return 0;
+            } else if (time == null) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+    }
+
+    /** 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
+     * directories. In case of directories, they are scanned to find all the images they contain.
+     * Then all the images that have be found are loaded as ImageEntry instances.
+     */
+    private static final class Loader extends PleaseWaitRunnable {
+
+        private boolean cancelled = false;
+        private GeoImageLayer layer;
+        private Collection<File> selection;
+        private HashSet<String> loadedDirectories = new HashSet<String>();
+        private LinkedHashSet<String> errorMessages;
+
+        protected void rememberError(String message) {
+        	this.errorMessages.add(message);
+        }
+
+        public Loader(Collection<File> selection, GpxLayer gpxLayer) {
+            super(tr("Extracting GPS locations from EXIF"));
+            this.selection = selection;
+            errorMessages = new LinkedHashSet<String>();
+        }
+
+        @Override protected void realRun() throws IOException {
+
+            progressMonitor.subTask(tr("Starting directory scan"));
+            Collection<File> files = new ArrayList<File>();
+            try {
+                addRecursiveFiles(files, selection);
+            } catch(NullPointerException npe) {
+                rememberError(tr("One of the selected files was null"));
+            }
+
+            if (cancelled) {
+                return;
+            }            
+            progressMonitor.subTask(tr("Read photos..."));
+            progressMonitor.setTicksCount(files.size());
+
+            progressMonitor.subTask(tr("Read photos..."));
+            progressMonitor.setTicksCount(files.size());
+
+            // read the image files
+            List<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
+
+            for (File f : files) {
+
+                if (cancelled) {
+                    break;
+                }
+
+                progressMonitor.subTask(tr("Reading {0}...", f.getName()));
+                progressMonitor.worked(1);
+
+                ImageEntry e = new ImageEntry();
+
+                // Changed to silently cope with no time info in exif. One case
+                // of person having time that couldn't be parsed, but valid GPS info
+
+                try {
+                    e.time = ExifReader.readTime(f);
+                } catch (ParseException e1) {
+                    e.time = null;
+                }
+                e.file = f;
+                extractExif(e);
+                data.add(e);
+            }
+            layer = new GeoImageLayer(data);
+            files.clear();
+            Thread thumbsloader = new Thread(new Thumbsloader());
+            thumbsloader.setPriority(Thread.MIN_PRIORITY);
+            thumbsloader.start();            
+        }
+
+        private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
+            boolean nullFile = false;
+
+            for (File f : sel) {
+
+                if(cancelled) {
+                    break;
+                }
+
+                if (f == null) {
+                    nullFile = true;
+
+                } else if (f.isDirectory()) {
+                    String canonical = null;
+                    try {
+                        canonical = f.getCanonicalPath();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                        rememberError(tr("Unable to get canonical path for directory {0}\n",
+                                           f.getAbsolutePath()));
+                    }
+
+                    if (canonical == null || loadedDirectories.contains(canonical)) {
+                        continue;
+                    } else {
+                        loadedDirectories.add(canonical);
+                    }
+
+                    Collection<File> children = Arrays.asList(f.listFiles(JpegFileFilter.getInstance()));
+                    if (children != null) {
+                        progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
+                        try {
+                            addRecursiveFiles(files, children);
+                        } catch(NullPointerException npe) {
+                            npe.printStackTrace();
+                            rememberError(tr("Found null file in directory {0}\n", f.getPath()));
+                        }
+                    } else {
+                    	rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
+                    }
+
+                } else {
+                      files.add(f);
+                }
+            }
+
+            if (nullFile) {
+                throw new NullPointerException();
+            }
+        }
+
+        protected String formatErrorMessages() {
+        	StringBuffer sb = new StringBuffer();
+        	sb.append("<html>");
+    		if (errorMessages.size() == 1) {
+    			sb.append(errorMessages.iterator().next());
+    		} else {
+    			sb.append("<ul>");
+    			for (String msg: errorMessages) {
+    				sb.append("<li>").append(msg).append("</li>");
+    			}
+    			sb.append("/ul>");
+    		}
+    		sb.append("</html>");
+    		return sb.toString();
+        }
+
+        @Override protected void finish() {
+        	if (!errorMessages.isEmpty()) {
+        		JOptionPane.showMessageDialog(
+        				Main.parent,
+        				formatErrorMessages(),
+        				tr("Error"),
+        				JOptionPane.ERROR_MESSAGE
+        		);
+        	}
+            if (layer != null) {
+                Main.main.addLayer(layer);
+                layer.hook_up_mouse_events(); // Main.map.mapView should exist
+                                              // now. Can add mouse listener
+
+                if (! cancelled && layer.data.size() > 0) {
+                    boolean noGeotagFound = true;
+                    for (ImageEntry e : layer.data) {
+                        if (e.pos != null) {
+                            noGeotagFound = false;
+                        }
+                    }
+                    if (noGeotagFound) {
+                        new CorrelateGpxWithImages(layer).actionPerformed(null);
+                    }
+                }
+            }
+        }
+
+        @Override protected void cancel() {
+            cancelled = true;
+        }
+        
+        class Thumbsloader implements Runnable {
+            public void run() {
+                System.err.println("Load Thumbnails");
+                MediaTracker tracker = new MediaTracker(Main.map.mapView); 
+                for (int i = 0; i < layer.data.size(); i++) {
+                    System.err.println("getImg "+i);
+                    Image img = Toolkit.getDefaultToolkit().createImage(layer.data.get(i).file.getPath());
+                    tracker.addImage(img, 0);
+                    try {
+                		tracker.waitForID(0);
+            	    } catch (InterruptedException e) {
+            	        System.err.println("InterruptedException");
+                		return; //  FIXME
+            	    }
+    	            BufferedImage scaledBI = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
+                    Graphics2D g = scaledBI.createGraphics();
+                    while (!g.drawImage(img, 0, 0, 16, 16, null))
+                    {
+                        try {
+                            Thread.sleep(10);
+                        } catch(InterruptedException ie) {}
+                    }
+                    g.dispose();
+                    tracker.removeImage(img);
+                    layer.data.get(i).thumbnail = scaledBI;
+                    if (Main.map != null && Main.map.mapView != null) {
+                        Main.map.mapView.repaint();
+                    }
+                }
+                
+//                boolean error = tracker.isErrorID(1);
+//                if (img != null && (img.getWidth(null) == 0 || img.getHeight(null) == 0)) {
+//                    error = true;
+//                }
+
+
+            }
+        }        
+    }
+    
+    private static boolean addedToggleDialog = false;
+
+    public static void create(Collection<File> files, GpxLayer gpxLayer) {
+        Loader loader = new Loader(files, gpxLayer);
+        Main.worker.execute(loader);
+        if (!addedToggleDialog) {
+            Main.map.addToggleDialog(ImageViewerDialog.getInstance());
+            addedToggleDialog = true;
+        }
+    }
+
+    private GeoImageLayer(final List<ImageEntry> data) {
+
+        super(tr("Geotagged Images"));
+
+        Collections.sort(data);
+        this.data = data;
+    }
+
+    @Override
+    public Icon getIcon() {
+        return ImageProvider.get("dialogs/geoimage");
+    }
+
+    @Override
+    public Object getInfoComponent() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Component[] getMenuEntries() {
+
+        JMenuItem correlateItem = new JMenuItem(tr("Correlate to GPX"), ImageProvider.get("dialogs/geoimage/gpx2img"));
+        correlateItem.addActionListener(new CorrelateGpxWithImages(this));
+
+        return new Component[] {
+                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
+                new JMenuItem(new RenameLayerAction(null, this)),
+                new JSeparator(),
+                correlateItem
+                };
+    }
+
+    @Override
+    public String getToolTipText() {
+        int i = 0;
+        for (ImageEntry e : data)
+            if (e.pos != null)
+                i++;
+        return data.size() + " " + trn("image", "images", data.size())
+                + " loaded. " + tr("{0} were found to be gps tagged.", i);
+    }
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return other instanceof GeoImageLayer;
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {
+        GeoImageLayer l = (GeoImageLayer) from;
+
+        ImageEntry selected = null;
+        if (l.currentPhoto >= 0) {
+            selected = l.data.get(l.currentPhoto);
+        }
+
+        data.addAll(l.data);
+        Collections.sort(data);
+
+        // Supress 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.file.equals(prev.file)) {
+                    data.remove(i);
+                } else {
+                    prev = cur;
+                }
+            }
+        }
+
+        if (selected != null) {
+            for (int i = 0; i < data.size() ; i++) {
+                if (data.get(i) == selected) {
+                    currentPhoto = i;
+                    ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i));
+                    break;
+                }
+            }
+        }
+
+        setName(l.getName());
+
+    }
+
+    @Override
+    public void paint(Graphics2D g, MapView mv, Bounds bounds) {
+
+        for (ImageEntry e : data) {
+            if (e.pos != null) {
+                Point p = mv.getPoint(e.pos);
+                if (e.thumbnail != null && e.thumbnail.getWidth(null) > 0 && e.thumbnail.getHeight(null) > 0) {
+                    g.drawImage(e.thumbnail, 
+                                p.x - e.thumbnail.getWidth(null) / 2, 
+                                p.y - e.thumbnail.getHeight(null) / 2, null);
+                }
+                else {
+                icon.paintIcon(mv, g, 
+                               p.x - icon.getIconWidth() / 2, 
+                               p.y - icon.getIconHeight() / 2);
+                }
+            }
+        }
+
+        // Draw the selection on top of the other pictures.
+        if (currentPhoto >= 0 && currentPhoto < data.size()) {
+            ImageEntry e = data.get(currentPhoto);
+
+            if (e.pos != null) {
+                Point p = mv.getPoint(e.pos);
+
+                Rectangle r = new Rectangle(p.x - selectedIcon.getIconWidth() / 2,
+                                            p.y - selectedIcon.getIconHeight() / 2,
+                                            selectedIcon.getIconWidth(),
+                                            selectedIcon.getIconHeight());
+                selectedIcon.paintIcon(mv, g, r.x, r.y);
+            }
+        }
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {
+        for (ImageEntry e : data)
+            v.visit(e.pos);
+    }
+
+    /*
+     * Extract gps from image exif
+     *
+     * If successful, fills in the LatLon and EastNorth attributes of passed in
+     * image;
+     */
+
+    private static void extractExif(ImageEntry e) {
+
+        try {
+            int deg;
+            float min, sec;
+            double lon, lat;
+
+            Metadata metadata = JpegMetadataReader.readMetadata(e.file);
+            Directory dir = metadata.getDirectory(GpsDirectory.class);
+
+            // longitude
+
+            Rational[] components = dir
+                    .getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
+
+            deg = components[0].intValue();
+            min = components[1].floatValue();
+            sec = components[2].floatValue();
+
+            lon = (deg + (min / 60) + (sec / 3600));
+
+            if (dir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W')
+                lon = -lon;
+
+            // latitude
+
+            components = dir.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
+
+            deg = components[0].intValue();
+            min = components[1].floatValue();
+            sec = components[2].floatValue();
+
+            lat = (deg + (min / 60) + (sec / 3600));
+
+            if (dir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S')
+                lat = -lat;
+
+            // Store values
+
+            e.setCoor(new LatLon(lat, lon));
+            e.exifCoor = e.pos;
+
+        } catch (Exception p) {
+            e.pos = null;
+        }
+    }
+
+    public void showNextPhoto() {
+        if (data != null && data.size() > 0) {
+            currentPhoto++;
+            if (currentPhoto >= data.size()) {
+                currentPhoto = data.size() - 1;
+            }
+            ImageViewerDialog.showImage(this, data.get(currentPhoto));
+        } else {
+            currentPhoto = -1;
+        }
+        Main.main.map.repaint();
+    }
+
+    public void showPreviousPhoto() {
+        if (data != null && data.size() > 0) {
+            currentPhoto--;
+            if (currentPhoto < 0) {
+                currentPhoto = 0;
+            }
+            ImageViewerDialog.showImage(this, data.get(currentPhoto));
+        } else {
+            currentPhoto = -1;
+        }
+        Main.main.map.repaint();
+    }
+    
+    public void checkPreviousNextButtons() {
+        System.err.println("check: " + currentPhoto);
+        ImageViewerDialog.setNextEnabled(currentPhoto < data.size() - 1);
+        ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);
+    }
+
+    public void removeCurrentPhoto() {
+        if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
+            data.remove(currentPhoto);
+            if (currentPhoto >= data.size()) {
+                currentPhoto = data.size() - 1;
+            }
+            if (currentPhoto >= 0) {
+                ImageViewerDialog.showImage(this, data.get(currentPhoto));
+            } else {
+                ImageViewerDialog.showImage(this, null);
+            }
+        }
+        Main.main.map.repaint();
+    }
+
+    private MouseAdapter mouseAdapter = null;
+
+    private void hook_up_mouse_events() {
+        mouseAdapter = new MouseAdapter() {
+            @Override public void mousePressed(MouseEvent e) {
+
+                if (e.getButton() != MouseEvent.BUTTON1) {
+                    return;
+                }
+                if (isVisible())
+                    Main.map.mapView.repaint();
+            }
+
+            @Override public void mouseReleased(MouseEvent ev) {
+
+                if (ev.getButton() != MouseEvent.BUTTON1) {
+                    return;
+                }
+                if (!isVisible()) {
+                    return;
+                }
+
+                ImageViewerDialog d = ImageViewerDialog.getInstance();
+//                System.err.println(d.isDialogShowing());
+
+
+                for (int i = data.size() - 1; i >= 0; --i) {
+                    ImageEntry e = data.get(i);
+                    if (e.pos == null)
+                        continue;
+                    Point p = Main.map.mapView.getPoint(e.pos);
+                    Rectangle r = new Rectangle(p.x - icon.getIconWidth() / 2,
+                                                p.y - icon.getIconHeight() / 2,
+                                                icon.getIconWidth(),
+                                                icon.getIconHeight());
+                    if (r.contains(ev.getPoint())) {
+                        currentPhoto = i;
+                        ImageViewerDialog.showImage(GeoImageLayer.this, e);
+                        Main.main.map.repaint();
+                        
+                        
+                        break;
+                    }
+                }
+                Main.map.mapView.repaint();
+            }
+        };
+        Main.map.mapView.addMouseListener(mouseAdapter);
+        Layer.listeners.add(new LayerChangeListener() {
+            public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+                if (newLayer == GeoImageLayer.this && currentPhoto >= 0) {
+                    Main.main.map.repaint();
+                    ImageViewerDialog.showImage(GeoImageLayer.this, data.get(currentPhoto));
+                }
+            }
+
+            public void layerAdded(Layer newLayer) {
+            }
+
+            public void layerRemoved(Layer oldLayer) {
+                if (oldLayer == GeoImageLayer.this) {
+                    Main.map.mapView.removeMouseListener(mouseAdapter);
+                    currentPhoto = -1;
+                    data.clear();
+                    data = null;
+                }
+            }
+        });
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 2566)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 2566)
@@ -0,0 +1,616 @@
+// License: GPL. See LICENSE file for details.
+// Copyright 2007 by Christian Gallioz (aka khris78)
+
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+
+import javax.swing.JComponent;
+
+import org.openstreetmap.josm.Main;
+
+public class ImageDisplay extends JComponent {
+
+    /** The file that is currently displayed */
+    private File file = null;
+
+    /** The image currently displayed */
+    private Image image = null;
+    private Image image_c = null;
+
+    /** The image currently displayed */
+    private boolean errorLoading = false;
+    private boolean errorLoading_c = false;
+
+    /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
+     * each time the zoom is modified */
+    private Rectangle visibleRect = null;
+    private Rectangle visibleRect_c = null;
+
+    /** When a selection is done, the rectangle of the selection (in image coordinates) */
+    private Rectangle selectedRect = null;
+
+    /** The tracker to load the images */
+    private MediaTracker tracker = new MediaTracker(this);
+
+    private String osdText = null;
+    
+    private static int DRAG_BUTTON = Main.pref.getBoolean("geoimage.agpifo-style-drag-and-zoom", false) ? 1 : 3;
+    private static int ZOOM_BUTTON = DRAG_BUTTON == 1 ? 3 : 1;
+
+    /** The thread that reads the images. */
+    private class LoadImageRunnable implements Runnable {
+
+        File file = null;
+
+        public LoadImageRunnable(File file) {
+            this.file = file;
+        }
+
+        public void run() {
+            Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
+            tracker.addImage(img, 1);
+
+            // Wait for the end of loading
+            while (! tracker.checkID(1, true)) {
+                if (this.file != ImageDisplay.this.file) {
+                    // The file has changed
+                    tracker.removeImage(img);
+                    return;
+                }
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {
+                }
+            }
+
+            boolean error = tracker.isErrorID(1);
+            if (img != null && (img.getWidth(null) == 0 || img.getHeight(null) == 0)) {
+                error = true;
+            }
+
+            synchronized(ImageDisplay.this) {
+                if (this.file != ImageDisplay.this.file) {
+                    // The file has changed
+                    tracker.removeImage(img);
+                    return;
+                }
+                ImageDisplay.this.image = img;
+                visibleRect = new Rectangle(0, 0, img.getWidth(null), img.getHeight(null));
+                selectedRect = null;
+                errorLoading = error;
+            }
+            tracker.removeImage(img);
+            ImageDisplay.this.repaint();
+        }
+    }
+
+    private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
+
+        boolean mouseIsDragging = false;
+        long lastTimeForMousePoint = 0l;
+        Point mousePointInImg = null;
+
+        /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
+         * at the same place */
+        public void mouseWheelMoved(MouseWheelEvent e) {
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            mouseIsDragging = false;
+            selectedRect = null;
+
+            if (image == null) {
+                return;
+            }
+
+            // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
+            // on that mouse position. 
+            // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated 
+            // again if there was less than 1.5seconds since the last event.
+            System.out.println(e);
+            if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
+            	lastTimeForMousePoint = e.getWhen();
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+            }
+
+            // Applicate the zoom to the visible rectangle in image coordinates
+            if (e.getWheelRotation() > 0) {
+                visibleRect.width = visibleRect.width * 3 / 2;
+                visibleRect.height = visibleRect.height * 3 / 2;
+            } else {
+                visibleRect.width = visibleRect.width * 2 / 3;
+                visibleRect.height = visibleRect.height * 2 / 3;
+            }
+
+            // Check that the zoom doesn't exceed 2:1
+            if (visibleRect.width < getSize().width / 2) {
+                visibleRect.width = getSize().width / 2;
+            }
+            if (visibleRect.height < getSize().height / 2) {
+                visibleRect.height = getSize().height / 2;
+            }
+
+            // Set the same ratio for the visible rectangle and the display area
+            int hFact = visibleRect.height * getSize().width;
+            int wFact = visibleRect.width * getSize().height;
+            if (hFact > wFact) {
+                visibleRect.width = hFact / getSize().height;
+            } else {
+                visibleRect.height = wFact / getSize().width;
+            }
+
+            // The size of the visible rectangle is limited by the image size.
+            checkVisibleRectSize(image, visibleRect);
+
+            // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
+            Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+            visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
+            visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
+
+            // The position is also limited by the image size
+            checkVisibleRectPos(image, visibleRect);
+
+            synchronized(ImageDisplay.this) {
+                if (ImageDisplay.this.file == file) {
+                    ImageDisplay.this.visibleRect = visibleRect;
+                }
+            }
+            ImageDisplay.this.repaint();
+        }
+
+        /** Center the display on the point that has been clicked */
+        public void mouseClicked(MouseEvent e) {
+            // Move the center to the clicked point.
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                return;
+            }
+
+            if (e.getButton() != DRAG_BUTTON) {
+                return;
+            }
+
+            // Calculate the translation to set the clicked point the center of the view.
+            Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
+            Point center = getCenterImgCoord(visibleRect);
+
+            visibleRect.x += click.x - center.x;
+            visibleRect.y += click.y - center.y;
+
+            checkVisibleRectPos(image, visibleRect);
+
+            synchronized(ImageDisplay.this) {
+                if (ImageDisplay.this.file == file) {
+                    ImageDisplay.this.visibleRect = visibleRect;
+                }
+            }
+            ImageDisplay.this.repaint();
+        }
+
+        /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
+         * a picture part) */
+        public void mousePressed(MouseEvent e) {
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                return;
+            }
+
+            if (e.getButton() == DRAG_BUTTON) {
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                mouseIsDragging = true;
+                selectedRect = null;
+            } else if (e.getButton() == ZOOM_BUTTON) {
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                checkPointInVisibleRect(mousePointInImg, visibleRect);
+                mouseIsDragging = false;
+                selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
+                ImageDisplay.this.repaint();
+            } else {
+                mouseIsDragging = false;
+                selectedRect = null;
+            }
+        }
+
+        public void mouseDragged(MouseEvent e) {
+            if (! mouseIsDragging && selectedRect == null) {
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            if (mouseIsDragging) {
+                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                visibleRect.x += mousePointInImg.x - p.x;
+                visibleRect.y += mousePointInImg.y - p.y;
+                checkVisibleRectPos(image, visibleRect);
+                synchronized(ImageDisplay.this) {
+                    if (ImageDisplay.this.file == file) {
+                        ImageDisplay.this.visibleRect = visibleRect;
+                    }
+                }
+                ImageDisplay.this.repaint();
+
+            } else if (selectedRect != null) {
+                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                checkPointInVisibleRect(p, visibleRect);
+                Rectangle rect = new Rectangle(
+                        (p.x < mousePointInImg.x ? p.x : mousePointInImg.x),
+                        (p.y < mousePointInImg.y ? p.y : mousePointInImg.y),
+                        (p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x),
+                        (p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y));
+                checkVisibleRectSize(image, rect);
+                checkVisibleRectPos(image, rect);
+                ImageDisplay.this.selectedRect = rect;
+                ImageDisplay.this.repaint();
+            }
+
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            if (! mouseIsDragging && selectedRect == null) {
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            if (mouseIsDragging) {
+                mouseIsDragging = false;
+
+            } else if (selectedRect != null) {
+                int oldWidth = selectedRect.width;
+                int oldHeight = selectedRect.height;
+
+                // Check that the zoom doesn't exceed 2:1
+                if (selectedRect.width < getSize().width / 2) {
+                    selectedRect.width = getSize().width / 2;
+                }
+                if (selectedRect.height < getSize().height / 2) {
+                    selectedRect.height = getSize().height / 2;
+                }
+
+                // Set the same ratio for the visible rectangle and the display area
+                int hFact = selectedRect.height * getSize().width;
+                int wFact = selectedRect.width * getSize().height;
+                if (hFact > wFact) {
+                    selectedRect.width = hFact / getSize().height;
+                } else {
+                    selectedRect.height = wFact / getSize().width;
+                }
+
+                // Keep the center of the selection
+                if (selectedRect.width != oldWidth) {
+                    selectedRect.x -= (selectedRect.width - oldWidth) / 2;
+                }
+                if (selectedRect.height != oldHeight) {
+                    selectedRect.y -= (selectedRect.height - oldHeight) / 2;
+                }
+
+                checkVisibleRectSize(image, selectedRect);
+                checkVisibleRectPos(image, selectedRect);
+
+                synchronized (ImageDisplay.this) {
+                    if (file == ImageDisplay.this.file) {
+                        ImageDisplay.this.visibleRect = selectedRect;
+                    }
+                }
+                selectedRect = null;
+                ImageDisplay.this.repaint();
+            }
+        }
+
+        public void mouseEntered(MouseEvent e) {
+        }
+
+        public void mouseExited(MouseEvent e) {
+        }
+
+        public void mouseMoved(MouseEvent e) {
+        }
+
+        private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
+            if (p.x < visibleRect.x) {
+                p.x = visibleRect.x;
+            }
+            if (p.x > visibleRect.x + visibleRect.width) {
+                p.x = visibleRect.x + visibleRect.width;
+            }
+            if (p.y < visibleRect.y) {
+                p.y = visibleRect.y;
+            }
+            if (p.y > visibleRect.y + visibleRect.height) {
+                p.y = visibleRect.y + visibleRect.height;
+            }
+        }
+    }
+
+    public ImageDisplay() {
+        ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
+        addMouseListener(mouseListener);
+        addMouseWheelListener(mouseListener);
+        addMouseMotionListener(mouseListener);
+    }
+
+    public void setImage(File file) {
+        synchronized(this) {
+            this.file = file;
+            image = null;
+            selectedRect = null;
+            errorLoading = false;
+        }
+        repaint();
+        if (file != null) {
+            new Thread(new LoadImageRunnable(file)).start();
+        }
+    }
+
+    public void setOsdText(String text) {
+        this.osdText = text;
+    }
+
+    public void paintComponent(Graphics g) {
+        Image image;
+        File file;
+        Rectangle visibleRect;
+        boolean errorLoading;
+
+        synchronized(this) {
+            image = this.image;
+            file = this.file;
+            visibleRect = this.visibleRect;
+            errorLoading = this.errorLoading;
+        }
+
+        if (file == null) {
+            g.setColor(Color.black);
+            String noImageStr = tr("No image");
+            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
+            Dimension size = getSize();
+            g.drawString(noImageStr,
+                         (int) ((size.width - noImageSize.getWidth()) / 2),
+                         (int) ((size.height - noImageSize.getHeight()) / 2));
+        } else if (image == null) {
+            g.setColor(Color.black);
+            String loadingStr;
+            if (! errorLoading) {;
+                loadingStr = tr("Loading {0}", file.getName());
+            } else {
+                loadingStr = tr("Error on file {0}", file.getName());
+            }
+            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
+            Dimension size = getSize();
+            g.drawString(loadingStr,
+                         (int) ((size.width - noImageSize.getWidth()) / 2),
+                         (int) ((size.height - noImageSize.getHeight()) / 2));
+        } else {
+            Rectangle target = calculateDrawImageRectangle(visibleRect);
+            g.drawImage(image,
+                        target.x, target.y, target.x + target.width, target.y + target.height,
+                        visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
+                        null);
+            if (selectedRect != null) {
+                Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
+                Point bottomRight = img2compCoord(visibleRect,
+                                                  selectedRect.x + selectedRect.width,
+                                                  selectedRect.y + selectedRect.height);
+                g.setColor(new Color(128, 128, 128, 180));
+                g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
+                g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
+                g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
+                g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
+                g.setColor(Color.black);
+                g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
+            }
+            if (errorLoading) {
+                String loadingStr = tr("Error on file {0}", file.getName());
+                Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
+                Dimension size = getSize();
+                g.drawString(loadingStr,
+                             (int) ((size.width - noImageSize.getWidth()) / 2),
+                             (int) ((size.height - noImageSize.getHeight()) / 2));
+            }
+            if (osdText != null) {
+                FontMetrics metrics = g.getFontMetrics(g.getFont());
+                int ascent = metrics.getAscent();
+                Color bkground = new Color(255, 255, 255, 128);
+                int lastPos = 0;
+                int pos = osdText.indexOf("\n");
+                int x = 3;
+                int y = 3;
+                String line;
+                while (pos > 0) {
+                    line = osdText.substring(lastPos, pos);
+                    Rectangle2D lineSize = metrics.getStringBounds(line, g);
+                    g.setColor(bkground);
+                    g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
+                    g.setColor(Color.black);
+                    g.drawString(line, x, y + ascent);
+                    y += (int) lineSize.getHeight();
+                    lastPos = pos + 1;
+                    pos = osdText.indexOf("\n", lastPos);
+                }
+
+                line = osdText.substring(lastPos);
+                Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
+                g.setColor(bkground);
+                g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
+                g.setColor(Color.black);
+                g.drawString(line, x, y + ascent);
+            }
+        }
+    }
+
+    private final Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
+        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+        return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
+                         drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
+    }
+
+    private final Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
+        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+        return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
+                         visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
+    }
+
+   private final Point getCenterImgCoord(Rectangle visibleRect) {
+        return new Point(visibleRect.x + visibleRect.width / 2,
+                                 visibleRect.y + visibleRect.height / 2);
+    }
+
+    private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
+        Dimension size = getSize();
+        int x, y, w, h;
+        x = 0;
+        y = 0;
+        w = size.width;
+        h = size.height;
+
+        int wFact = w * visibleRect.height;
+        int hFact = h * visibleRect.width;
+        if (wFact != hFact) {
+            if (wFact > hFact) {
+                w = hFact / visibleRect.height;
+                x = (size.width - w) / 2;
+            } else {
+                h = wFact / visibleRect.width;
+                y = (size.height - h) / 2;
+            }
+        }
+        return new Rectangle(x, y, w, h);
+    }
+
+    public void zoomBestFitOrOne() {
+        File file;
+        Image image;
+        Rectangle visibleRect;
+
+        synchronized (this) {
+            file = ImageDisplay.this.file;
+            image = ImageDisplay.this.image;
+            visibleRect = ImageDisplay.this.visibleRect;
+        }
+
+        if (image == null) {
+            return;
+        }
+
+        if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
+            // The display is not at best fit. => Zoom to best fit
+            visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
+
+        } else {
+            // The display is at best fit => zoom to 1:1
+            Point center = getCenterImgCoord(visibleRect);
+            visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
+                                        getWidth(), getHeight());
+            checkVisibleRectPos(image, visibleRect);
+        }
+
+        synchronized(this) {
+            if (file == this.file) {
+                this.visibleRect = visibleRect;
+            }
+        }
+        repaint();
+    }
+
+    private final void checkVisibleRectPos(Image image, Rectangle visibleRect) {
+        if (visibleRect.x < 0) {
+            visibleRect.x = 0;
+        }
+        if (visibleRect.y < 0) {
+            visibleRect.y = 0;
+        }
+        if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
+            visibleRect.x = image.getWidth(null) - visibleRect.width;
+        }
+        if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
+            visibleRect.y = image.getHeight(null) - visibleRect.height;
+        }
+    }
+
+    private void checkVisibleRectSize(Image image, Rectangle visibleRect) {
+        if (visibleRect.width > image.getWidth(null)) {
+            visibleRect.width = image.getWidth(null);
+        }
+        if (visibleRect.height > image.getHeight(null)) {
+            visibleRect.height = image.getHeight(null);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 2566)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 2566)
@@ -0,0 +1,199 @@
+// License: GPL. See LICENSE file for details.
+// Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class ImageViewerDialog extends ToggleDialog implements ActionListener {
+
+    private static final String COMMAND_ZOOM = "zoom";
+    private static final String COMMAND_CENTERVIEW = "centre";
+    private static final String COMMAND_NEXT = "next";
+    private static final String COMMAND_REMOVE = "remove";
+    private static final String COMMAND_PREVIOUS = "previous";
+
+    private ImageDisplay imgDisplay = new ImageDisplay();
+    private boolean centerView = false;
+
+    // Only one instance of that class
+    static private ImageViewerDialog INSTANCE = null;
+
+    public static ImageViewerDialog getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = new ImageViewerDialog();
+        }
+        return INSTANCE;
+    }
+    
+    private JButton btnNext;
+    private JButton btnPrevious;
+
+    private ImageViewerDialog() {
+        super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged", tr("Tool: {0}", tr("Display geotagged images")), KeyEvent.VK_Y, Shortcut.GROUP_EDIT), 200);
+
+        if (INSTANCE != null) {
+            throw new IllegalStateException("Image viewer dialog should not be instanciated twice !");
+        }
+
+        INSTANCE = this;
+        
+        JPanel content = new JPanel();
+        content.setLayout(new BorderLayout());
+
+        content.add(imgDisplay, BorderLayout.CENTER);
+
+        JPanel buttons = new JPanel();
+        buttons.setLayout(new FlowLayout());
+
+        JButton button;
+
+        Dimension buttonDim = new Dimension(26,26);
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "previous"));
+        button.setActionCommand(COMMAND_PREVIOUS);
+        button.setToolTipText(tr("Previous"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+        btnPrevious = button; //FIX
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "delete"));
+        button.setActionCommand(COMMAND_REMOVE);
+        button.setToolTipText(tr("Remove photo from layer"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "next"));
+        button.setActionCommand(COMMAND_NEXT);
+        button.setToolTipText(tr("Next"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+        btnNext = button;
+
+        JToggleButton tb = new JToggleButton();
+        tb.setIcon(ImageProvider.get("dialogs", "centreview"));
+        tb.setActionCommand(COMMAND_CENTERVIEW);
+        tb.setToolTipText(tr("Center view"));
+        tb.addActionListener(this);
+        tb.setPreferredSize(buttonDim);
+        buttons.add(tb);
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "zoom-best-fit"));
+        button.setActionCommand(COMMAND_ZOOM);
+        button.setToolTipText(tr("Zoom best fit and 1:1"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        content.add(buttons, BorderLayout.SOUTH);
+
+        add(content, BorderLayout.CENTER);
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        if (COMMAND_NEXT.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+                currentLayer.showNextPhoto();
+            }
+        } else if (COMMAND_PREVIOUS.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+                currentLayer.showPreviousPhoto();
+            }
+
+        } else if (COMMAND_CENTERVIEW.equals(e.getActionCommand())) {
+            centerView = ((JToggleButton) e.getSource()).isSelected();
+            if (centerView && currentEntry != null && currentEntry.pos != null) {
+                Main.map.mapView.zoomTo(currentEntry.pos);
+            }
+
+        } else if (COMMAND_ZOOM.equals(e.getActionCommand())) {
+            imgDisplay.zoomBestFitOrOne();
+
+        } else if (COMMAND_REMOVE.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+               currentLayer.removeCurrentPhoto();
+            }
+        }
+
+    }
+
+    public static void showImage(GeoImageLayer layer, ImageEntry entry) {
+        getInstance().displayImage(layer, entry);
+        layer.checkPreviousNextButtons();
+    }
+    public static void setPreviousEnabled(Boolean value) {
+        getInstance().btnPrevious.setEnabled(value);
+    }
+    public static void setNextEnabled(Boolean value) {
+        getInstance().btnNext.setEnabled(value);
+    }
+
+
+    private GeoImageLayer currentLayer = null;
+    private ImageEntry currentEntry = null;
+
+    public void displayImage(GeoImageLayer layer, ImageEntry entry) {
+        synchronized(this) {
+            if (currentLayer == layer && currentEntry == entry) {
+                repaint();
+                return;
+            }
+
+            if (centerView && Main.map != null && entry != null && entry.pos != null) {
+                Main.map.mapView.zoomTo(entry.pos);
+            }
+
+            currentLayer = layer;
+            currentEntry = entry;
+        }
+
+        if (entry != null) {
+            imgDisplay.setImage(entry.file);
+            StringBuffer osd = new StringBuffer(entry.file != null ? entry.file.getName() : "");
+            if (entry.elevation != null) {
+                osd.append(tr("\nAltitude: {0} m", entry.elevation.longValue()));
+            }
+            if (entry.speed != null) {
+                osd.append(tr("\n{0} km/h", Math.round(entry.speed)));
+            }
+            imgDisplay.setOsdText(osd.toString());
+        } else {
+            imgDisplay.setImage(null);
+            imgDisplay.setOsdText("");
+        }
+    }
+    
+    /**
+     * Returns whether an image is currently displayed
+     * @return If image is currently displayed
+     */
+    public boolean hasImage() {
+        return currentEntry != null;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/JpegFileFilter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/JpegFileFilter.java	(revision 2566)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/JpegFileFilter.java	(revision 2566)
@@ -0,0 +1,34 @@
+// License: GPL. See LICENSE file for details.
+// Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+//import javax.swing.JFileChooser;
+import java.io.File;
+
+class JpegFileFilter extends javax.swing.filechooser.FileFilter
+                                    implements java.io.FileFilter {
+
+    static final private JpegFileFilter instance = new JpegFileFilter();
+    public static JpegFileFilter getInstance() {
+        return instance;
+    }
+    
+    @Override public boolean accept(File f) {
+        if (f.isDirectory()) {
+            return true;
+        } else {
+            String name = f.getName().toLowerCase();
+            return name.endsWith(".jpg") || name.endsWith(".jpeg");
+        }
+    }
+
+    @Override public String getDescription() {
+        return tr("JPEG images (*.jpg)");
+    }
+}
+
