Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java	(revision 98)
+++ src/org/openstreetmap/josm/Main.java	(revision 99)
@@ -100,4 +100,6 @@
 				public void layerRemoved(final Layer oldLayer) {}
 			});
+			if (map.mapView.editLayer != null)
+				map.mapView.editLayer.listenerCommands.add(redoUndoListener);
 		}
 		redoUndoListener.commandChanged(0,0);
@@ -206,5 +208,5 @@
 	 * Use this to register shortcuts to
 	 */
-	public static JPanel panel = new JPanel(new BorderLayout());
+	public static final JPanel contentPane = new JPanel(new BorderLayout());
 
 
@@ -214,6 +216,7 @@
 
 
+	private static JPanel panel = new JPanel(new BorderLayout());
+
 	protected final JMenuBar mainMenu = new JMenuBar();
-	protected static final JPanel contentPane = new JPanel(new BorderLayout());
 	protected static Rectangle bounds;
 
Index: src/org/openstreetmap/josm/actions/DownloadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 99)
@@ -38,6 +38,6 @@
 import org.openstreetmap.josm.gui.BookmarkList.Bookmark;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 import org.openstreetmap.josm.io.OsmServerReader;
 import org.openstreetmap.josm.tools.GBC;
@@ -104,5 +104,5 @@
 				return;
 			String name = latlon[0].getText() + " " + latlon[1].getText() + " x " + latlon[2].getText() + " " + latlon[3].getText();
-			Main.main.addLayer(new RawGpsDataLayer(rawData, name));
+			Main.main.addLayer(new RawGpsLayer(rawData, name));
 		}
 
@@ -170,5 +170,5 @@
 					mv.getLatLon(0, mv.getHeight()),
 					mv.getLatLon(mv.getWidth(), 0)));
-			rawGps.setSelected(mv.getActiveLayer() instanceof RawGpsDataLayer);
+			rawGps.setSelected(mv.getActiveLayer() instanceof RawGpsLayer);
 		}
 		dlg.add(rawGps, GBC.eop());
Index: src/org/openstreetmap/josm/actions/GpxExportAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 99)
@@ -27,5 +27,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer;
 import org.openstreetmap.josm.io.GpxWriter;
 import org.openstreetmap.josm.tools.GBC;
@@ -118,6 +118,6 @@
 					authorName.getText(), email.getText(), copyright.getText(),
 					copyrightYear.getText(), keywords.getText());
-			if (layer instanceof RawGpsDataLayer)
-				w.output(((RawGpsDataLayer)layer).data);
+			if (layer instanceof RawGpsLayer)
+				w.output(((RawGpsLayer)layer).data);
 			else
 				w.output(Main.ds);
Index: src/org/openstreetmap/josm/actions/GroupAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/GroupAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/GroupAction.java	(revision 99)
@@ -52,6 +52,6 @@
 	public GroupAction(int shortCut, int modifiers) {
 		String idName = getClass().getName();
-		Main.panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(shortCut, modifiers), idName);
-        Main.panel.getActionMap().put(idName, this);
+		Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(shortCut, modifiers), idName);
+        Main.contentPane.getActionMap().put(idName, this);
 		shortCutName = ShortCutLabel.name(shortCut, modifiers);
 		addPropertyChangeListener(new PropertyChangeListener(){
Index: src/org/openstreetmap/josm/actions/JosmAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/JosmAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/JosmAction.java	(revision 99)
@@ -33,6 +33,8 @@
 		super(name, ImageProvider.get(iconName));
 		putValue(SHORT_DESCRIPTION, "<html>"+tooltip+" <font size='-2'>"+shortCutName+"</font>&nbsp;</html>");
-		Main.panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortCut, name);
-        Main.panel.getActionMap().put(name, this);
+		//Main.panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortCut, name);
+        //Main.panel.getActionMap().put(name, this);
+        Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortCut, name);
+        Main.contentPane.getActionMap().put(name, this);
 	}
 
Index: src/org/openstreetmap/josm/actions/OpenAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/OpenAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/OpenAction.java	(revision 99)
@@ -18,6 +18,6 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 import org.openstreetmap.josm.io.GpxReader;
 import org.openstreetmap.josm.io.OsmReader;
@@ -66,5 +66,5 @@
 				} else
 					throw new IllegalStateException();
-				Main.main.addLayer(new RawGpsDataLayer(data, filename.getName()));
+				Main.main.addLayer(new RawGpsLayer(data, filename.getName()));
 			} else {
 				DataSet dataSet;
@@ -102,8 +102,8 @@
 		} catch (SAXException x) {
 			x.printStackTrace();
-			JOptionPane.showMessageDialog(Main.parent, "Error while parsing: "+x.getMessage());
+			JOptionPane.showMessageDialog(Main.parent, "Error while parsing "+fn+": "+x.getMessage());
 		} catch (JDOMException x) {
 			x.printStackTrace();
-			JOptionPane.showMessageDialog(Main.parent, "Error while parsing: "+x.getMessage());
+			JOptionPane.showMessageDialog(Main.parent, "Error while parsing "+fn+": "+x.getMessage());
 		} catch (IOException x) {
 			x.printStackTrace();
Index: src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UploadAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/UploadAction.java	(revision 99)
@@ -39,5 +39,9 @@
 
 	public void actionPerformed(ActionEvent e) {
-
+		if (Main.map == null) {
+			JOptionPane.showMessageDialog(Main.parent, "Nothing to upload. Get some data first.");
+			return;
+		}
+		
 		String osmDataServer = Main.pref.get("osm-server.url");
 		//TODO: Remove this in later versions (temporary only)
Index: src/org/openstreetmap/josm/actions/mapmode/AddNodeAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/AddNodeAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/mapmode/AddNodeAction.java	(revision 99)
@@ -1,4 +1,5 @@
 package org.openstreetmap.josm.actions.mapmode;
 
+import java.awt.Cursor;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
@@ -53,4 +54,5 @@
 		super.enterMode();
 		Main.map.mapView.addMouseListener(this);
+		Main.map.mapView.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
 	}
 
@@ -58,4 +60,5 @@
 		super.exitMode();
 		Main.map.mapView.removeMouseListener(this);
+		Main.map.mapView.setCursor(Cursor.getDefaultCursor());
 	}
 
Index: src/org/openstreetmap/josm/actions/mapmode/AddWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/AddWayAction.java	(revision 98)
+++ src/org/openstreetmap/josm/actions/mapmode/AddWayAction.java	(revision 99)
@@ -14,5 +14,4 @@
 import org.openstreetmap.josm.command.AddCommand;
 import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.DeleteCommand;
 import org.openstreetmap.josm.data.SelectionChangedListener;
@@ -64,12 +63,6 @@
 	@Override public void enterMode() {
 		super.enterMode();
-		Command c = null;
 		way = makeWay();
-		if (way != null) {
-			c = new AddCommand(way);
-			Main.ds.setSelected(way);
-			Main.main.editLayer().add(c);
-		} else
-			Main.ds.clearSelection();
+		Main.ds.setSelected(way);
 		Main.map.mapView.addMouseListener(this);
 	}
@@ -147,4 +140,6 @@
 		}
 
+		Way wayToAdd = null;
+		boolean reordered = false;
 		if (numberOfSelectedWays > 0) {
 			String ways = "way" + (numberOfSelectedWays==1?" has":"s have");
@@ -155,4 +150,22 @@
 					if (osm instanceof Way)
 						segmentSet.addAll(((Way)osm).segments);
+			} else if (numberOfSelectedWays == 1) {
+				answer = JOptionPane.showConfirmDialog(Main.parent, "Do you want to add all other selected segments to the one selected way?", "Add segments to way?", JOptionPane.YES_NO_OPTION);
+				if (answer == JOptionPane.YES_OPTION) {
+					for (OsmPrimitive osm : selection) {
+						if (osm instanceof Way) {
+							wayToAdd = (Way)osm;
+							answer = JOptionPane.showConfirmDialog(Main.parent, "Reorder all line segments?", "Reorder?", JOptionPane.YES_NO_CANCEL_OPTION);
+							if (answer == JOptionPane.CANCEL_OPTION)
+								return wayToAdd;
+							if (answer == JOptionPane.YES_OPTION) {
+								segmentSet.addAll(wayToAdd.segments);
+								reordered = true;
+							} else
+								segmentSet.removeAll(wayToAdd.segments);
+							break;
+						}
+					}
+				}
 			}
 		}
@@ -193,4 +206,13 @@
 		}
 
+		if (wayToAdd != null) {
+			Way w = new Way(wayToAdd);
+			if (reordered)
+				w.segments.clear();
+			w.segments.addAll(sortedSegments);
+			Main.main.editLayer().add(new ChangeCommand(wayToAdd, w));
+			return wayToAdd;
+		}
+
 		if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(Main.parent, "Create a new way out of "+sortedSegments.size()+" segments?", "Create new way", JOptionPane.YES_NO_OPTION))
 			return null;
@@ -198,4 +220,7 @@
 		Way w = new Way();
 		w.segments.addAll(sortedSegments);
+
+		if (way != null)
+			Main.main.editLayer().add(new AddCommand(way));
 		return w;
 	}
Index: src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java	(revision 98)
+++ src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java	(revision 99)
@@ -88,5 +88,5 @@
 	 * @param color The color of the node.
 	 */
-	private void drawNode(Node n, Color color) {
+	public void drawNode(Node n, Color color) {
 		Point p = nc.getPoint(n.eastNorth);
 		g.setColor(color);
Index: src/org/openstreetmap/josm/data/projection/Mercator.java
===================================================================
--- src/org/openstreetmap/josm/data/projection/Mercator.java	(revision 98)
+++ src/org/openstreetmap/josm/data/projection/Mercator.java	(revision 99)
@@ -1,6 +1,6 @@
 package org.openstreetmap.josm.data.projection;
 
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.coor.EastNorth;
 
 /**
Index: src/org/openstreetmap/josm/data/projection/Projection.java
===================================================================
--- src/org/openstreetmap/josm/data/projection/Projection.java	(revision 98)
+++ src/org/openstreetmap/josm/data/projection/Projection.java	(revision 99)
@@ -1,6 +1,6 @@
 package org.openstreetmap.josm.data.projection;
 
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.coor.EastNorth;
 
 /**
Index: src/org/openstreetmap/josm/gui/IconToggleButton.java
===================================================================
--- src/org/openstreetmap/josm/gui/IconToggleButton.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/IconToggleButton.java	(revision 99)
@@ -40,6 +40,8 @@
 
 	public void propertyChange(PropertyChangeEvent evt) {
-		if (evt.getPropertyName().equals("active"))
+		if (evt.getPropertyName().equals("active")) {
 			setSelected((Boolean)evt.getNewValue());
+			requestFocusInWindow();
+		}
 	}
 }
Index: src/org/openstreetmap/josm/gui/MapFrame.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapFrame.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/MapFrame.java	(revision 99)
@@ -133,7 +133,11 @@
 	 */
 	public void setVisibleDialogs() {
-		for (Component c : toggleDialogs.getComponents())
-			if (c instanceof ToggleDialog)
-				c.setVisible(Main.pref.getBoolean(((ToggleDialog)c).prefName+".visible"));
+		for (Component c : toggleDialogs.getComponents()) {
+			if (c instanceof ToggleDialog) {
+				boolean sel = Main.pref.getBoolean(((ToggleDialog)c).prefName+".visible");
+				((ToggleDialog)c).action.button.setSelected(sel);
+				c.setVisible(sel);
+			}
+		}
 	}
 
Index: src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapView.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/MapView.java	(revision 99)
@@ -6,4 +6,8 @@
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -12,4 +16,7 @@
 
 import javax.swing.JOptionPane;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 
 import org.openstreetmap.josm.Main;
@@ -76,4 +83,37 @@
 	private final AutoScaleAction autoScaleAction;
 
+	
+	private final class Scaler extends JSlider implements PropertyChangeListener, ChangeListener {
+		boolean hovered = false;
+		public Scaler() {
+			super(0, 20);
+			addMouseListener(new MouseAdapter(){
+				@Override public void mouseEntered(MouseEvent e) {
+	                hovered = true;
+                }
+				@Override public void mouseExited(MouseEvent e) {
+	                hovered = false;
+                }
+			});
+			MapView.this.addPropertyChangeListener(this);
+			addChangeListener(this);
+        }
+		public void propertyChange(PropertyChangeEvent evt) {
+			if (evt.getPropertyName().equals("scale") && !getModel().getValueIsAdjusting())
+				setValue(zoom());
+        }
+		public void stateChanged(ChangeEvent e) {
+			if (!hovered)
+				return;
+			EastNorth pos = world;
+			for (int zoom = 0; zoom < getValue(); ++zoom)
+				pos = new EastNorth(pos.east()/2, pos.north()/2);
+			if (MapView.this.getWidth() < MapView.this.getHeight())
+				zoomTo(center, pos.east()*2/(MapView.this.getWidth()-20));
+			else
+				zoomTo(center, pos.north()*2/(MapView.this.getHeight()-20));
+        }
+	}
+	
 	public MapView(AutoScaleAction autoScaleAction) {
 		this.autoScaleAction = autoScaleAction;
@@ -91,4 +131,8 @@
 			}
 		});
+		Scaler zoomScaler = new Scaler();
+		zoomScaler.setOpaque(false);
+		add(zoomScaler);
+		zoomScaler.setBounds(0,0, 100, 30);
 	}
 
@@ -182,4 +226,5 @@
 		if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
 			g.drawRect(x1, y1, x2-x1+1, y2-y1+1);
+		super.paint(g);
 	}
 
Index: src/org/openstreetmap/josm/gui/NavigatableComponent.java
===================================================================
--- src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 99)
@@ -8,9 +8,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.coor.EastNorth;
-import org.openstreetmap.josm.data.osm.Segment;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Segment;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.projection.Projection;
@@ -24,4 +24,6 @@
 public class NavigatableComponent extends JComponent {
 
+	public static final EastNorth world = Main.proj.latlon2eastNorth(new LatLon(Projection.MAX_LAT, Projection.MAX_LON));
+
 	/**
 	 * The scale factor in x or y-units per pixel. This means, if scale = 10,
@@ -35,4 +37,20 @@
 	protected EastNorth center;
 
+	public NavigatableComponent() {
+		setLayout(null);
+    }
+
+	/**
+	 * Return the OSM-conform zoom factor (0 for whole world, 1 for half, 2 for quarter...)
+	 */
+	public int zoom() {
+		double sizex = scale * getWidth();
+		double sizey = scale * getHeight();
+		for (int zoom = 0; zoom <= 32; zoom++, sizex *= 2, sizey *= 2)
+			if (sizex > world.east() || sizey > world.north())
+				return zoom;
+		return 32;
+	}
+	
 	/**
 	 * Return the current scale value.
Index: src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java
===================================================================
--- src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 99)
@@ -82,5 +82,5 @@
 		} catch (FileNotFoundException x) {
 			x.printStackTrace();
-			errorMessage = "URL not found: " + x.getMessage();
+			errorMessage = "Not found: " + x.getMessage();
 		} catch (IOException x) {
 			x.printStackTrace();
Index: src/org/openstreetmap/josm/gui/SelectionManager.java
===================================================================
--- src/org/openstreetmap/josm/gui/SelectionManager.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/SelectionManager.java	(revision 99)
@@ -6,5 +6,7 @@
 import java.awt.Point;
 import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
@@ -15,8 +17,12 @@
 import java.util.LinkedList;
 
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.KeyStroke;
+
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.Segment;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Segment;
 import org.openstreetmap.josm.data.osm.Way;
 
@@ -121,4 +127,13 @@
 		eventSource.addMouseMotionListener(this);
 		selectionEndedListener.addPropertyChangeListener(this);
+		Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "SelectionManager");
+        Main.contentPane.getActionMap().put("SelectionManager", new AbstractAction(){
+			public void actionPerformed(ActionEvent e) {
+				if (mousePos != null && mousePosStart != null)
+					paintRect();
+				mousePosStart = null;
+				mousePos = null;
+            }
+        });
 	}
 	/**
@@ -132,4 +147,6 @@
 		eventSource.removeMouseMotionListener(this);
 		selectionEndedListener.removePropertyChangeListener(this);
+		Main.contentPane.getInputMap().remove(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
+		Main.contentPane.getActionMap().remove("SelectionManager");
 	}
 
@@ -327,21 +344,7 @@
 	}
 	
-	
-	/**
-	 * Does nothing. Only to satisfy MouseListener
-	 */
 	public void mouseClicked(MouseEvent e) {}
-	/**
-	 * Does nothing. Only to satisfy MouseListener
-	 */
 	public void mouseEntered(MouseEvent e) {}
-	/**
-	 * Does nothing. Only to satisfy MouseListener
-	 */
 	public void mouseExited(MouseEvent e) {}
-	/**
-	 * Does nothing. Only to satisfy MouseMotionListener
-	 */
 	public void mouseMoved(MouseEvent e) {}
-
 }
Index: src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 98)
+++ src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 99)
@@ -38,4 +38,7 @@
 			public void layerRemoved(Layer oldLayer) {}
 		});
+		if (mapFrame.mapView.editLayer != null)
+			mapFrame.mapView.editLayer.listenerCommands.add(this);
+			
 		tree.setRootVisible(false);
 		tree.setShowsRootHandles(true);
Index: src/org/openstreetmap/josm/gui/layer/GeoImageLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/GeoImageLayer.java	(revision 99)
+++ src/org/openstreetmap/josm/gui/layer/GeoImageLayer.java	(revision 99)
@@ -0,0 +1,404 @@
+package org.openstreetmap.josm.gui.layer;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+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.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.JToggleButton;
+import javax.swing.JViewport;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+import javax.swing.filechooser.FileFilter;
+
+import org.jdom.JDOMException;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+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.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.xml.sax.SAXException;
+
+/**
+ * A layer which imports several photos from disk and read EXIF time information from them.
+ *
+ * @author Imi
+ */
+public class GeoImageLayer extends Layer {
+
+	private static final class ImageEntry {
+		File image;
+		Date time;
+		LatLon coor;
+		EastNorth pos;
+		Icon icon;
+	}
+
+	private static final class Loader extends PleaseWaitRunnable {
+		boolean cancelled = false;
+		private GeoImageLayer layer;
+		private final Collection<File> files;
+		private final RawGpsLayer gpsLayer;
+		public Loader(Collection<File> files, RawGpsLayer gpsLayer) {
+	        super("Images");
+	        this.files = files;
+			this.gpsLayer = gpsLayer;
+        }
+		@Override protected void realRun() throws SAXException, JDOMException, IOException {
+			currentAction.setText("Read GPS...");
+			layer = new GeoImageLayer();
+
+			// check the gps layer for time loops (and process it on the way)
+			Date last = null;
+			Pattern reg = Pattern.compile("(\\d\\d/\\d\\d/\\d{4}).(\\d\\d:\\d\\d:\\d\\d)");
+			try {
+				for (Collection<GpsPoint> c : gpsLayer.data) {
+					for (GpsPoint p : c) {
+						if (p.time == null)
+							throw new IOException("No time for point "+p.latlon.lat()+","+p.latlon.lon());
+						Matcher m = reg.matcher(p.time);
+						if (!m.matches())
+							throw new IOException("Cannot read time from point "+p.latlon.lat()+","+p.latlon.lon());
+						Date d = dateFormat.parse(m.group(1)+" "+m.group(2));
+						layer.gps.add(new TimedPoint(d, p.eastNorth));
+						if (last != null && last.after(d))
+							throw new IOException("Time loop in gps data.");
+					}
+				}
+			} catch (ParseException e) {
+				e.printStackTrace();
+				throw new IOException("Incorrect date information");
+			}
+
+			if (layer.gps.isEmpty()) {
+				layer.data = new ArrayList<ImageEntry>();
+				return;
+			}
+
+			// read the image files
+			layer.data = new ArrayList<ImageEntry>(files.size());
+			int i = 0;
+			progress.setMaximum(files.size());
+			for (File f : files) {
+				if (cancelled)
+					break;
+				currentAction.setText("Reading "+f.getName()+"...");
+				progress.setValue(i++);
+
+				ImageEntry e = new ImageEntry();
+				e.time = ExifReader.readTime(f);
+				if (e.time == null)
+					continue;
+				e.image = f;
+				e.icon = loadScaledImage(f, 16);
+
+				layer.data.add(e);
+			}
+			layer.calculatePosition();
+        }
+		@Override protected void finish() {
+			if (layer != null)
+				Main.main.addLayer(layer);
+		}
+		@Override protected void cancel() {cancelled = true;}
+	}
+	
+	public List<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");
+
+	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 {
+		Date time;
+		EastNorth pos;
+		public TimedPoint(Date time, EastNorth pos) {
+			this.time = time;
+			this.pos = pos;
+		}
+	}
+
+	public static void create(Collection<File> files, RawGpsLayer gpsLayer) {
+		Loader loader = new Loader(files, gpsLayer);
+		new Thread(loader).start();
+		loader.pleaseWaitDlg.setVisible(true);
+	}
+	
+	private GeoImageLayer() {
+		super("Geotagged Images");
+		Main.map.mapView.addMouseListener(new MouseAdapter(){
+			@Override public void mousePressed(MouseEvent e) {
+				if (e.getButton() != MouseEvent.BUTTON1)
+					return;
+				mousePressed  = true;
+				if (visible)
+					Main.map.mapView.repaint();
+            }
+			@Override public void mouseReleased(MouseEvent ev) {
+				if (ev.getButton() != MouseEvent.BUTTON1)
+					return;
+				mousePressed = false;
+				if (!visible)
+					return;
+				for (ImageEntry e : data) {
+					if (e.pos == null)
+						continue;
+					Point p = Main.map.mapView.getPoint(e.pos);
+					Rectangle r = new Rectangle(p.x-e.icon.getIconWidth()/2, p.y-e.icon.getIconHeight()/2, e.icon.getIconWidth(), e.icon.getIconHeight());
+					if (r.contains(ev.getPoint())) {
+						showImage(e);
+						break;
+					}
+				}
+				Main.map.mapView.repaint();
+            }
+		});
+	}
+
+	private void showImage(final ImageEntry e) {
+		final JPanel p = new JPanel(new BorderLayout());
+		final JScrollPane scroll = new JScrollPane(new JLabel(new ImageIcon(e.image.getPath())));
+		scroll.setPreferredSize(new Dimension(800,600));
+		p.add(scroll, BorderLayout.CENTER);
+		
+		final JToggleButton scale = new JToggleButton(ImageProvider.get("misc", "rectangle"));
+		JPanel p2 = new JPanel();
+		p2.add(scale);
+		p.add(p2, BorderLayout.SOUTH);
+		scale.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent ev) {
+				JViewport vp = scroll.getViewport();
+				p.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+				if (scale.getModel().isSelected())
+					vp.setView(new JLabel(loadScaledImage(e.image, Math.max(vp.getWidth(), vp.getHeight()))));
+				else
+					vp.setView(new JLabel(new ImageIcon(e.image.getPath())));
+				p.setCursor(Cursor.getDefaultCursor());
+            }
+		});
+		JOptionPane.showMessageDialog(Main.parent, p, e.image+" ("+e.coor.lat()+","+e.coor.lon()+")", JOptionPane.PLAIN_MESSAGE);
+    }
+
+	@Override public Icon getIcon() {
+		return ImageProvider.get("layer", "tagimages");
+	}
+
+	@Override public Object getInfoComponent() {
+		JPanel p = new JPanel(new GridBagLayout());
+		p.add(new JLabel(getToolTipText()), GBC.eop());
+
+		p.add(new JLabel("GPS start: "+dateFormat.format(gps.getFirst().time)), GBC.eol());
+		p.add(new JLabel("GPS end: "+dateFormat.format(gps.getLast().time)), GBC.eop());
+		
+		p.add(new JLabel("current delta: "+(delta/1000.0)+"s"), GBC.eol());
+		p.add(new JLabel("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(e.icon);
+				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()+" images, "+i+" within the track.";
+	}
+
+	@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(Graphics g, MapView mv) {
+		boolean clickedFound = false;
+		for (ImageEntry e : data) {
+			if (e.pos != null) {
+				Point p = mv.getPoint(e.pos);
+				Rectangle r = new Rectangle(p.x-e.icon.getIconWidth()/2, p.y-e.icon.getIconHeight()/2, e.icon.getIconWidth(), e.icon.getIconHeight());
+				e.icon.paintIcon(mv, g, r.x, r.y);
+				Border b = BorderFactory.createBevelBorder(!clickedFound && mousePressed && r.contains(mv.getMousePosition()) ? BevelBorder.LOWERED : BevelBorder.RAISED);
+				Insets i = b.getBorderInsets(mv);
+				r.grow((i.top+i.bottom)/2, (i.left+i.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 void addMenuEntries(JPopupMenu menu) {
+		JMenuItem sync = new JMenuItem("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 "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();
+			}
+		});
+		menu.add(sync);
+
+		menu.addSeparator();
+		menu.add(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) {
+					double x = (lastTP.pos.east()+tp.pos.east())/2;
+					double y = (lastTP.pos.north()+tp.pos.north())/2;
+					e.pos = new EastNorth(x,y);
+					break;
+				}
+				lastTP = tp;
+			}
+			if (e.pos != null)
+				e.coor = Main.proj.eastNorth2latlon(e.pos);
+		}
+	}
+
+	private void sync(File f) {
+		Date exifDate = ExifReader.readTime(f);
+		JPanel p = new JPanel(new GridBagLayout());
+		p.add(new JLabel("Image"), GBC.eol());
+		p.add(new JLabel(loadScaledImage(f, 300)), GBC.eop());
+		p.add(new JLabel("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("GPS unit timezome (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, "Syncronize Time with GPS Unit", JOptionPane.OK_CANCEL_OPTION);
+			if (answer != JOptionPane.OK_OPTION || gpsText.getText().equals(""))
+				return;
+			try {
+				delta = dateFormat.parse(gpsText.getText()).getTime() - exifDate.getTime();
+				Main.pref.put("tagimages.delta", ""+delta);
+				String time = gpsTimezone.getText();
+				if (!time.equals("") && time.charAt(0) == '+')
+					time = time.substring(1);
+				if (time.equals(""))
+					time = "0";
+				Main.pref.put("tagimages.gpstimezone", time);
+				gpstimezone = Long.valueOf(time)*60*60*1000;
+				calculatePosition();
+				return;
+			} catch (ParseException x) {
+				JOptionPane.showMessageDialog(Main.parent, "Time entered could not be parsed.");
+			}
+		}
+	}
+
+	private static Icon loadScaledImage(File f, int maxSize) {
+		Image img = new ImageIcon(f.getPath()).getImage();
+		int w = img.getWidth(null);
+		int h = img.getHeight(null);
+		if (w>h) {
+			h = Math.round(maxSize*((float)h/w));
+			w = maxSize;
+		} else {
+			w = Math.round(maxSize*((float)w/h));
+			h = maxSize;
+		}
+		return new ImageIcon(img.getScaledInstance(w, h, Image.SCALE_SMOOTH));
+	}
+}
Index: src/org/openstreetmap/josm/gui/layer/RawGpsDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/RawGpsDataLayer.java	(revision 98)
+++ 	(revision )
@@ -1,158 +1,0 @@
-package org.openstreetmap.josm.gui.layer;
-
-import java.awt.Color;
-import java.awt.Graphics;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.Collection;
-
-import javax.swing.Icon;
-import javax.swing.JColorChooser;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.GpxExportAction;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
-import org.openstreetmap.josm.data.coor.EastNorth;
-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.dialogs.LayerListPopup;
-import org.openstreetmap.josm.tools.ColorHelper;
-import org.openstreetmap.josm.tools.ImageProvider;
-
-/**
- * A layer holding data from a gps source.
- * The data is read only.
- * 
- * @author imi
- */
-public class RawGpsDataLayer extends Layer {
-
-	private static Icon icon;
-	
-	public static class GpsPoint {
-		public final LatLon latlon;
-		public final EastNorth eastNorth;
-		public final String time;
-		public GpsPoint(LatLon ll, String t) {
-			latlon = ll; 
-			eastNorth = Main.proj.latlon2eastNorth(ll); 
-			time = t;
-		}
-	}
-	
-	/**
-	 * A list of ways which containing a list of points.
-	 */
-	public final Collection<Collection<GpsPoint>> data;
-
-	public RawGpsDataLayer(Collection<Collection<GpsPoint>> data, String name) {
-		super(name);
-		this.data = data;
-		Main.pref.listener.add(new PreferenceChangedListener(){
-        	public void preferenceChanged(String key, String newValue) {
-        		if (Main.map != null && (key.equals("drawRawGpsLines") || key.equals("forceRawGpsLines")))
-        			Main.map.repaint();
-        	}
-        });
-	}
-
-	/**
-	 * Return a static icon.
-	 */
-	@Override public Icon getIcon() {
-		if (icon == null)
-			icon = ImageProvider.get("layer", "rawgps");
-		return icon;
-	}
-
-	@Override public void paint(Graphics g, MapView mv) {
-		String gpsCol = Main.pref.get("color.gps point");
-		String gpsColSpecial = Main.pref.get("color.layer "+name);
-		if (!gpsColSpecial.equals(""))
-			g.setColor(ColorHelper.html2color(gpsColSpecial));
-		else if (!gpsCol.equals(""))
-			g.setColor(ColorHelper.html2color(gpsCol));
-		else
-			g.setColor(Color.GRAY);
-		Point old = null;
-		for (Collection<GpsPoint> c : data) {
-			if (!Main.pref.getBoolean("forceRawGpsLines"))
-				old = null;
-			for (GpsPoint p : c) {
-				Point screen = mv.getPoint(p.eastNorth);
-				if (Main.pref.getBoolean("drawRawGpsLines") && old != null)
-					g.drawLine(old.x, old.y, screen.x, screen.y);
-				else
-					g.drawRect(screen.x, screen.y, 0, 0);
-				old = screen;
-			}
-		}
-	}
-
-	@Override public String getToolTipText() {
-		int points = 0;
-		for (Collection<GpsPoint> c : data)
-			points += c.size();
-		return data.size()+" tracks, "+points+" points.";
-	}
-
-	@Override public void mergeFrom(Layer from) {
-		RawGpsDataLayer layer = (RawGpsDataLayer)from;
-		data.addAll(layer.data);
-	}
-
-	@Override public boolean isMergable(Layer other) {
-		return other instanceof RawGpsDataLayer;
-	}
-
-	@Override public void visitBoundingBox(BoundingXYVisitor v) {
-		for (Collection<GpsPoint> c : data)
-			for (GpsPoint p : c)
-				v.visit(p.eastNorth);
-	}
-
-	@Override public Object getInfoComponent() {
-		StringBuilder b = new StringBuilder();
-		int points = 0;
-		for (Collection<GpsPoint> c : data) {
-			b.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a track with "+c.size()+" points<br>");
-			points += c.size();
-		}
-		b.append("</html>");
-		return "<html>"+name+" consists of "+data.size()+" tracks ("+points+" points)<br>"+b.toString();
-	}
-
-	@Override public void addMenuEntries(JPopupMenu menu) {
-		menu.add(new JMenuItem(new GpxExportAction(this)));
-		
-		JMenuItem color = new JMenuItem("Customize Color", ImageProvider.get("colorchooser"));
-		color.addActionListener(new ActionListener(){
-			public void actionPerformed(ActionEvent e) {
-				String col = Main.pref.get("color.layer "+name, Main.pref.get("color.gps point", ColorHelper.color2html(Color.gray)));
-				JColorChooser c = new JColorChooser(ColorHelper.html2color(col));
-				Object[] options = new Object[]{"OK", "Cancel", "Default"};
-				int answer = JOptionPane.showOptionDialog(Main.parent, c, "Choose a color", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
-				switch (answer) {
-				case 0:
-					Main.pref.put("color.layer "+name, ColorHelper.color2html(c.getColor()));
-					break;
-				case 1:
-					return;
-				case 2:
-					Main.pref.put("color.layer "+name, null);
-					break;
-				}
-				Main.map.repaint();
-			}
-		});
-		menu.add(color);
-		
-		menu.addSeparator();
-		menu.add(new LayerListPopup.InfoAction(this));
-    }
-}
Index: src/org/openstreetmap/josm/gui/layer/RawGpsLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/RawGpsLayer.java	(revision 99)
+++ src/org/openstreetmap/josm/gui/layer/RawGpsLayer.java	(revision 99)
@@ -0,0 +1,198 @@
+package org.openstreetmap.josm.gui.layer;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.swing.Icon;
+import javax.swing.JColorChooser;
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.GpxExportAction;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.coor.EastNorth;
+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.dialogs.LayerListPopup;
+import org.openstreetmap.josm.tools.ColorHelper;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A layer holding data from a gps source.
+ * The data is read only.
+ * 
+ * @author imi
+ */
+public class RawGpsLayer extends Layer {
+
+	private static Icon icon;
+	
+	public static class GpsPoint {
+		public final LatLon latlon;
+		public final EastNorth eastNorth;
+		public final String time;
+		public GpsPoint(LatLon ll, String t) {
+			latlon = ll; 
+			eastNorth = Main.proj.latlon2eastNorth(ll); 
+			time = t;
+		}
+	}
+	
+	/**
+	 * A list of ways which containing a list of points.
+	 */
+	public final Collection<Collection<GpsPoint>> data;
+
+	public RawGpsLayer(Collection<Collection<GpsPoint>> data, String name) {
+		super(name);
+		this.data = data;
+		Main.pref.listener.add(new PreferenceChangedListener(){
+        	public void preferenceChanged(String key, String newValue) {
+        		if (Main.map != null && (key.equals("drawRawGpsLines") || key.equals("forceRawGpsLines")))
+        			Main.map.repaint();
+        	}
+        });
+	}
+
+	/**
+	 * Return a static icon.
+	 */
+	@Override public Icon getIcon() {
+		if (icon == null)
+			icon = ImageProvider.get("layer", "rawgps");
+		return icon;
+	}
+
+	@Override public void paint(Graphics g, MapView mv) {
+		String gpsCol = Main.pref.get("color.gps point");
+		String gpsColSpecial = Main.pref.get("color.layer "+name);
+		if (!gpsColSpecial.equals(""))
+			g.setColor(ColorHelper.html2color(gpsColSpecial));
+		else if (!gpsCol.equals(""))
+			g.setColor(ColorHelper.html2color(gpsCol));
+		else
+			g.setColor(Color.GRAY);
+		Point old = null;
+		for (Collection<GpsPoint> c : data) {
+			if (!Main.pref.getBoolean("forceRawGpsLines"))
+				old = null;
+			for (GpsPoint p : c) {
+				Point screen = mv.getPoint(p.eastNorth);
+				if (Main.pref.getBoolean("drawRawGpsLines") && old != null)
+					g.drawLine(old.x, old.y, screen.x, screen.y);
+				else
+					g.drawRect(screen.x, screen.y, 0, 0);
+				old = screen;
+			}
+		}
+	}
+
+	@Override public String getToolTipText() {
+		int points = 0;
+		for (Collection<GpsPoint> c : data)
+			points += c.size();
+		return data.size()+" tracks, "+points+" points.";
+	}
+
+	@Override public void mergeFrom(Layer from) {
+		RawGpsLayer layer = (RawGpsLayer)from;
+		data.addAll(layer.data);
+	}
+
+	@Override public boolean isMergable(Layer other) {
+		return other instanceof RawGpsLayer;
+	}
+
+	@Override public void visitBoundingBox(BoundingXYVisitor v) {
+		for (Collection<GpsPoint> c : data)
+			for (GpsPoint p : c)
+				v.visit(p.eastNorth);
+	}
+
+	@Override public Object getInfoComponent() {
+		StringBuilder b = new StringBuilder();
+		int points = 0;
+		for (Collection<GpsPoint> c : data) {
+			b.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a track with "+c.size()+" points<br>");
+			points += c.size();
+		}
+		b.append("</html>");
+		return "<html>"+name+" consists of "+data.size()+" tracks ("+points+" points)<br>"+b.toString();
+	}
+
+	@Override public void addMenuEntries(JPopupMenu menu) {
+		menu.add(new JMenuItem(new GpxExportAction(this)));
+		
+		JMenuItem color = new JMenuItem("Customize Color", ImageProvider.get("colorchooser"));
+		color.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				String col = Main.pref.get("color.layer "+name, Main.pref.get("color.gps point", ColorHelper.color2html(Color.gray)));
+				JColorChooser c = new JColorChooser(ColorHelper.html2color(col));
+				Object[] options = new Object[]{"OK", "Cancel", "Default"};
+				int answer = JOptionPane.showOptionDialog(Main.parent, c, "Choose a color", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
+				switch (answer) {
+				case 0:
+					Main.pref.put("color.layer "+name, ColorHelper.color2html(c.getColor()));
+					break;
+				case 1:
+					return;
+				case 2:
+					Main.pref.put("color.layer "+name, null);
+					break;
+				}
+				Main.map.repaint();
+			}
+		});
+		menu.add(color);
+		
+		JMenuItem tagimage = new JMenuItem("Import images", ImageProvider.get("tagimages"));
+		tagimage.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
+				fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+				fc.setMultiSelectionEnabled(true);
+				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 "JPEG images (*.jpg)";
+					}
+				});
+				fc.showOpenDialog(Main.parent);
+				File[] sel = fc.getSelectedFiles();
+				if (sel == null || sel.length == 0)
+					return;
+				LinkedList<File> files = new LinkedList<File>();
+				addRecursiveFiles(files, sel);
+				Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
+				GeoImageLayer.create(files, RawGpsLayer.this);
+            }
+
+			private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
+				for (File f : sel) {
+					if (f.isDirectory())
+						addRecursiveFiles(files, f.listFiles());
+					else if (f.getName().toLowerCase().endsWith(".jpg"))
+						files.add(f);
+				}
+            }
+		});
+		menu.add(tagimage);
+		
+		menu.addSeparator();
+		menu.add(new LayerListPopup.InfoAction(this));
+    }
+}
Index: src/org/openstreetmap/josm/io/GpxWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxWriter.java	(revision 98)
+++ src/org/openstreetmap/josm/io/GpxWriter.java	(revision 99)
@@ -23,5 +23,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 import org.openstreetmap.josm.tools.XmlWriter;
 
Index: src/org/openstreetmap/josm/io/OsmConnection.java
===================================================================
--- src/org/openstreetmap/josm/io/OsmConnection.java	(revision 98)
+++ src/org/openstreetmap/josm/io/OsmConnection.java	(revision 99)
@@ -8,4 +8,5 @@
 
 import javax.swing.BoundedRangeModel;
+import javax.swing.JCheckBox;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
@@ -29,5 +30,5 @@
 	protected JLabel currentAction;
 	protected BoundedRangeModel progress;
-	
+
 	private static OsmAuth authentication;
 	/**
@@ -38,46 +39,56 @@
 		Authenticator.setDefault(authentication = new OsmAuth());
 	}
-	
+
 	/**
-     * The authentication class handling the login requests.
-     */
-    private static class OsmAuth extends Authenticator {
-    	/**
-    	 * Set to true, when the autenticator tried the password once.
-    	 */
-    	boolean passwordtried = false;
-    	/**
-    	 * Whether the user cancelled the password dialog
-    	 */
-    	boolean authCancelled = false;
-    
-    	@Override protected PasswordAuthentication getPasswordAuthentication() {
-    		String username = Main.pref.get("osm-server.username");
-    		String password = Main.pref.get("osm-server.password");
-    		if (passwordtried || username.equals("") || password.equals("")) {
-    			JPanel p = new JPanel(new GridBagLayout());
-    			p.add(new JLabel("Username"), GBC.std().insets(0,0,10,0));
-    			JTextField usernameField = new JTextField(username, 20);
-    			p.add(usernameField, GBC.eol());
-    			p.add(new JLabel("Password"), GBC.std().insets(0,0,10,0));
-    			JPasswordField passwordField = new JPasswordField(password, 20);
-    			p.add(passwordField, GBC.eol());
-    			JLabel warning = new JLabel("Warning: The password is transferred unencrypted.");
-    			warning.setFont(warning.getFont().deriveFont(Font.ITALIC));
-    			p.add(warning, GBC.eol());
-    			int choice = JOptionPane.showConfirmDialog(Main.parent, p, "Enter Password", JOptionPane.OK_CANCEL_OPTION);
-    			if (choice == JOptionPane.CANCEL_OPTION) {
-    				authCancelled = true;
-    				return null;
-    			}
-    			username = usernameField.getText();
-    			password = String.valueOf(passwordField.getPassword());
-    			if (username.equals(""))
-    				return null;
-    		}
-    		passwordtried = true;
-    		return new PasswordAuthentication(username, password.toCharArray());
-    	}
-    }
+	 * The authentication class handling the login requests.
+	 */
+	private static class OsmAuth extends Authenticator {
+		/**
+		 * Set to true, when the autenticator tried the password once.
+		 */
+		boolean passwordtried = false;
+		/**
+		 * Whether the user cancelled the password dialog
+		 */
+		boolean authCancelled = false;
+
+		@Override protected PasswordAuthentication getPasswordAuthentication() {
+			String username = Main.pref.get("osm-server.username");
+			String password = Main.pref.get("osm-server.password");
+			if (passwordtried || username.equals("") || password.equals("")) {
+				JPanel p = new JPanel(new GridBagLayout());
+				if (!username.equals("") && !password.equals(""))
+					p.add(new JLabel("Incorrect password or username."), GBC.eop());
+				p.add(new JLabel("Username"), GBC.std().insets(0,0,10,0));
+				JTextField usernameField = new JTextField(username, 20);
+				p.add(usernameField, GBC.eol());
+				p.add(new JLabel("Password"), GBC.std().insets(0,0,10,0));
+				JPasswordField passwordField = new JPasswordField(password, 20);
+				p.add(passwordField, GBC.eol());
+				JLabel warning = new JLabel("Warning: The password is transferred unencrypted.");
+				warning.setFont(warning.getFont().deriveFont(Font.ITALIC));
+				p.add(warning, GBC.eop());
+
+				JCheckBox savePassword = new JCheckBox("Save user and password (unencrypted)", !username.equals("") && !password.equals(""));
+				p.add(savePassword, GBC.eop());
+
+				int choice = JOptionPane.showConfirmDialog(Main.parent, p, "Enter Password", JOptionPane.OK_CANCEL_OPTION);
+				if (choice == JOptionPane.CANCEL_OPTION) {
+					authCancelled = true;
+					return null;
+				}
+				username = usernameField.getText();
+				password = String.valueOf(passwordField.getPassword());
+				if (savePassword.isSelected()) {
+					Main.pref.put("osm-server.username", username);
+					Main.pref.put("osm-server.password", password);
+				}
+				if (username.equals(""))
+					return null;
+			}
+			passwordtried = true;
+			return new PasswordAuthentication(username, password.toCharArray());
+		}
+	}
 
 	/**
@@ -88,5 +99,5 @@
 		authentication.passwordtried = false;
 	}
-	
+
 	/**
 	 * @return Whether the connection was cancelled.
@@ -99,14 +110,14 @@
 		this.currentAction = currentAction;
 		this.progress = progress;
-    }
+	}
 
 	public void cancel() {
 		currentAction.setText("Aborting...");
-    	cancel = true;
-    	if (activeConnection != null) {
-    		activeConnection.setConnectTimeout(1);
-    		activeConnection.setReadTimeout(1);
-    		activeConnection.disconnect();
-    	}
-    }
+		cancel = true;
+		if (activeConnection != null) {
+			activeConnection.setConnectTimeout(1);
+			activeConnection.setReadTimeout(1);
+			activeConnection.disconnect();
+		}
+	}
 }
Index: src/org/openstreetmap/josm/io/OsmServerReader.java
===================================================================
--- src/org/openstreetmap/josm/io/OsmServerReader.java	(revision 98)
+++ src/org/openstreetmap/josm/io/OsmServerReader.java	(revision 99)
@@ -11,5 +11,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 import org.xml.sax.SAXException;
 
Index: src/org/openstreetmap/josm/io/RawCsvReader.java
===================================================================
--- src/org/openstreetmap/josm/io/RawCsvReader.java	(revision 98)
+++ src/org/openstreetmap/josm/io/RawCsvReader.java	(revision 99)
@@ -12,5 +12,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 
 /**
@@ -28,5 +28,5 @@
 		this.in = new BufferedReader(in);
 	}
-	
+
 	public Collection<GpsPoint> parse() throws JDOMException, IOException {
 		Collection<GpsPoint> data = new LinkedList<GpsPoint>();
@@ -36,5 +36,5 @@
 		if (formatStr == null)
 			throw new JDOMException("Could not detect data format string.");
-		
+
 		// get delimiter
 		String delim = ",";
@@ -45,9 +45,13 @@
 			}
 		}
-		
+
 		// convert format string
 		ArrayList<String> format = new ArrayList<String>();
-		for (StringTokenizer st = new StringTokenizer(formatStr, delim); st.hasMoreTokens();)
-			format.add(st.nextToken());
+		for (StringTokenizer st = new StringTokenizer(formatStr, delim); st.hasMoreTokens();) {
+			String token = st.nextToken();
+			if (!token.equals("lat") && !token.equals("lon") && !token.equals("time"))
+				token = "ignore";
+			format.add(token);
+		}
 
 		// test for completness
@@ -57,5 +61,5 @@
 			throw new JDOMException("Format string is incomplete. Need at least 'lat' and 'lon' specification");
 		}
-		
+
 		int lineNo = 0;
 		try {
Index: src/org/openstreetmap/josm/io/RawGpsReader.java
===================================================================
--- src/org/openstreetmap/josm/io/RawGpsReader.java	(revision 98)
+++ src/org/openstreetmap/josm/io/RawGpsReader.java	(revision 99)
@@ -11,5 +11,5 @@
 import org.jdom.input.SAXBuilder;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.gui.layer.RawGpsDataLayer.GpsPoint;
+import org.openstreetmap.josm.gui.layer.RawGpsLayer.GpsPoint;
 
 /**
Index: src/org/openstreetmap/josm/tools/ExifReader.java
===================================================================
--- src/org/openstreetmap/josm/tools/ExifReader.java	(revision 99)
+++ src/org/openstreetmap/josm/tools/ExifReader.java	(revision 99)
@@ -0,0 +1,34 @@
+package org.openstreetmap.josm.tools;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+
+import com.drew.imaging.jpeg.JpegMetadataReader;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.Tag;
+
+/**
+ * Read out exif file information from a jpeg file
+ * @author Imi
+ */
+public class ExifReader {
+
+	@SuppressWarnings("unchecked") public static Date readTime(File filename) {
+		try {
+	        Metadata metadata = JpegMetadataReader.readMetadata(filename);
+	        for (Iterator<Directory> dirIt = metadata.getDirectoryIterator(); dirIt.hasNext();) {
+	            for (Iterator<Tag> tagIt = dirIt.next().getTagIterator(); tagIt.hasNext();) {
+	                Tag tag = tagIt.next();
+	                if (tag.getTagType() == 0x132 || tag.getTagType() == 0x9003 || tag.getTagType() == 0x9004)
+	                	return new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(tag.getDescription());
+	            }
+	        }
+        } catch (Exception e) {
+	        e.printStackTrace();
+        }
+		return null;
+	}
+}
