Index: trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(revision 4195)
@@ -7,6 +7,13 @@
 import java.util.regex.Pattern;
 
+import javax.swing.ImageIcon;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
+import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
 
 /**
@@ -33,17 +40,21 @@
     }
 
-    String name;
+    private String name;
     private String url = null;
-    String cookies = null;
-    public final String eulaAcceptanceRequired;
-    ImageryType imageryType = ImageryType.WMS;
-    double pixelPerDegree = 0.0;
-    int maxZoom = 0;
-    int defaultMaxZoom = 0;
-    int defaultMinZoom = 0;
+    private String cookies = null;
+    private String eulaAcceptanceRequired= null;
+    private ImageryType imageryType = ImageryType.WMS;
+    private double pixelPerDegree = 0.0;
+    private int maxZoom = 0;
+    private int defaultMaxZoom = 0;
+    private int defaultMinZoom = 0;
+    private Bounds bounds = null;
+    private String attributionText;
+    private String attributionImage;
+    private String attributionLinkURL;
+    private String termsOfUseURL;
 
     public ImageryInfo(String name) {
         this.name=name;
-        this.eulaAcceptanceRequired = null;
     }
 
@@ -51,5 +62,4 @@
         this.name=name;
         setUrl(url);
-        this.eulaAcceptanceRequired = null;
     }
 
@@ -72,44 +82,21 @@
         this.cookies=cookies;
         this.pixelPerDegree=pixelPerDegree;
-        this.eulaAcceptanceRequired = null;
     }
 
     public ArrayList<String> getInfoArray() {
-        String e2 = null;
-        String e3 = null;
-        String e4 = null;
-        if(url != null && !url.isEmpty()) {
-            e2 = getFullUrl();
-        }
-        if(cookies != null && !cookies.isEmpty()) {
-            e3 = cookies;
-        }
-        if(imageryType == ImageryType.WMS || imageryType == ImageryType.HTML) {
-            if(pixelPerDegree != 0.0) {
-                e4 = String.valueOf(pixelPerDegree);
-            }
-        } else {
-            if(maxZoom != 0) {
-                e4 = String.valueOf(maxZoom);
-            }
-        }
-        if(e4 != null && e3 == null) {
-            e3 = "";
-        }
-        if(e3 != null && e2 == null) {
-            e2 = "";
-        }
-
         ArrayList<String> res = new ArrayList<String>();
         res.add(name);
-        if(e2 != null) {
-            res.add(e2);
-        }
-        if(e3 != null) {
-            res.add(e3);
-        }
-        if(e4 != null) {
-            res.add(e4);
-        }
+        res.add((url != null && !url.isEmpty()) ? getFullUrl() : null);
+        res.add(cookies);
+        if(imageryType == ImageryType.WMS || imageryType == ImageryType.HTML) {
+            res.add(pixelPerDegree != 0.0 ? String.valueOf(pixelPerDegree) : null);
+        } else {
+            res.add(maxZoom != 0 ? String.valueOf(maxZoom) : null);
+        }
+        res.add(bounds != null ? bounds.encodeAsString(",") : null);
+        res.add(attributionText);
+        res.add(attributionLinkURL);
+        res.add(attributionImage);
+        res.add(termsOfUseURL);
         return res;
     }
@@ -118,11 +105,11 @@
         ArrayList<String> array = new ArrayList<String>(list);
         this.name=array.get(0);
-        if(array.size() >= 2) {
+        if(array.size() >= 2 && !array.get(1).isEmpty()) {
             setUrl(array.get(1));
         }
-        if(array.size() >= 3) {
+        if(array.size() >= 3 && !array.get(2).isEmpty()) {
             this.cookies=array.get(2);
         }
-        if(array.size() >= 4) {
+        if(array.size() >= 4 && !array.get(3).isEmpty()) {
             if (imageryType == ImageryType.WMS || imageryType == ImageryType.HTML) {
                 this.pixelPerDegree=Double.valueOf(array.get(3));
@@ -131,5 +118,23 @@
             }
         }
-        this.eulaAcceptanceRequired = null;
+        if(array.size() >= 5 && !array.get(4).isEmpty()) {
+            try {
+                bounds = new Bounds(array.get(4), ",");
+            } catch (IllegalArgumentException e) {
+                Main.warn(e.toString());
+            }
+        }
+        if(array.size() >= 6 && !array.get(5).isEmpty()) {
+            setAttributionText(array.get(5));
+        }
+        if(array.size() >= 7 && !array.get(6).isEmpty()) {
+            setAttributionLinkURL(array.get(6));
+        }
+        if(array.size() >= 8 && !array.get(7).isEmpty()) {
+            setAttributionImage(array.get(7));
+        }
+        if(array.size() >= 9 && !array.get(8).isEmpty()) {
+            setTermsOfUseURL(array.get(8));
+        }
     }
 
@@ -139,7 +144,14 @@
         this.cookies=i.cookies;
         this.imageryType=i.imageryType;
+        this.defaultMinZoom=i.defaultMinZoom;
+        this.maxZoom=i.maxZoom;
         this.defaultMaxZoom=i.defaultMaxZoom;
         this.pixelPerDegree=i.pixelPerDegree;
         this.eulaAcceptanceRequired = null;
+        this.bounds = i.bounds;
+        this.attributionImage = i.attributionImage;
+        this.attributionLinkURL = i.attributionLinkURL;
+        this.attributionText = i.attributionText;
+        this.termsOfUseURL = i.termsOfUseURL;
     }
 
@@ -168,4 +180,24 @@
     public void setMaxZoom(int maxZoom) {
         this.maxZoom = maxZoom;
+    }
+
+    public void setBounds(Bounds b) {
+        this.bounds = b;
+    }
+
+    public void setAttributionText(String text) {
+         attributionText = text;
+    }
+
+    public void setAttributionImage(String text) {
+        attributionImage = text;
+    }
+
+    public void setAttributionLinkURL(String text) {
+        attributionLinkURL = text;
+    }
+
+    public void setTermsOfUseURL(String text) {
+        termsOfUseURL = text;
     }
 
@@ -224,4 +256,8 @@
     }
 
+    public String getEulaAcceptanceRequired() {
+        return eulaAcceptanceRequired;
+    }
+
     public String getFullUrl() {
         return imageryType.getUrlString() + (defaultMaxZoom != 0
@@ -247,4 +283,31 @@
         }
         return res;
+    }
+
+    public void setAttribution(TMSTileSource s)
+    {
+        if(attributionLinkURL != null) {
+            if(attributionLinkURL.equals("osm"))
+                s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
+            else
+                s.setAttributionLinkURL(attributionLinkURL);
+        }
+        if(attributionText != null) {
+            if(attributionText.equals("osm"))
+                s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
+            else
+                s.setAttributionText(attributionText);
+        }
+        if(attributionImage != null) {
+            ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
+            if(i != null)
+                s.setAttributionImage(i.getImage());
+        }
+        if(termsOfUseURL != null) {
+            if(termsOfUseURL.equals("osm"))
+                s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
+            else
+                s.setTermsOfUseURL(termsOfUseURL);
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 4195)
@@ -17,4 +17,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.io.MirroredInputStream;
 
@@ -73,5 +74,5 @@
                     {
                         String val[] = line.split(";");
-                        if(!line.startsWith("#") && (val.length == 3 || val.length == 4)) {
+                        if(!line.startsWith("#") && val.length >= 3) {
                             boolean force = "true".equals(val[0]);
                             String name = tr(val[1]);
@@ -79,10 +80,33 @@
                             String eulaAcceptanceRequired = null;
 
-                            if (val.length == 4) {
+                            if (val.length >= 4 && !val[3].isEmpty()) {
                                 // 4th parameter optional for license agreement (EULA)
                                 eulaAcceptanceRequired = val[3];
                             }
 
-                            defaultLayers.add(new ImageryInfo(name, url, eulaAcceptanceRequired));
+                            ImageryInfo info = new ImageryInfo(name, url, eulaAcceptanceRequired);
+
+                            if (val.length >= 5 && !val[4].isEmpty()) {
+                                // 5th parameter optional for bounds
+                                try {
+                                    info.setBounds(new Bounds(val[4], ","));
+                                } catch (IllegalArgumentException e) {
+                                    Main.warn(e.toString());
+                                }
+                            }
+                            if (val.length >= 6 && !val[5].isEmpty()) {
+                                info.setAttributionText(val[5]);
+                            }
+                            if (val.length >= 7 && !val[6].isEmpty()) {
+                                info.setAttributionLinkURL(val[6]);
+                            }
+                            if (val.length >= 8 && !val[7].isEmpty()) {
+                                info.setTermsOfUseURL(val[7]);
+                            }
+                            if (val.length >= 9 && !val[8].isEmpty()) {
+                                info.setAttributionImage(val[8]);
+                            }
+
+                            defaultLayers.add(info);
 
                             if (force) {
Index: trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 4195)
@@ -3,4 +3,5 @@
 
 import java.awt.Color;
+import java.awt.Desktop;
 import java.awt.Dimension;
 import java.awt.Graphics;
@@ -12,4 +13,6 @@
 import java.io.File;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -217,4 +220,34 @@
     }
 
+    public boolean handleAttribution(Point p, boolean click) {
+        TileSource ts = tileController.getTileSource();
+        if(!ts.requiresAttribution())
+            return false;
+
+        /* TODO: Somehow indicate the link is clickable state to user */
+
+        try {
+            if((attrImageBounds != null && attrImageBounds.contains(p))
+            || (attrTextBounds != null && attrTextBounds.contains(p))) {
+                if(click)
+                    Desktop.getDesktop().browse(new URI(ts.getAttributionLinkURL()));
+                /*else
+                    Main.warn(ts.getAttributionLinkURL());*/
+                return true;
+            } else if(attrToUBounds.contains(p)) {
+                if(click)
+                    Desktop.getDesktop().browse(new URI(ts.getTermsOfUseURL()));
+                /*else
+                    Main.warn(ts.getTermsOfUseURL());*/
+                return true;
+            }
+        } catch (IOException e1) {
+            e1.printStackTrace();
+        } catch (URISyntaxException e1) {
+            e1.printStackTrace();
+        }
+        return false;
+    }
+
     protected Point getTopLeftCoordinates() {
         return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
Index: trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java	(revision 4195)
@@ -149,8 +149,9 @@
                 iSizeButton.toggle();
                 iSlippyMapChooser.resizeSlippyMap();
+            } else if (iSlippyMapChooser.handleAttribution(e.getPoint(), true)) {
+                /* do nothing, handleAttribution() already did the work */
             } else if (sourceButton == SourceButton.HIDE_OR_SHOW) {
                 iSourceButton.toggle();
                 iSlippyMapChooser.repaint();
-
             } else if (sourceButton != 0) {
                 iSlippyMapChooser.toggleMapSource(iSourceButton.hitToTileSource(sourceButton));
@@ -170,4 +171,5 @@
     @Override
     public void mouseMoved(MouseEvent e) {
+        iSlippyMapChooser.handleAttribution(e.getPoint(), false);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 4195)
@@ -159,5 +159,5 @@
     private Image attrImage;
     private String attrTermsUrl;
-    private Rectangle attrImageBounds, attrToUBounds;
+    private Rectangle attrImageBounds, attrToUBounds, attrTextBounds;
     private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13);
     private static final Font ATTR_FONT = new Font("Arial", Font.PLAIN, 10);
@@ -233,8 +233,13 @@
     public static TileSource getTileSource(ImageryInfo info) {
         if (info.getImageryType() == ImageryType.TMS) {
-            if(ImageryInfo.isUrlWithPatterns(info.getUrl()))
-                return new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getMinZoom(), info.getMaxZoom());
-            else
-                return new TMSTileSource(info.getName(),info.getUrl(), info.getMinZoom(), info.getMaxZoom());
+            if(ImageryInfo.isUrlWithPatterns(info.getUrl())) {
+                TMSTileSource t = new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getMinZoom(), info.getMaxZoom());
+                info.setAttribution(t);
+                return t;
+            } else {
+                TMSTileSource t = new TMSTileSource(info.getName(),info.getUrl(), info.getMinZoom(), info.getMaxZoom());
+                info.setAttribution(t);
+                return t;
+            }
         } else if (info.getImageryType() == ImageryType.BING)
             return new BingAerialTileSource();
@@ -317,5 +322,5 @@
         if(!isProjectionSupported(Main.getProjection())) {
               JOptionPane.showMessageDialog(Main.parent,
-                  tr("TMS layers do not support the projection {1}.\n{2}\n"
+                  tr("TMS layers do not support the projection {0}.\n{1}\n"
                   + "Change the projection or remove the layer.",
                       Main.getProjection().toCode(), nameSupportedProjections()),
@@ -455,5 +460,6 @@
                                 return;
 
-                            if(attrImageBounds != null && attrImageBounds.contains(e.getPoint())) {
+                            if((attrImageBounds != null && attrImageBounds.contains(e.getPoint()))
+                            || (attrTextBounds != null && attrTextBounds.contains(e.getPoint()))) {
                                 try {
                                     java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
@@ -1200,5 +1206,6 @@
             // Draw terms of use text
             Rectangle2D termsStringBounds = g.getFontMetrics().getStringBounds("Background Terms of Use", g);
-            int textHeight = (int) termsStringBounds.getHeight() - 5;
+            int textRealHeight = (int) termsStringBounds.getHeight();
+            int textHeight = textRealHeight - 5;
             int textWidth = (int) termsStringBounds.getWidth();
             int termsTextY = mv.getHeight() - textHeight;
@@ -1206,5 +1213,5 @@
                 int x = 2;
                 int y = mv.getHeight() - textHeight;
-                attrToUBounds = new Rectangle(x, y, textWidth, textHeight);
+                attrToUBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
                 myDrawString(g, "Background Terms of Use", x, y);
             }
@@ -1228,4 +1235,5 @@
                 int y = mv.getHeight() - textHeight;
                 myDrawString(g, attributionText, x, y);
+                attrTextBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
             }
 
Index: trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java	(revision 4194)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java	(revision 4195)
@@ -530,6 +530,6 @@
                     }
 
-                    if (info.eulaAcceptanceRequired != null) {
-                        if (!confirmEulaAcceptance(gui, info.eulaAcceptanceRequired)) {
+                    if (info.getEulaAcceptanceRequired() != null) {
+                        if (!confirmEulaAcceptance(gui, info.getEulaAcceptanceRequired())) {
                             continue outer;
                         }
