Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryLayer.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryLayer.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryLayer.java	(revision 24584)
@@ -1,8 +1,23 @@
 package org.openstreetmap.josm.plugins.imagery;
 
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.Component;
+import java.awt.GridBagLayout;
 import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.util.List;
 
+import javax.swing.AbstractAction;
+import javax.swing.Action;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
 
 import org.openstreetmap.josm.Main;
@@ -13,4 +28,6 @@
 import org.openstreetmap.josm.plugins.imagery.tms.TMSLayer;
 import org.openstreetmap.josm.plugins.imagery.wms.WMSLayer;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
 
 public abstract class ImageryLayer extends Layer {
@@ -18,5 +35,5 @@
         new ImageIcon(Toolkit.getDefaultToolkit().createImage(ImageryPlugin.class.getResource("/images/imagery_small.png")));
 
-    protected ImageryInfo info;
+    protected final ImageryInfo info;
     protected MapView mv;
 
@@ -30,13 +47,7 @@
     }
 
-
     public double getPPD(){
         ProjectionBounds bounds = mv.getProjectionBounds();
         return mv.getWidth() / (bounds.max.east() - bounds.min.east());
-    }
-
-    public void displace(double dx, double dy) {
-        this.dx += dx;
-        this.dy += dy;
     }
 
@@ -47,4 +58,13 @@
     public double getDy() {
         return dy;
+    }
+
+    public void setOffset(double dx, double dy) {
+        this.dx = dx;
+        this.dy = dy;
+    }
+
+    public void displace(double dx, double dy) {
+        setOffset(this.dx += dx, this.dy += dy);
     }
 
@@ -79,3 +99,79 @@
         } else throw new AssertionError();
     }
+
+    class ApplyOffsetAction extends AbstractAction {
+        private OffsetBookmark b;
+        ApplyOffsetAction(OffsetBookmark b) {
+            super(b.name);
+            this.b = b;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent arg0) {
+            setOffset(b.dx, b.dy);
+            Main.map.repaint();
+        }
+    }
+
+    class NewBookmarkAction extends AbstractAction {
+        private class BookmarkNamePanel extends JPanel {
+            public JTextField text = new JTextField();
+            public BookmarkNamePanel() {
+                super(new GridBagLayout());
+                add(new JLabel(tr("Bookmark name: ")),GBC.eol());
+                add(text,GBC.eol().fill(GBC.HORIZONTAL));
+            }
+        }
+        public NewBookmarkAction() {
+            super(tr("(save current)"));
+        }
+        @Override
+        public void actionPerformed(ActionEvent arg0) {
+            BookmarkNamePanel p = new BookmarkNamePanel();
+            int answer = JOptionPane.showConfirmDialog(
+                    Main.parent, p,
+                    tr("Add offset bookmark"),
+                    JOptionPane.OK_CANCEL_OPTION);
+            if (answer == JOptionPane.OK_OPTION) {
+                OffsetBookmark b =
+                    new OffsetBookmark(Main.proj,info.getName(),p.text.getText(),getDx(),getDy());
+                OffsetBookmark.allBookmarks.add(b);
+                OffsetBookmark.saveBookmarks();
+            }
+        }
+    }
+
+    class OffsetAction extends AbstractAction implements LayerAction {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+        }
+        @Override
+        public Component createMenuComponent() {
+            JMenu menu = new JMenu(trc("layer", "Offset"));
+            menu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
+            boolean haveCurrent = false;
+            for (OffsetBookmark b : OffsetBookmark.allBookmarks) {
+                if (!b.isUsable(ImageryLayer.this)) continue;
+                JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
+                if (b.dx == dx && b.dy == dy) {
+                    item.setSelected(true);
+                    haveCurrent = true;
+                }
+                menu.add(item);
+            }
+            if (!haveCurrent) {
+                menu.insert(new NewBookmarkAction(), 0);
+            }
+            return menu;
+        }
+        @Override
+        public boolean supportLayers(List<Layer> layers) {
+            return false;
+        }
+    }
+
+    public Action getOffsetAction() {
+        return new OffsetAction();
+    }
+
 }
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPlugin.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPlugin.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPlugin.java	(revision 24584)
@@ -40,5 +40,4 @@
 
     public ImageryLayerInfo info = new ImageryLayerInfo();
-
     // remember state of menu item to restore on changed preferences
     private boolean menuEnabled = false;
@@ -205,4 +204,5 @@
         instance = this;
         this.info.load();
+        OffsetBookmark.loadBookmarks();
         refreshMenu();
         initRemoteControl();
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferenceEditor.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferenceEditor.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferenceEditor.java	(revision 24584)
@@ -2,20 +2,12 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trc;
 
 import java.awt.Color;
 import java.awt.Component;
-import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.Font;
-import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 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.URL;
-import java.util.Locale;
 
 import javax.swing.BorderFactory;
@@ -25,5 +17,4 @@
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
-import javax.swing.JEditorPane;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
@@ -34,15 +25,10 @@
 import javax.swing.JSpinner;
 import javax.swing.JTabbedPane;
-import javax.swing.JTable;
 import javax.swing.SpinnerNumberModel;
-import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableColumnModel;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
-import org.openstreetmap.josm.plugins.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.plugins.imagery.tms.TMSPreferences;
-import org.openstreetmap.josm.plugins.imagery.wms.AddWMSLayerPanel;
 import org.openstreetmap.josm.plugins.imagery.wms.WMSAdapter;
 import org.openstreetmap.josm.tools.ColorHelper;
@@ -50,6 +36,5 @@
 
 public class ImageryPreferenceEditor implements PreferenceSetting {
-    private ImageryLayerTableModel model;
-    private JComboBox browser;
+    ImageryProvidersPanel imageryProviders;
 
     // Common settings
@@ -61,4 +46,5 @@
 
     // WMS Settings
+    private JComboBox browser;
     JCheckBox overlapCheckBox;
     JSpinner spinEast;
@@ -73,124 +59,4 @@
     private JSpinner minZoomLvl;
     private JSpinner maxZoomLvl;
-
-
-    private JPanel buildImageryProvidersPanel(final PreferenceTabbedPane gui) {
-        final JPanel p = new JPanel(new GridBagLayout());
-        model = new ImageryLayerTableModel();
-        final JTable list = new JTable(model) {
-            @Override
-            public String getToolTipText(MouseEvent e) {
-                java.awt.Point p = e.getPoint();
-                return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
-            }
-        };
-        JScrollPane scroll = new JScrollPane(list);
-        p.add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
-        scroll.setPreferredSize(new Dimension(200, 200));
-
-        final ImageryDefaultLayerTableModel modeldef = new ImageryDefaultLayerTableModel();
-        final JTable listdef = new JTable(modeldef) {
-            @Override
-            public String getToolTipText(MouseEvent e) {
-                java.awt.Point p = e.getPoint();
-                return (String) modeldef.getValueAt(rowAtPoint(p), columnAtPoint(p));
-            }
-        };
-        JScrollPane scrolldef = new JScrollPane(listdef);
-        // scrolldef is added after the buttons so it's clearer the buttons
-        // control the top list and not the default one
-        scrolldef.setPreferredSize(new Dimension(200, 200));
-
-        TableColumnModel mod = listdef.getColumnModel();
-        mod.getColumn(1).setPreferredWidth(800);
-        mod.getColumn(0).setPreferredWidth(200);
-        mod = list.getColumnModel();
-        mod.getColumn(2).setPreferredWidth(50);
-        mod.getColumn(1).setPreferredWidth(800);
-        mod.getColumn(0).setPreferredWidth(200);
-
-        JPanel buttonPanel = new JPanel(new FlowLayout());
-
-        JButton add = new JButton(tr("Add"));
-        buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
-        add.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                AddWMSLayerPanel p = new AddWMSLayerPanel();
-                int answer = JOptionPane.showConfirmDialog(
-                        gui, p,
-                        tr("Add Imagery URL"),
-                        JOptionPane.OK_CANCEL_OPTION);
-                if (answer == JOptionPane.OK_OPTION) {
-                    model.addRow(new ImageryInfo(p.getUrlName(), p.getUrl()));
-                }
-            }
-        });
-
-        JButton delete = new JButton(tr("Delete"));
-        buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0));
-        delete.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                if (list.getSelectedRow() == -1)
-                    JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
-                else {
-                    Integer i;
-                    while ((i = list.getSelectedRow()) != -1)
-                        model.removeRow(i);
-                }
-            }
-        });
-
-        JButton copy = new JButton(tr("Copy Selected Default(s)"));
-        buttonPanel.add(copy, GBC.std().insets(0, 5, 0, 0));
-        copy.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                int[] lines = listdef.getSelectedRows();
-                if (lines.length == 0) {
-                    JOptionPane.showMessageDialog(
-                            gui,
-                            tr("Please select at least one row to copy."),
-                            tr("Information"),
-                            JOptionPane.INFORMATION_MESSAGE);
-                    return;
-                }
-
-                outer: for (int i = 0; i < lines.length; i++) {
-                    ImageryInfo info = modeldef.getRow(lines[i]);
-
-                    // Check if an entry with exactly the same values already
-                    // exists
-                    for (int j = 0; j < model.getRowCount(); j++) {
-                        if (info.equalsBaseValues(model.getRow(j))) {
-                            // Select the already existing row so the user has
-                            // some feedback in case an entry exists
-                            list.getSelectionModel().setSelectionInterval(j, j);
-                            list.scrollRectToVisible(list.getCellRect(j, 0, true));
-                            continue outer;
-                        }
-                    }
-
-                    if (info.eulaAcceptanceRequired != null) {
-                        if (!confirmeEulaAcceptance(gui, info.eulaAcceptanceRequired))
-                            continue outer;
-                    }
-
-                    model.addRow(new ImageryInfo(info));
-                    int lastLine = model.getRowCount() - 1;
-                    list.getSelectionModel().setSelectionInterval(lastLine, lastLine);
-                    list.scrollRectToVisible(list.getCellRect(lastLine, 0, true));
-                }
-            }
-        });
-
-        p.add(buttonPanel);
-        p.add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
-        // Add default item list
-        p.add(scrolldef, GBC.eol().insets(0, 5, 0, 0).fill(GridBagConstraints.BOTH));
-
-        return p;
-    }
 
     private JPanel buildCommonSettingsPanel(final PreferenceTabbedPane gui) {
@@ -331,8 +197,11 @@
         JPanel p = gui.createPreferenceTab("imagery", tr("Imagery Preferences"), tr("Modify list of imagery layers displayed in the Imagery menu"));
         JTabbedPane pane = new JTabbedPane();
-        pane.add(buildImageryProvidersPanel(gui));
+        imageryProviders = new ImageryProvidersPanel(gui, plugin.info);
+        pane.add(imageryProviders);
         pane.add(buildSettingsPanel(gui));
+        pane.add(new OffsetBookmarksPanel(gui));
         pane.setTitleAt(0, tr("Imagery providers"));
         pane.setTitleAt(1, tr("Settings"));
+        pane.setTitleAt(2, tr("Offset bookmarks"));
         p.add(pane,GBC.std().fill(GBC.BOTH));
     }
@@ -342,4 +211,5 @@
         plugin.info.save();
         plugin.refreshMenu();
+        OffsetBookmark.saveBookmarks();
 
         wmsAdapter.PROP_OVERLAP.put(overlapCheckBox.getModel().isSelected());
@@ -373,11 +243,11 @@
      */
     public void setServerUrl(String server, String url) {
-        for (int i = 0; i < model.getRowCount(); i++) {
-            if (server.equals(model.getValueAt(i, 0).toString())) {
-                model.setValueAt(url, i, 1);
+        for (int i = 0; i < imageryProviders.model.getRowCount(); i++) {
+            if (server.equals(imageryProviders.model.getValueAt(i, 0).toString())) {
+                imageryProviders.model.setValueAt(url, i, 1);
                 return;
             }
         }
-        model.addRow(new String[] { server, url });
+        imageryProviders.model.addRow(new String[] { server, url });
     }
 
@@ -390,157 +260,10 @@
      */
     public String getServerUrl(String server) {
-        for (int i = 0; i < model.getRowCount(); i++) {
-            if (server.equals(model.getValueAt(i, 0).toString())) {
-                return model.getValueAt(i, 1).toString();
+        for (int i = 0; i < imageryProviders.model.getRowCount(); i++) {
+            if (server.equals(imageryProviders.model.getValueAt(i, 0).toString())) {
+                return imageryProviders.model.getValueAt(i, 1).toString();
             }
         }
         return null;
     }
-
-    /**
-     * The table model for the WMS layer
-     *
-     */
-    class ImageryLayerTableModel extends DefaultTableModel {
-        public ImageryLayerTableModel() {
-            setColumnIdentifiers(new String[] { tr("Menu Name"), tr("Imagery URL"), trc("layer", "Zoom") });
-        }
-
-        public ImageryInfo getRow(int row) {
-            return plugin.info.layers.get(row);
-        }
-
-        public void addRow(ImageryInfo i) {
-            plugin.info.add(i);
-            int p = getRowCount() - 1;
-            fireTableRowsInserted(p, p);
-        }
-
-        @Override
-        public void removeRow(int i) {
-            plugin.info.remove(getRow(i));
-            fireTableRowsDeleted(i, i);
-        }
-
-        @Override
-        public int getRowCount() {
-            return plugin.info.layers.size();
-        }
-
-        @Override
-        public Object getValueAt(int row, int column) {
-            ImageryInfo info = plugin.info.layers.get(row);
-            switch (column) {
-            case 0:
-                return info.name;
-            case 1:
-                return info.getFullURL();
-            case 2:
-                return (info.imageryType == ImageryType.WMS) ? (info.pixelPerDegree == 0.0 ? "" : info.pixelPerDegree)
-                                                             : (info.maxZoom == 0 ? "" : info.maxZoom);
-            default:
-                throw new ArrayIndexOutOfBoundsException();
-            }
-        }
-
-        @Override
-        public void setValueAt(Object o, int row, int column) {
-            ImageryInfo info = plugin.info.layers.get(row);
-            switch (column) {
-            case 0:
-                info.name = (String) o;
-                break;
-            case 1:
-                info.setURL((String)o);
-                break;
-            case 2:
-                info.pixelPerDegree = 0;
-                info.maxZoom = 0;
-                try {
-                    if(info.imageryType == ImageryType.WMS)
-                        info.pixelPerDegree = Double.parseDouble((String) o);
-                    else
-                        info.maxZoom = Integer.parseInt((String) o);
-                } catch (NumberFormatException e) {
-                }
-                break;
-            default:
-                throw new ArrayIndexOutOfBoundsException();
-            }
-        }
-
-        @Override
-        public boolean isCellEditable(int row, int column) {
-            return true;
-        }
-    }
-
-    /**
-     * The table model for the WMS layer
-     *
-     */
-    class ImageryDefaultLayerTableModel extends DefaultTableModel {
-        public ImageryDefaultLayerTableModel() {
-            setColumnIdentifiers(new String[] { tr("Menu Name (Default)"), tr("Imagery URL (Default)") });
-        }
-
-        public ImageryInfo getRow(int row) {
-            return plugin.info.defaultLayers.get(row);
-        }
-
-        @Override
-        public int getRowCount() {
-            return plugin.info.defaultLayers.size();
-        }
-
-        @Override
-        public Object getValueAt(int row, int column) {
-            ImageryInfo info = plugin.info.defaultLayers.get(row);
-            switch (column) {
-            case 0:
-                return info.name;
-            case 1:
-                return info.getFullURL();
-            }
-            return null;
-        }
-
-        @Override
-        public boolean isCellEditable(int row, int column) {
-            return false;
-        }
-    }
-
-    private boolean confirmeEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) {
-        URL url = null;
-        try {
-            url = new URL(eulaUrl.replaceAll("\\{lang\\}", Locale.getDefault().toString()));
-            JEditorPane htmlPane = null;
-            try {
-                htmlPane = new JEditorPane(url);
-            } catch (IOException e1) {
-                // give a second chance with a default Locale 'en'
-                try {
-                    url = new URL(eulaUrl.replaceAll("\\{lang\\}", "en"));
-                    htmlPane = new JEditorPane(url);
-                } catch (IOException e2) {
-                    JOptionPane.showMessageDialog(gui ,tr("EULA license URL not available: {0}", eulaUrl));
-                    return false;
-                }
-            }
-            Box box = Box.createVerticalBox();
-            htmlPane.setEditable(false);
-            JScrollPane scrollPane = new JScrollPane(htmlPane);
-            scrollPane.setPreferredSize(new Dimension(400, 400));
-            box.add(scrollPane);
-            int option = JOptionPane.showConfirmDialog(Main.parent, box, tr("Please abort if you are not sure"), JOptionPane.YES_NO_OPTION,
-                    JOptionPane.WARNING_MESSAGE);
-            if (option == JOptionPane.YES_OPTION) {
-                return true;
-            }
-        } catch (MalformedURLException e2) {
-            JOptionPane.showMessageDialog(gui ,tr("Malformed URL for the EULA licence: {0}", eulaUrl));
-        }
-        return false;
-    }
 }
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferences.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferences.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryPreferences.java	(revision 24584)
@@ -24,5 +24,3 @@
         Main.pref.putColor("imagery.fade", color);
     }
-
-
 }
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryProvidersPanel.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryProvidersPanel.java	(revision 24584)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/ImageryProvidersPanel.java	(revision 24584)
@@ -0,0 +1,301 @@
+package org.openstreetmap.josm.plugins.imagery;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Locale;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JEditorPane;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumnModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.plugins.imagery.ImageryInfo.ImageryType;
+import org.openstreetmap.josm.plugins.imagery.wms.AddWMSLayerPanel;
+import org.openstreetmap.josm.tools.GBC;
+
+public class ImageryProvidersPanel extends JPanel {
+    final ImageryLayerTableModel model;
+    private final ImageryLayerInfo layerInfo;
+
+    public ImageryProvidersPanel(final PreferenceTabbedPane gui, ImageryLayerInfo layerInfo) {
+        super(new GridBagLayout());
+        this.layerInfo = layerInfo;
+        this.model = new ImageryLayerTableModel();
+
+        final JTable list = new JTable(model) {
+            @Override
+            public String getToolTipText(MouseEvent e) {
+                java.awt.Point p = e.getPoint();
+                return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
+            }
+        };
+        JScrollPane scroll = new JScrollPane(list);
+        add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
+        scroll.setPreferredSize(new Dimension(200, 200));
+
+        final ImageryDefaultLayerTableModel modeldef = new ImageryDefaultLayerTableModel();
+        final JTable listdef = new JTable(modeldef) {
+            @Override
+            public String getToolTipText(MouseEvent e) {
+                java.awt.Point p = e.getPoint();
+                return (String) modeldef.getValueAt(rowAtPoint(p), columnAtPoint(p));
+            }
+        };
+        JScrollPane scrolldef = new JScrollPane(listdef);
+        // scrolldef is added after the buttons so it's clearer the buttons
+        // control the top list and not the default one
+        scrolldef.setPreferredSize(new Dimension(200, 200));
+
+        TableColumnModel mod = listdef.getColumnModel();
+        mod.getColumn(1).setPreferredWidth(800);
+        mod.getColumn(0).setPreferredWidth(200);
+        mod = list.getColumnModel();
+        mod.getColumn(2).setPreferredWidth(50);
+        mod.getColumn(1).setPreferredWidth(800);
+        mod.getColumn(0).setPreferredWidth(200);
+
+        JPanel buttonPanel = new JPanel(new FlowLayout());
+
+        JButton add = new JButton(tr("Add"));
+        buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
+        add.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AddWMSLayerPanel p = new AddWMSLayerPanel();
+                int answer = JOptionPane.showConfirmDialog(
+                        gui, p,
+                        tr("Add Imagery URL"),
+                        JOptionPane.OK_CANCEL_OPTION);
+                if (answer == JOptionPane.OK_OPTION) {
+                    model.addRow(new ImageryInfo(p.getUrlName(), p.getUrl()));
+                }
+            }
+        });
+
+        JButton delete = new JButton(tr("Delete"));
+        buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0));
+        delete.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                if (list.getSelectedRow() == -1)
+                    JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
+                else {
+                    Integer i;
+                    while ((i = list.getSelectedRow()) != -1)
+                        model.removeRow(i);
+                }
+            }
+        });
+
+        JButton copy = new JButton(tr("Copy Selected Default(s)"));
+        buttonPanel.add(copy, GBC.std().insets(0, 5, 0, 0));
+        copy.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                int[] lines = listdef.getSelectedRows();
+                if (lines.length == 0) {
+                    JOptionPane.showMessageDialog(
+                            gui,
+                            tr("Please select at least one row to copy."),
+                            tr("Information"),
+                            JOptionPane.INFORMATION_MESSAGE);
+                    return;
+                }
+
+                outer: for (int i = 0; i < lines.length; i++) {
+                    ImageryInfo info = modeldef.getRow(lines[i]);
+
+                    // Check if an entry with exactly the same values already
+                    // exists
+                    for (int j = 0; j < model.getRowCount(); j++) {
+                        if (info.equalsBaseValues(model.getRow(j))) {
+                            // Select the already existing row so the user has
+                            // some feedback in case an entry exists
+                            list.getSelectionModel().setSelectionInterval(j, j);
+                            list.scrollRectToVisible(list.getCellRect(j, 0, true));
+                            continue outer;
+                        }
+                    }
+
+                    if (info.eulaAcceptanceRequired != null) {
+                        if (!confirmEulaAcceptance(gui, info.eulaAcceptanceRequired))
+                            continue outer;
+                    }
+
+                    model.addRow(new ImageryInfo(info));
+                    int lastLine = model.getRowCount() - 1;
+                    list.getSelectionModel().setSelectionInterval(lastLine, lastLine);
+                    list.scrollRectToVisible(list.getCellRect(lastLine, 0, true));
+                }
+            }
+        });
+
+        add(buttonPanel);
+        add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
+        // Add default item list
+        add(scrolldef, GBC.eol().insets(0, 5, 0, 0).fill(GridBagConstraints.BOTH));
+    }
+
+    /**
+     * The table model for imagery layer list
+     */
+    class ImageryLayerTableModel extends DefaultTableModel {
+        public ImageryLayerTableModel() {
+            setColumnIdentifiers(new String[] { tr("Menu Name"), tr("Imagery URL"), trc("layer", "Zoom") });
+        }
+
+        public ImageryInfo getRow(int row) {
+            return layerInfo.layers.get(row);
+        }
+
+        public void addRow(ImageryInfo i) {
+            layerInfo.add(i);
+            int p = getRowCount() - 1;
+            fireTableRowsInserted(p, p);
+        }
+
+        @Override
+        public void removeRow(int i) {
+            layerInfo.remove(getRow(i));
+            fireTableRowsDeleted(i, i);
+        }
+
+        @Override
+        public int getRowCount() {
+            return layerInfo.layers.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            ImageryInfo info = layerInfo.layers.get(row);
+            switch (column) {
+            case 0:
+                return info.name;
+            case 1:
+                return info.getFullURL();
+            case 2:
+                return (info.imageryType == ImageryType.WMS) ? (info.pixelPerDegree == 0.0 ? "" : info.pixelPerDegree)
+                                                             : (info.maxZoom == 0 ? "" : info.maxZoom);
+            default:
+                throw new ArrayIndexOutOfBoundsException();
+            }
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            ImageryInfo info = layerInfo.layers.get(row);
+            switch (column) {
+            case 0:
+                info.name = (String) o;
+                break;
+            case 1:
+                info.setURL((String)o);
+                break;
+            case 2:
+                info.pixelPerDegree = 0;
+                info.maxZoom = 0;
+                try {
+                    if(info.imageryType == ImageryType.WMS)
+                        info.pixelPerDegree = Double.parseDouble((String) o);
+                    else
+                        info.maxZoom = Integer.parseInt((String) o);
+                } catch (NumberFormatException e) {
+                }
+                break;
+            default:
+                throw new ArrayIndexOutOfBoundsException();
+            }
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return true;
+        }
+    }
+
+    /**
+     * The table model for the default imagery layer list
+     */
+    class ImageryDefaultLayerTableModel extends DefaultTableModel {
+        public ImageryDefaultLayerTableModel() {
+            setColumnIdentifiers(new String[] { tr("Menu Name (Default)"), tr("Imagery URL (Default)") });
+        }
+
+        public ImageryInfo getRow(int row) {
+            return layerInfo.defaultLayers.get(row);
+        }
+
+        @Override
+        public int getRowCount() {
+            return layerInfo.defaultLayers.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            ImageryInfo info = layerInfo.defaultLayers.get(row);
+            switch (column) {
+            case 0:
+                return info.name;
+            case 1:
+                return info.getFullURL();
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+    }
+
+    private boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) {
+        URL url = null;
+        try {
+            url = new URL(eulaUrl.replaceAll("\\{lang\\}", Locale.getDefault().toString()));
+            JEditorPane htmlPane = null;
+            try {
+                htmlPane = new JEditorPane(url);
+            } catch (IOException e1) {
+                // give a second chance with a default Locale 'en'
+                try {
+                    url = new URL(eulaUrl.replaceAll("\\{lang\\}", "en"));
+                    htmlPane = new JEditorPane(url);
+                } catch (IOException e2) {
+                    JOptionPane.showMessageDialog(gui ,tr("EULA license URL not available: {0}", eulaUrl));
+                    return false;
+                }
+            }
+            Box box = Box.createVerticalBox();
+            htmlPane.setEditable(false);
+            JScrollPane scrollPane = new JScrollPane(htmlPane);
+            scrollPane.setPreferredSize(new Dimension(400, 400));
+            box.add(scrollPane);
+            int option = JOptionPane.showConfirmDialog(Main.parent, box, tr("Please abort if you are not sure"), JOptionPane.YES_NO_OPTION,
+                    JOptionPane.WARNING_MESSAGE);
+            if (option == JOptionPane.YES_OPTION) {
+                return true;
+            }
+        } catch (MalformedURLException e2) {
+            JOptionPane.showMessageDialog(gui ,tr("Malformed URL for the EULA licence: {0}", eulaUrl));
+        }
+        return false;
+    }
+}
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmark.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmark.java	(revision 24584)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmark.java	(revision 24584)
@@ -0,0 +1,75 @@
+package org.openstreetmap.josm.plugins.imagery;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.projection.Projection;
+
+public class OffsetBookmark {
+    public static List<OffsetBookmark> allBookmarks = new ArrayList<OffsetBookmark>();
+
+    public Projection proj;
+    public String layerName;
+    public String name;
+    public double dx, dy;
+
+    public boolean isUsable(ImageryLayer layer) {
+        return Main.proj.getClass() == proj.getClass() &&
+                layer.getInfo().getName().equals(layerName);
+    }
+    public OffsetBookmark(Projection proj, String layerName, String name, double dx, double dy) {
+        this.proj = proj;
+        this.layerName = layerName;
+        this.name = name;
+        this.dx = dx;
+        this.dy = dy;
+    }
+
+    public OffsetBookmark(Collection<String> list) {
+        ArrayList<String> array = new ArrayList<String>(list);
+        String projectionName = array.get(0);
+        for (Projection proj : Projection.allProjections) {
+            if (proj.getCacheDirectoryName().equals(projectionName)) {
+                this.proj = proj;
+                break;
+            }
+        }
+        if (this.proj == null)
+            throw new IllegalStateException(tr("Projection ''{0}'' not found", projectionName));
+        this.layerName = array.get(1);
+        this.name = array.get(2);
+        this.dx = Double.valueOf(array.get(3));
+        this.dy = Double.valueOf(array.get(4));
+    }
+
+    public ArrayList<String> getInfoArray() {
+        ArrayList<String> res = new ArrayList<String>(5);
+        res.add(proj.getCacheDirectoryName()); // we should use non-localized projection name
+        res.add(layerName);
+        res.add(name);
+        res.add(String.valueOf(dx));
+        res.add(String.valueOf(dy));
+        return res;
+    }
+
+    public static void loadBookmarks() {
+        for(Collection<String> c : Main.pref.getArray("imagery.offsets",
+                Collections.<Collection<String>>emptySet())) {
+                    allBookmarks.add(new OffsetBookmark(c));
+                }
+    }
+
+    public static void saveBookmarks() {
+        LinkedList<Collection<String>> coll = new LinkedList<Collection<String>>();
+        for (OffsetBookmark b : allBookmarks) {
+            coll.add(b.getInfoArray());
+        }
+        Main.pref.putArray("imagery.offsets", coll);
+    }
+}
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmarksPanel.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmarksPanel.java	(revision 24584)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/OffsetBookmarksPanel.java	(revision 24584)
@@ -0,0 +1,155 @@
+package org.openstreetmap.josm.plugins.imagery;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumnModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.tools.GBC;
+
+
+public class OffsetBookmarksPanel extends JPanel {
+    List<OffsetBookmark> bookmarks = OffsetBookmark.allBookmarks;
+    OffsetsBookmarksModel model = new OffsetsBookmarksModel();
+
+    public OffsetBookmarksPanel(final PreferenceTabbedPane gui) {
+        super(new GridBagLayout());
+        final JTable list = new JTable(model) {
+            @Override
+            public String getToolTipText(MouseEvent e) {
+                java.awt.Point p = e.getPoint();
+                return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
+            }
+        };
+        JScrollPane scroll = new JScrollPane(list);
+        add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
+        scroll.setPreferredSize(new Dimension(200, 200));
+
+        TableColumnModel mod = list.getColumnModel();
+        mod.getColumn(0).setPreferredWidth(150);
+        mod.getColumn(1).setPreferredWidth(200);
+        mod.getColumn(2).setPreferredWidth(300);
+        mod.getColumn(3).setPreferredWidth(150);
+        mod.getColumn(4).setPreferredWidth(150);
+
+        JPanel buttonPanel = new JPanel(new FlowLayout());
+
+        JButton add = new JButton(tr("Add"));
+        buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
+        add.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                OffsetBookmark b = new OffsetBookmark(Main.proj,"","",0,0);
+                model.addRow(b);
+            }
+        });
+
+        JButton delete = new JButton(tr("Delete"));
+        buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0));
+        delete.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                if (list.getSelectedRow() == -1)
+                    JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
+                else {
+                    Integer i;
+                    while ((i = list.getSelectedRow()) != -1)
+                        model.removeRow(i);
+                }
+            }
+        });
+
+        add(buttonPanel,GBC.eol());
+    }
+
+    /**
+     * The table model for imagery offsets list
+     */
+    class OffsetsBookmarksModel extends DefaultTableModel {
+        public OffsetsBookmarksModel() {
+            setColumnIdentifiers(new String[] { tr("Projection"),  tr("Layer"), tr("Name"), tr("Easting"), tr("Northing"),});
+        }
+
+        public OffsetBookmark getRow(int row) {
+            return bookmarks.get(row);
+        }
+
+        public void addRow(OffsetBookmark i) {
+            bookmarks.add(i);
+            int p = getRowCount() - 1;
+            fireTableRowsInserted(p, p);
+        }
+
+        @Override
+        public void removeRow(int i) {
+            bookmarks.remove(getRow(i));
+            fireTableRowsDeleted(i, i);
+        }
+
+        @Override
+        public int getRowCount() {
+            return bookmarks.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            OffsetBookmark info = bookmarks.get(row);
+            switch (column) {
+            case 0:
+                return info.proj.toString();
+            case 1:
+                return info.layerName;
+            case 2:
+                return info.name;
+            case 3:
+                return info.dx;
+            case 4:
+                return info.dy;
+            default:
+                throw new ArrayIndexOutOfBoundsException();
+            }
+        }
+
+        @Override
+        public void setValueAt(Object o, int row, int column) {
+            OffsetBookmark info = bookmarks.get(row);
+            switch (column) {
+            case 1:
+                info.layerName = o.toString();
+                break;
+            case 2:
+                info.name = o.toString();
+                break;
+            case 3:
+                info.dx = Double.parseDouble((String) o);;
+                break;
+            case 4:
+                info.dy = Double.parseDouble((String) o);;
+                break;
+            default:
+                throw new ArrayIndexOutOfBoundsException();
+            }
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return column >= 1;
+        }
+    }
+}
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/tms/TMSLayer.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/tms/TMSLayer.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/tms/TMSLayer.java	(revision 24584)
@@ -161,6 +161,6 @@
 
     @Override
-    public void displace(double dx, double dy) {
-        super.displace(dx, dy);
+    public void setOffset(double dx, double dy) {
+        super.setOffset(dx, dy);
         needRedraw = true;
     }
@@ -1138,4 +1138,5 @@
                 SeparatorLayerAction.INSTANCE,
                 // color,
+                getOffsetAction(),
                 new RenameLayerAction(this.getAssociatedFile(), this),
                 SeparatorLayerAction.INSTANCE,
Index: applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/wms/WMSLayer.java
===================================================================
--- applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/wms/WMSLayer.java	(revision 24583)
+++ applications/editors/josm/plugins/imagery/src/org/openstreetmap/josm/plugins/imagery/wms/WMSLayer.java	(revision 24584)
@@ -30,7 +30,7 @@
 import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
-import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
@@ -42,7 +42,7 @@
 import org.openstreetmap.josm.io.CacheFiles;
 import org.openstreetmap.josm.plugins.imagery.ImageryInfo;
-import org.openstreetmap.josm.plugins.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.plugins.imagery.ImageryLayer;
 import org.openstreetmap.josm.plugins.imagery.ImageryPlugin;
+import org.openstreetmap.josm.plugins.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.plugins.imagery.wms.GeorefImage.State;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -69,4 +69,5 @@
     protected boolean autoDownloadEnabled = true;
     protected boolean settingsChanged;
+    protected ImageryInfo info;
 
     // Image index boundary for current view
@@ -242,6 +243,6 @@
 
     @Override
-    public void displace(double dx, double dy) {
-        super.displace(dx, dy);
+    public void setOffset(double dx, double dy) {
+        super.setOffset(dx, dy);
         settingsChanged = true;
     }
@@ -357,4 +358,5 @@
                 LayerListDialog.getInstance().createDeleteLayerAction(),
                 SeparatorLayerAction.INSTANCE,
+                getOffsetAction(),
                 new LoadWmsAction(),
                 new SaveWmsAction(),
