Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryData.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryData.java	(revision 31248)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryData.java	(revision 31249)
@@ -6,4 +6,5 @@
 import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
 import org.openstreetmap.josm.plugins.mapillary.cache.MapillaryCache;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryToggleDialog;
 
 import java.util.ArrayList;
@@ -191,5 +192,10 @@
 		Main.map.mapView.repaint();
 	}
-	
+
+	/**
+	 * Adds a set of MapillaryImage objects to the list of selected images.
+	 * 
+	 * @param images
+	 */
 	public void addMultiSelectedImage(List<MapillaryImage> images) {
 		for (MapillaryImage image : images)
@@ -199,5 +205,5 @@
 				else
 					this.setSelectedImage(image);
-		}
+			}
 		Main.map.mapView.repaint();
 	}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryExportAction.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryExportAction.java	(revision 31248)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryExportAction.java	(revision 31249)
@@ -14,4 +14,5 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryExportManager;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryExportDialog;
 import org.openstreetmap.josm.tools.ImageProvider;
 
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java	(revision 31248)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java	(revision 31249)
@@ -9,4 +9,5 @@
 import org.openstreetmap.josm.plugins.mapillary.commands.MapillaryRecord;
 import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryToggleDialog;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.layer.Layer;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java	(revision 31248)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java	(revision 31249)
@@ -18,4 +18,6 @@
 import org.openstreetmap.josm.plugins.Plugin;
 import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryPreferenceSetting;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryToggleDialog;
 import org.openstreetmap.josm.tools.ImageProvider;
 
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java	(revision 31249)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java	(revision 31249)
@@ -0,0 +1,126 @@
+package org.openstreetmap.josm.plugins.mapillary.gui;
+
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+import java.awt.Desktop;
+
+public class HyperlinkLabel extends JLabel implements ActionListener {
+
+	/**
+	 * The normal text set by the user.
+	 */
+
+	private String text;
+
+	private URL url;
+
+	/**
+	 * Creates a new LinkLabel with the given text.
+	 */
+
+	public HyperlinkLabel() {
+		super("View in website", SwingUtilities.RIGHT);
+		this.addActionListener(this);
+		setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+
+		enableEvents(MouseEvent.MOUSE_EVENT_MASK);
+	}
+
+	/**
+	 * Sets the text of the label.
+	 */
+
+	public void setText(String text) {
+		super.setText("<html><font color=\"#0000CF\" size=\"2\">" + text + "</font></html>"); //$NON-NLS-1$ //$NON-NLS-2$
+		this.text = text;
+	}
+
+	public void setURL(String key) {
+		try {
+			this.url = new URL("http://www.mapillary.com/map/im/" + key);
+		} catch (MalformedURLException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Returns the text set by the user.
+	 */
+
+	public String getNormalText() {
+		return text;
+	}
+
+	/**
+	 * Processes mouse events and responds to clicks.
+	 */
+
+	protected void processMouseEvent(MouseEvent evt) {
+		super.processMouseEvent(evt);
+		if (evt.getID() == MouseEvent.MOUSE_CLICKED)
+			fireActionPerformed(new ActionEvent(this,
+					ActionEvent.ACTION_PERFORMED, getNormalText()));
+	}
+
+	/**
+	 * Adds an ActionListener to the list of listeners receiving notifications
+	 * when the label is clicked.
+	 */
+
+	public void addActionListener(ActionListener listener) {
+		listenerList.add(ActionListener.class, listener);
+	}
+
+	/**
+	 * Removes the given ActionListener from the list of listeners receiving
+	 * notifications when the label is clicked.
+	 */
+
+	public void removeActionListener(ActionListener listener) {
+		listenerList.remove(ActionListener.class, listener);
+	}
+
+	/**
+	 * Fires an ActionEvent to all interested listeners.
+	 */
+
+	protected void fireActionPerformed(ActionEvent evt) {
+		Object[] listeners = listenerList.getListenerList();
+		for (int i = 0; i < listeners.length; i += 2) {
+			if (listeners[i] == ActionListener.class) {
+				ActionListener listener = (ActionListener) listeners[i + 1];
+				listener.actionPerformed(evt);
+			}
+		}
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		if (this.url == null)
+			return;
+		Desktop desktop = Desktop.getDesktop();
+		try {
+			desktop.browse(url.toURI());
+		} catch (IOException | URISyntaxException ex) {
+			ex.printStackTrace();
+		} catch (UnsupportedOperationException ex) {
+			Runtime runtime = Runtime.getRuntime();
+			try {
+				runtime.exec("xdg-open " + url);
+			} catch (IOException exc) {
+				exc.printStackTrace();
+			}
+		}
+	}
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryExportDialog.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryExportDialog.java	(revision 31249)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryExportDialog.java	(revision 31249)
@@ -0,0 +1,99 @@
+package org.openstreetmap.josm.plugins.mapillary.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
+
+/**
+ * GUI for exporting images.
+ * 
+ * @author nokutu
+ *
+ */
+public class MapillaryExportDialog extends JPanel implements ActionListener {
+
+	protected JOptionPane optionPane;
+	// Button to export all downloaded images.
+	public JRadioButton all;
+	// Button to export all images in the sequence of the selected
+	// MapillaryImage.
+	public JRadioButton sequence;
+	// Button to export all images belonging to the selected MapillaryImage
+	// objects.
+	public JRadioButton selected;
+	public ButtonGroup group;
+	protected JButton choose;
+	protected JLabel path;
+	public JFileChooser chooser;
+	protected String exportDirectory;
+
+	public MapillaryExportDialog() {
+		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+
+		group = new ButtonGroup();
+		all = new JRadioButton(tr("Export all images"));
+		sequence = new JRadioButton(tr("Export selected sequence"));
+		selected = new JRadioButton(tr("Export selected images"));
+		group.add(all);
+		group.add(sequence);
+		group.add(selected);
+		// Some options are disabled depending on the circumstances
+		if (MapillaryData.getInstance().getSelectedImage() == null
+				|| MapillaryData.getInstance().getSelectedImage().getSequence() == null) {
+			sequence.setEnabled(false);
+		}
+		if (MapillaryData.getInstance().getMultiSelectedImages().isEmpty()) {
+			selected.setEnabled(false);
+		}
+		path = new JLabel("Select a folder");
+		choose = new JButton(tr("Explore"));
+		choose.addActionListener(this);
+
+		// All options belong to the same jpanel so the are in line.
+		JPanel jpanel = new JPanel();
+		jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.PAGE_AXIS));
+		jpanel.add(all);
+		jpanel.add(sequence);
+		jpanel.add(selected);
+		jpanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+		path.setAlignmentX(Component.CENTER_ALIGNMENT);
+		choose.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+		add(jpanel);
+		add(path);
+		add(choose);
+
+	}
+
+	/**
+	 * Creates the folder choser GUI.
+	 */
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		chooser = new JFileChooser();
+		chooser.setCurrentDirectory(new java.io.File(System
+				.getProperty("user.home")));
+		chooser.setDialogTitle("Select a directory");
+		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+		chooser.setAcceptAllFileFilterUsed(false);
+
+		if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+			path.setText(chooser.getSelectedFile().toString());
+			this.updateUI();
+		}
+	}
+
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java	(revision 31249)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java	(revision 31249)
@@ -0,0 +1,507 @@
+package org.openstreetmap.josm.plugins.mapillary.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
+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.awt.image.BufferedImage;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * This object is a responsible JComponent which lets you zoom and drag. It is
+ * included in a {@link MapillaryToggleDialog} object.
+ * 
+ * @author Jorge
+ * @see ImageDisplay
+ * @see MapillaryToggleDialog
+ */
+public class MapillaryImageDisplay extends JComponent {
+
+	private static final int DRAG_BUTTON = 3;
+	private static final int ZOOM_BUTTON = 1;
+
+	/** The image currently displayed */
+	private transient BufferedImage image = null;
+
+	/**
+	 * 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;
+	/**
+	 * When a selection is done, the rectangle of the selection (in image
+	 * coordinates)
+	 */
+	private Rectangle selectedRect = null;
+
+	public HyperlinkLabel hyperlink;
+
+	private class ImgDisplayMouseListener implements MouseListener,
+			MouseWheelListener, MouseMotionListener {
+		private boolean mouseIsDragging = false;
+		private long lastTimeForMousePoint = 0L;
+		private 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
+		 */
+		@Override
+		public void mouseWheelMoved(MouseWheelEvent e) {
+			Image image;
+			Rectangle visibleRect;
+			synchronized (MapillaryImageDisplay.this) {
+				image = MapillaryImageDisplay.this.image;
+				visibleRect = MapillaryImageDisplay.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.
+			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 (MapillaryImageDisplay.this) {
+				MapillaryImageDisplay.this.visibleRect = visibleRect;
+			}
+			MapillaryImageDisplay.this.repaint();
+		}
+
+		/** Center the display on the point that has been clicked */
+		@Override
+		public void mouseClicked(MouseEvent e) {
+			// Move the center to the clicked point.
+			Image image;
+			Rectangle visibleRect;
+			synchronized (MapillaryImageDisplay.this) {
+				image = MapillaryImageDisplay.this.image;
+				visibleRect = MapillaryImageDisplay.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 (MapillaryImageDisplay.this) {
+				MapillaryImageDisplay.this.visibleRect = visibleRect;
+			}
+			MapillaryImageDisplay.this.repaint();
+		}
+
+		/**
+		 * Initialize the dragging, either with button 1 (simple dragging) or
+		 * button 3 (selection of a picture part)
+		 */
+		@Override
+		public void mousePressed(MouseEvent e) {
+			if (image == null) {
+				mouseIsDragging = false;
+				selectedRect = null;
+				return;
+			}
+			Image image;
+			Rectangle visibleRect;
+			synchronized (MapillaryImageDisplay.this) {
+				image = MapillaryImageDisplay.this.image;
+				visibleRect = MapillaryImageDisplay.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);
+				MapillaryImageDisplay.this.repaint();
+			} else {
+				mouseIsDragging = false;
+				selectedRect = null;
+			}
+		}
+
+		@Override
+		public void mouseDragged(MouseEvent e) {
+			if (!mouseIsDragging && selectedRect == null)
+				return;
+			Image image;
+			Rectangle visibleRect;
+			synchronized (MapillaryImageDisplay.this) {
+				image = MapillaryImageDisplay.this.image;
+				visibleRect = MapillaryImageDisplay.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 (MapillaryImageDisplay.this) {
+					MapillaryImageDisplay.this.visibleRect = visibleRect;
+				}
+				MapillaryImageDisplay.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);
+				MapillaryImageDisplay.this.selectedRect = rect;
+				MapillaryImageDisplay.this.repaint();
+			}
+		}
+
+		@Override
+		public void mouseReleased(MouseEvent e) {
+			if (!mouseIsDragging && selectedRect == null)
+				return;
+			Image image;
+			synchronized (MapillaryImageDisplay.this) {
+				image = MapillaryImageDisplay.this.image;
+			}
+			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 (MapillaryImageDisplay.this) {
+					MapillaryImageDisplay.this.visibleRect = selectedRect;
+				}
+				selectedRect = null;
+				MapillaryImageDisplay.this.repaint();
+			}
+		}
+
+		@Override
+		public void mouseEntered(MouseEvent e) {
+		}
+
+		@Override
+		public void mouseExited(MouseEvent e) {
+		}
+
+		@Override
+		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 MapillaryImageDisplay() {
+		ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
+		addMouseListener(mouseListener);
+		addMouseWheelListener(mouseListener);
+		addMouseMotionListener(mouseListener);
+		this.setLayout(new BorderLayout());
+		JPanel southPanel = new JPanel();
+		southPanel.setLayout(new BorderLayout());
+		hyperlink = new HyperlinkLabel();
+		southPanel.add(hyperlink, BorderLayout.EAST);
+		southPanel.setOpaque(false);
+
+		add(southPanel, BorderLayout.SOUTH);
+	}
+
+	/**
+	 * Sets a new picture to be displayed.
+	 * 
+	 * @param image
+	 */
+	public void setImage(BufferedImage image) {
+		synchronized (this) {
+			this.image = image;
+			selectedRect = null;
+			if (image != null)
+				this.visibleRect = new Rectangle(0, 0, image.getWidth(null),
+						image.getHeight(null));
+		}
+		repaint();
+	}
+
+	/**
+	 * Returns the picture that is being displayerd
+	 * 
+	 * @return
+	 */
+	public BufferedImage getImage() {
+		return this.image;
+	}
+
+	/**
+	 * Paints the visible part of the picture.
+	 */
+	public void paintComponent(Graphics g) {
+		Image image;
+		Rectangle visibleRect;
+		synchronized (this) {
+			image = this.image;
+			visibleRect = this.visibleRect;
+		}
+		if (image == 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 {
+			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);
+			}
+		}
+	}
+
+	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) {
+		return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0,
+				getSize().width, getSize().height));
+	}
+
+	/**
+	 * calculateDrawImageRectangle
+	 *
+	 * @param imgRect
+	 *            the part of the image that should be drawn (in image
+	 *            coordinates)
+	 * @param compRect
+	 *            the part of the component where the image should be drawn (in
+	 *            component coordinates)
+	 * @return the part of compRect with the same width/height ratio as the
+	 *         image
+	 */
+	static Rectangle calculateDrawImageRectangle(Rectangle imgRect,
+			Rectangle compRect) {
+		int x, y, w, h;
+		x = 0;
+		y = 0;
+		w = compRect.width;
+		h = compRect.height;
+		int wFact = w * imgRect.height;
+		int hFact = h * imgRect.width;
+		if (wFact != hFact) {
+			if (wFact > hFact) {
+				w = hFact / imgRect.height;
+				x = (compRect.width - w) / 2;
+			} else {
+				h = wFact / imgRect.width;
+				y = (compRect.height - h) / 2;
+			}
+		}
+		return new Rectangle(x + compRect.x, y + compRect.y, w, h);
+	}
+
+	public void zoomBestFitOrOne() {
+		Image image;
+		Rectangle visibleRect;
+		synchronized (this) {
+			image = MapillaryImageDisplay.this.image;
+			visibleRect = MapillaryImageDisplay.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) {
+			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: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java	(revision 31249)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java	(revision 31249)
@@ -0,0 +1,47 @@
+package org.openstreetmap.josm.plugins.mapillary.gui;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
+
+public class MapillaryPreferenceSetting implements SubPreferenceSetting {
+
+	private JCheckBox reverseButtons = new JCheckBox("Reverse buttons position when displaying images.");;
+	
+	@Override
+	public TabPreferenceSetting getTabPreferenceSetting(PreferenceTabbedPane gui) {
+		return gui.getDisplayPreference();
+	}
+
+	@Override
+	public void addGui(PreferenceTabbedPane gui) {
+		// TODO Auto-generated method stub
+		JPanel panel = new JPanel();
+		
+		reverseButtons.setSelected(Main.pref.getBoolean("mapillary.reverse-buttons"));
+		
+		panel.add(reverseButtons);
+		panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+        gui.getDisplayPreference().addSubTab(this, "Mapillary", panel);
+	}
+
+	@Override
+	public boolean ok() {
+        boolean mod = false;
+        Main.pref.put("mapillary.reverse-buttons", reverseButtons.isSelected());
+        return mod;
+	}
+
+	@Override
+	public boolean isExpert() {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryToggleDialog.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryToggleDialog.java	(revision 31249)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryToggleDialog.java	(revision 31249)
@@ -0,0 +1,302 @@
+package org.openstreetmap.josm.plugins.mapillary.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.cache.CacheEntry;
+import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
+import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+import org.openstreetmap.josm.plugins.mapillary.cache.MapillaryCache;
+
+import javax.imageio.ImageIO;
+import javax.swing.SwingUtilities;
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
+
+/**
+ * Toggle dialog that shows an image and some buttons.
+ * 
+ * @author nokutu
+ *
+ */
+public class MapillaryToggleDialog extends ToggleDialog implements
+		ICachedLoaderListener {
+
+	public static MapillaryToggleDialog INSTANCE;
+
+	public volatile MapillaryImage image;
+
+	final SideButton nextButton = new SideButton(new nextPictureAction());
+	final SideButton previousButton = new SideButton(
+			new previousPictureAction());
+	public final SideButton redButton = new SideButton(new redAction());
+	public final SideButton blueButton = new SideButton(new blueAction());
+
+	private JPanel buttonsPanel;
+	private JPanel top;
+
+	public MapillaryImageDisplay mapillaryImageDisplay;
+
+	private MapillaryCache imageCache;
+	private MapillaryCache thumbnailCache;
+
+	public MapillaryToggleDialog() {
+		super(tr("Mapillary image"), "mapillary.png",
+				tr("Open Mapillary window"), null, 200);
+		mapillaryImageDisplay = new MapillaryImageDisplay();
+
+		// this.add(mapillaryImageDisplay);
+		blueButton.setForeground(Color.BLUE);
+		redButton.setForeground(Color.RED);
+
+		this.setLayout(new BorderLayout());
+		top = new JPanel();
+		top.setLayout(new BorderLayout());
+		top.add(titleBar, BorderLayout.NORTH);
+
+		createLayout(
+				mapillaryImageDisplay,
+				Arrays.asList(new SideButton[] { blueButton, previousButton,
+						nextButton, redButton }),
+				Main.pref.getBoolean("mapillary.reverse-buttons"));
+	}
+
+	public static MapillaryToggleDialog getInstance() {
+		if (INSTANCE == null) {
+			INSTANCE = new MapillaryToggleDialog();
+		}
+		return INSTANCE;
+	}
+
+	public static void destroyInstance() {
+		INSTANCE = null;
+	}
+
+	/**
+	 * Downloads the image of the selected MapillaryImage and sets in the
+	 * MapillaryImageDisplay object.
+	 */
+	public synchronized void updateImage() {
+		if (!SwingUtilities.isEventDispatchThread()) {
+			SwingUtilities.invokeLater(new Runnable() {
+				@Override
+				public void run() {
+					updateImage();
+				}
+			});
+		} else {
+			if (MapillaryLayer.INSTANCED == false) {
+				return;
+			}
+			if (this.image == null)
+				return;
+			this.nextButton.setEnabled(true);
+			this.previousButton.setEnabled(true);
+			if (this.image.next() == null)
+				this.nextButton.setEnabled(false);
+			if (this.image.previous() == null)
+				this.previousButton.setEnabled(false);
+
+			mapillaryImageDisplay.hyperlink.setURL(image.getKey());
+			this.mapillaryImageDisplay.setImage(null);
+			if (thumbnailCache != null)
+				thumbnailCache.cancelOutstandingTasks();
+			thumbnailCache = new MapillaryCache(image.getKey(),
+					MapillaryCache.Type.THUMBNAIL);
+			thumbnailCache.submit(this, false);
+
+			if (imageCache != null)
+				imageCache.cancelOutstandingTasks();
+			imageCache = new MapillaryCache(image.getKey(),
+					MapillaryCache.Type.FULL_IMAGE);
+			imageCache.submit(this, false);
+
+		}
+	}
+
+	/**
+	 * Sets a new MapillaryImage to be shown.
+	 * 
+	 * @param image
+	 */
+	public synchronized void setImage(MapillaryImage image) {
+		this.image = image;
+	}
+
+	/**
+	 * Returns the MapillaryImage objects which is being shown.
+	 * 
+	 * @return
+	 */
+	public synchronized MapillaryImage getImage() {
+		return this.image;
+	}
+
+	/**
+	 * Action class form the next image button.
+	 * 
+	 * @author Jorge
+	 *
+	 */
+	class nextPictureAction extends AbstractAction {
+		public nextPictureAction() {
+			putValue(NAME, tr("Next picture"));
+			putValue(SHORT_DESCRIPTION,
+					tr("Shows the next picture in the sequence"));
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (MapillaryToggleDialog.getInstance().getImage() != null) {
+				MapillaryData.getInstance().selectNext();
+				if (MapillaryData.getInstance().getSelectedImage() != null)
+					Main.map.mapView.zoomTo(MapillaryData.getInstance()
+							.getSelectedImage().getLatLon());
+			}
+		}
+	}
+
+	/**
+	 * Action class for the previous image button.
+	 * 
+	 * @author Jorge
+	 *
+	 */
+	class previousPictureAction extends AbstractAction {
+		public previousPictureAction() {
+			putValue(NAME, tr("Previous picture"));
+			putValue(SHORT_DESCRIPTION,
+					tr("Shows the previous picture in the sequence"));
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (MapillaryToggleDialog.getInstance().getImage() != null) {
+				MapillaryData.getInstance().selectPrevious();
+				if (MapillaryData.getInstance().getSelectedImage() != null)
+					Main.map.mapView.zoomTo(MapillaryData.getInstance()
+							.getSelectedImage().getLatLon());
+			}
+		}
+	}
+
+	class redAction extends AbstractAction {
+		public redAction() {
+			putValue(NAME, "Jump to red");
+			putValue(SHORT_DESCRIPTION,
+					tr("Shows the previous picture in the sequence"));
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (MapillaryToggleDialog.getInstance().getImage() != null) {
+				MapillaryData.getInstance()
+						.setSelectedImage(MapillaryLayer.RED);
+				MapillaryToggleDialog.getInstance()
+						.setImage(MapillaryLayer.RED);
+				MapillaryToggleDialog.getInstance().updateImage();
+				Main.map.mapView.zoomTo(MapillaryData.getInstance()
+						.getSelectedImage().getLatLon());
+			}
+		}
+	}
+
+	class blueAction extends AbstractAction {
+		public blueAction() {
+			putValue(NAME, "Jump to blue");
+			putValue(SHORT_DESCRIPTION,
+					tr("Shows the previous picture in the sequence"));
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (MapillaryToggleDialog.getInstance().getImage() != null) {
+				MapillaryData.getInstance().setSelectedImage(
+						MapillaryLayer.BLUE);
+				MapillaryToggleDialog.getInstance().setImage(
+						MapillaryLayer.BLUE);
+				MapillaryToggleDialog.getInstance().updateImage();
+				Main.map.mapView.zoomTo(MapillaryData.getInstance()
+						.getSelectedImage().getLatLon());
+			}
+		}
+	}
+
+	/**
+	 * When the pictures are returned from the cache, they are set in the
+	 * {@link MapillaryImageDisplay} object.
+	 */
+	@Override
+	public void loadingFinished(CacheEntry data,
+			CacheEntryAttributes attributes, LoadResult result) {
+		if (!SwingUtilities.isEventDispatchThread()) {
+			SwingUtilities.invokeLater(new Runnable() {
+				@Override
+				public void run() {
+					updateImage();
+				}
+			});
+		} else if (data != null) {
+			try {
+				BufferedImage img = ImageIO.read(new ByteArrayInputStream(data
+						.getContent()));
+				if (this.mapillaryImageDisplay.getImage() == null)
+					mapillaryImageDisplay.setImage(img);
+				else if (img.getHeight() > this.mapillaryImageDisplay
+						.getImage().getHeight()) {
+					mapillaryImageDisplay.setImage(img);
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * Creates the layout of the dialog.
+	 * 
+	 * @param data
+	 *            The content of the dialog
+	 * @param buttons
+	 *            The buttons where you can click
+	 * @param reverse
+	 *            {@code true} if the buttons should go at the top;
+	 *            {@code false} otherwise.
+	 */
+	public void createLayout(Component data, List<SideButton> buttons,
+			boolean reverse) {
+		add(data, BorderLayout.CENTER);
+		if (!buttons.isEmpty() && buttons.get(0) != null) {
+			buttonsPanel = new JPanel(new GridLayout(1, 1));
+			final JPanel buttonRowPanel = new JPanel(Main.pref.getBoolean(
+					"dialog.align.left", false) ? new FlowLayout(
+					FlowLayout.LEFT) : new GridLayout(1, buttons.size()));
+			buttonsPanel.add(buttonRowPanel);
+			for (SideButton button : buttons) {
+				buttonRowPanel.add(button);
+			}
+			if (reverse)
+				top.add(buttonsPanel, BorderLayout.SOUTH);
+			else
+				add(buttonsPanel, BorderLayout.SOUTH);
+		}
+		add(top, BorderLayout.NORTH);
+	}
+}
