Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/AttributionSupport.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/AttributionSupport.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/AttributionSupport.java	(revision 26793)
@@ -2,4 +2,6 @@
 
 //License: GPL.
+
+import static org.openstreetmap.gui.jmapviewer.FeatureAdapter.tr;
 
 import java.awt.Color;
@@ -21,7 +23,10 @@
 
     private Image attrImage;
+    private String attrTermsText;
     private String attrTermsUrl;
     public static final Font ATTR_FONT = new Font("Arial", Font.PLAIN, 10);
     public static final Font ATTR_LINK_FONT;
+    
+    private static final String DEFAULT_TERMS_OF_USE_TEXT = tr("Background Terms of Use");
 
     protected Rectangle attrTextBounds = null;
@@ -40,5 +45,9 @@
         if (requireAttr) {
             attrImage = tileSource.getAttributionImage();
+            attrTermsText = tileSource.getTermsOfUseText();
             attrTermsUrl = tileSource.getTermsOfUseURL();
+            if (attrTermsUrl != null && attrTermsText == null) {
+                attrTermsText = DEFAULT_TERMS_OF_USE_TEXT;
+            }
         } else {
             attrImage = null;
@@ -48,5 +57,5 @@
 
     public void paintAttribution(Graphics g, int width, int height, Coordinate topLeft, Coordinate bottomRight, int zoom, ImageObserver observer) {
-        if (!tileSource.requiresAttribution())
+        if (tileSource == null || !tileSource.requiresAttribution())
             return;
         // Draw attribution
@@ -55,17 +64,20 @@
 
         // Draw terms of use text
-        Rectangle2D termsStringBounds = g.getFontMetrics().getStringBounds("Background Terms of Use", g);
-        int textRealHeight = (int) termsStringBounds.getHeight();
-        int textHeight = textRealHeight - 5;
-        int textWidth = (int) termsStringBounds.getWidth();
-        int termsTextY = height - textHeight;
-        if (attrTermsUrl != null) {
+        int termsTextHeight = 0;
+        int termsTextY = height;
+
+        if (attrTermsText != null) {
+            Rectangle2D termsStringBounds = g.getFontMetrics().getStringBounds(attrTermsText, g);
+            int textRealHeight = (int) termsStringBounds.getHeight();
+            termsTextHeight = textRealHeight - 5;
+            int termsTextWidth = (int) termsStringBounds.getWidth();
+            termsTextY = height - termsTextHeight;
             int x = 2;
-            int y = height - textHeight;
-            attrToUBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
+            int y = height - termsTextHeight;
+            attrToUBounds = new Rectangle(x, y-termsTextHeight, termsTextWidth, textRealHeight);
             g.setColor(Color.black);
-            g.drawString("Background Terms of Use", x + 1, y + 1);
+            g.drawString(attrTermsText, x + 1, y + 1);
             g.setColor(Color.white);
-            g.drawString("Background Terms of Use", x, y);
+            g.drawString(attrTermsText, x, y);
         }
 
@@ -75,5 +87,5 @@
             int imgWidth = attrImage.getWidth(observer);
             int imgHeight = attrImage.getHeight(observer);
-            int y = termsTextY - imgHeight - textHeight - 5;
+            int y = termsTextY - imgHeight - termsTextHeight - 5;
             attrImageBounds = new Rectangle(x, y, imgWidth, imgHeight);
             g.drawImage(attrImage, x, y, null);
@@ -84,4 +96,5 @@
         if (attributionText != null) {
             Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(attributionText, g);
+            int textHeight = (int) stringBounds.getHeight() - 5;
             int x = width - (int) stringBounds.getWidth();
             int y = height - textHeight;
@@ -90,5 +103,5 @@
             g.setColor(Color.white);
             g.drawString(attributionText, x, y);
-            attrTextBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
+            attrTextBounds = new Rectangle(x, y-textHeight, (int) stringBounds.getWidth(), (int) stringBounds.getHeight());
         }
 
@@ -97,20 +110,33 @@
 
     public boolean handleAttribution(Point p, boolean click) {
-        if (!tileSource.requiresAttribution())
+        if (tileSource == null || !tileSource.requiresAttribution())
             return false;
 
         /* TODO: Somehow indicate the link is clickable state to user */
 
-        if ((attrImageBounds != null && attrImageBounds.contains(p))
-                || (attrTextBounds != null && attrTextBounds.contains(p))) {
-            if (click) {
-                FeatureAdapter.openLink(tileSource.getAttributionLinkURL());
+        if (attrTextBounds != null && attrTextBounds.contains(p)) {
+            String attributionURL = tileSource.getAttributionLinkURL();
+            if (attributionURL != null) {
+                if (click) {
+                    FeatureAdapter.openLink(attributionURL);
+                }
+                return true;
             }
-            return true;
+        } else if (attrImageBounds != null && attrImageBounds.contains(p)) {
+            String attributionImageURL = tileSource.getAttributionImageURL();
+            if (attributionImageURL != null) {
+                if (click) {
+                    FeatureAdapter.openLink(tileSource.getAttributionImageURL());
+                }
+                return true;
+            }
         } else if (attrToUBounds != null && attrToUBounds.contains(p)) {
-            if (click) {
-                FeatureAdapter.openLink(tileSource.getTermsOfUseURL());
+            String termsOfUseURL = tileSource.getTermsOfUseURL();
+            if (termsOfUseURL != null) {
+                if (click) {
+                    FeatureAdapter.openLink(termsOfUseURL);
+                }
+                return true;
             }
-            return true;
         }
         return false;
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 26793)
@@ -48,5 +48,5 @@
      *         {@link JMapViewer#MAX_ZOOM}
      */
-    public int getMaxZoom();
+    int getMaxZoom();
 
     /**
@@ -57,5 +57,5 @@
      * @return minimum zoom value - usually 0
      */
-    public int getMinZoom();
+    int getMinZoom();
 
     /**
@@ -63,5 +63,5 @@
      * @see TileUpdate
      */
-    public TileUpdate getTileUpdate();
+    TileUpdate getTileUpdate();
 
     /**
@@ -71,5 +71,5 @@
      * @return Name of the tile layer
      */
-    public String getName();
+    String getName();
 
     /**
@@ -81,5 +81,5 @@
      * @return fully qualified url for downloading the specified tile image
      */
-    public String getTileUrl(int zoom, int tilex, int tiley) throws IOException;
+    String getTileUrl(int zoom, int tilex, int tiley) throws IOException;
 
     /**
@@ -89,5 +89,5 @@
      * @return file extension of the tile image type
      */
-    public String getTileType();
+    String getTileType();
 
     /**
@@ -95,10 +95,10 @@
      * @return The size of a single tile in pixels.
      */
-    public int getTileSize();
+    int getTileSize();
 
     /**
      * @return True if the tile source requires attribution in text or image form.
      */
-    public boolean requiresAttribution();
+    boolean requiresAttribution();
 
     /**
@@ -108,27 +108,41 @@
      * @return Attribution text for the image source.
      */
-    public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight);
+    String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight);
+
+    /**
+     * @return The URL to open when the user clicks the attribution text.
+     */
+    String getAttributionLinkURL();
 
     /**
      * @return The URL for the attribution image. Null if no image should be displayed.
      */
-    public Image getAttributionImage();
+    Image getAttributionImage();
 
     /**
      * @return The URL to open when the user clicks the attribution image.
+     * When return value is null, the image is still displayed (provided getAttributionImage()
+     * returns a value other than null), but the image does not link to a website.
      */
-    public String getAttributionLinkURL();
+    String getAttributionImageURL();
+
+    /**
+     * @return The attribution "Terms of Use" text.
+     * In case it returns null, but getTermsOfUseURL() is not null, a default
+     * terms of use text is used.
+     */
+    String getTermsOfUseText();
 
     /**
      * @return The URL to open when the user clicks the attribution "Terms of Use" text.
      */
-    public String getTermsOfUseURL();
+    String getTermsOfUseURL();
 
-    public double latToTileY(double lat, int zoom);
+    double latToTileY(double lat, int zoom);
 
-    public double lonToTileX(double lon, int zoom);
+    double lonToTileX(double lon, int zoom);
 
-    public double tileYToLat(int y, int zoom);
+    double tileYToLat(int y, int zoom);
 
-    public double tileXToLon(int x, int zoom);
+    double tileXToLon(int x, int zoom);
 }
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java	(revision 26793)
@@ -40,4 +40,19 @@
 
     @Override
+    public Image getAttributionImage() {
+        return null;
+    }
+
+    @Override
+    public String getAttributionImageURL() {
+        return null;
+    }
+
+    @Override
+    public String getTermsOfUseText() {
+        return null;
+    }
+
+    @Override
     public String getTermsOfUseURL() {
         return "http://www.openstreetmap.org/copyright";
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTSMTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTSMTileSource.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTSMTileSource.java	(revision 26793)
@@ -31,12 +31,15 @@
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    @Override
     public int getMaxZoom() {
         return 21;
     }
 
+    @Override
     public int getMinZoom() {
         return 0;
@@ -58,4 +61,5 @@
     }
 
+    @Override
     public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
         return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
@@ -67,35 +71,54 @@
     }
 
+    @Override
     public String getTileType() {
         return "png";
     }
 
+    @Override
     public int getTileSize() {
         return 256;
     }
 
+    @Override
+    public boolean requiresAttribution() {
+        return false;
+    }
+
+    @Override
+    public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
+        throw new UnsupportedOperationException("no attribution");
+    }
+
+    @Override
+    public String getAttributionLinkURL() {
+        throw new UnsupportedOperationException("no attribution");
+    }
+
+    @Override
     public Image getAttributionImage() {
         if (attrImgUrl != null)
             return new ImageIcon(attrImgUrl).getImage();
+
         else
             return null;
     }
 
-    public boolean requiresAttribution() {
-        return false;
-    }
-
-    public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
+    @Override
+    public String getAttributionImageURL() {
         throw new UnsupportedOperationException("no attribution");
     }
 
-    public String getAttributionLinkURL() {
+    @Override
+    public String getTermsOfUseText() {
         throw new UnsupportedOperationException("no attribution");
     }
 
+    @Override
     public String getTermsOfUseURL() {
         throw new UnsupportedOperationException("no attribution");
     }
 
+    @Override
     public double latToTileY(double lat, int zoom) {
         double l = lat / 180 * Math.PI;
@@ -104,12 +127,15 @@
     }
 
+    @Override
     public double lonToTileX(double lon, int zoom) {
         return Math.pow(2.0, zoom - 3) * (lon + 180.0) / 45.0;
     }
 
+    @Override
     public double tileYToLat(int y, int zoom) {
         return Math.atan(Math.sinh(Math.PI - (Math.PI * y / Math.pow(2.0, zoom - 1)))) * 180 / Math.PI;
     }
 
+    @Override
     public double tileXToLon(int x, int zoom) {
         return x * 45.0 / Math.pow(2.0, zoom - 3) - 180.0;
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 26793)
@@ -1,3 +1,5 @@
 package org.openstreetmap.gui.jmapviewer.tilesources;
+
+//License: GPL.
 
 import java.awt.Image;
@@ -177,4 +179,12 @@
 
     @Override
+    public String getAttributionLinkURL() {
+        //return "http://bing.com/maps"
+        // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
+        // (the requirement is that we have such a link at the bottom of the window)
+        return "http://go.microsoft.com/?linkid=9710837";
+    }
+
+    @Override
     public Image getAttributionImage() {
         try {
@@ -186,9 +196,11 @@
 
     @Override
-    public String getAttributionLinkURL() {
-        //return "http://bing.com/maps"
-        // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
-        // (the requirement is that we have such a link at the bottom of the window)
-        return "http://go.microsoft.com/?linkid=9710837";
+    public String getAttributionImageURL() {
+        return "http://opengeodata.org/microsoft-imagery-details";
+    }
+
+    @Override
+    public String getTermsOfUseText() {
+        return null;
     }
 
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java	(revision 26792)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java	(revision 26793)
@@ -1,3 +1,5 @@
 package org.openstreetmap.gui.jmapviewer.tilesources;
+
+//License: GPL.
 
 import java.awt.Image;
@@ -9,6 +11,8 @@
     protected int minZoom = 0;
     protected String attributionText;
+    protected String attributionLinkURL;
     protected Image attributionImage;
-    protected String attributionLinkURL;
+    protected String attributionImageURL;
+    protected String termsOfUseText;
     protected String termsOfUseURL;
 
@@ -49,4 +53,9 @@
 
     @Override
+    public String getAttributionLinkURL() {
+        return attributionLinkURL;
+    }
+
+    @Override
     public Image getAttributionImage() {
         return attributionImage;
@@ -54,6 +63,11 @@
 
     @Override
-    public String getAttributionLinkURL() {
-        return attributionLinkURL;
+    public String getAttributionImageURL() {
+        return attributionImageURL;
+    }
+
+    @Override
+    public String getTermsOfUseText() {
+        return termsOfUseText;
     }
 
@@ -67,10 +81,18 @@
     }
 
+    public void setAttributionLinkURL(String text) {
+        attributionLinkURL = text;
+    }
+
     public void setAttributionImage(Image img) {
         attributionImage = img;
     }
 
-    public void setAttributionLinkURL(String text) {
-        attributionLinkURL = text;
+    public void setAttributionImageURL(String text) {
+        attributionImageURL = text;
+    }
+
+    public void setTermsOfUseText(String text) {
+        termsOfUseText = text;
     }
 
