Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 4488)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 4489)
@@ -42,4 +42,5 @@
 import javax.swing.UIManager;
 
+import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.actions.OpenFileAction;
@@ -79,4 +80,5 @@
 import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.OpenBrowser;
 import org.openstreetmap.josm.tools.OsmUrlToBounds;
 import org.openstreetmap.josm.tools.PlatformHook;
@@ -252,4 +254,13 @@
         validator = new OsmValidator();
         MapView.addLayerChangeListener(validator);
+
+        // hooks for the jmapviewer component
+        FeatureAdapter.registerBrowserAdapter(new FeatureAdapter.BrowserAdapter() {
+            @Override
+            public void openLink(String url) {
+                OpenBrowser.displayUrl(url);
+            }
+        });
+        FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
 
         toolbar.refreshToolbarControl();
@@ -796,5 +807,5 @@
     /**
      * Replies the current projection.
-     * 
+     *
      * @return
      */
@@ -805,5 +816,5 @@
     /**
      * Sets the current projection
-     * 
+     *
      * @param p the projection
      */
@@ -849,5 +860,5 @@
     /**
      * Register a projection change listener
-     * 
+     *
      * @param listener the listener. Ignored if null.
      */
@@ -865,5 +876,5 @@
     /**
      * Removes a projection change listener
-     * 
+     *
      * @param listener the listener. Ignored if null.
      */
Index: trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 4488)
+++ trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 4489)
@@ -215,25 +215,5 @@
 
     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 */
-
-        if ((attrImageBounds != null && attrImageBounds.contains(p))
-                || (attrTextBounds != null && attrTextBounds.contains(p))) {
-            if (click)
-                OpenBrowser.displayUrl(ts.getAttributionLinkURL());
-            /*else
-                Main.warn(ts.getAttributionLinkURL());*/
-            return true;
-        } else if (attrToUBounds != null && attrToUBounds.contains(p)) {
-            if (click)
-                OpenBrowser.displayUrl(ts.getTermsOfUseURL());
-            /*else
-                Main.warn(ts.getTermsOfUseURL());*/
-            return true;
-        }
-        return false;
+        return attribution.handleAttribution(p, click);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 4488)
+++ trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 4489)
@@ -36,4 +36,5 @@
 import javax.swing.SwingUtilities;
 
+import org.openstreetmap.gui.jmapviewer.AttributionSupport;
 import org.openstreetmap.gui.jmapviewer.Coordinate;
 import org.openstreetmap.gui.jmapviewer.JobDispatcher;
@@ -158,15 +159,6 @@
     JCheckBoxMenuItem showErrorsPopup;
     Tile showMetadataTile;
-    private Image attrImage;
-    private String attrTermsUrl;
-    private Rectangle attrImageBounds, attrToUBounds, attrTextBounds;
+    private AttributionSupport attribution = new AttributionSupport();
     private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13);
-    private static final Font ATTR_FONT = new Font("Arial", Font.PLAIN, 10);
-    private static final Font ATTR_LINK_FONT;
-    static {
-        HashMap<TextAttribute, Integer> aUnderline = new HashMap<TextAttribute, Integer>();
-        aUnderline.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
-        ATTR_LINK_FONT = ATTR_FONT.deriveFont(aUnderline);
-    }
 
     protected boolean autoZoom;
@@ -248,15 +240,5 @@
     {
         this.tileSource = tileSource;
-        boolean requireAttr = tileSource.requiresAttribution();
-        if(requireAttr) {
-            attrImage = tileSource.getAttributionImage();
-            /*if(attrImage == null) {
-                Main.debug("Attribution image was null.");
-            } else {
-                Main.debug("Got an attribution image " + attrImage.getHeight(this) + "x" + attrImage.getWidth(this));
-            }*/
-
-            attrTermsUrl = tileSource.getTermsOfUseURL();
-        }
+        attribution.initialize(tileSource);
 
         currentZoomLevel = getBestZoom();
@@ -477,13 +459,5 @@
                             tileOptionMenu.show(e.getComponent(), e.getX(), e.getY());
                         } else if (e.getButton() == MouseEvent.BUTTON1) {
-                            if (!tileSource.requiresAttribution())
-                                return;
-
-                            if ((attrImageBounds != null && attrImageBounds.contains(e.getPoint()))
-                                    || (attrTextBounds != null && attrTextBounds.contains(e.getPoint()))) {
-                                OpenBrowser.displayUrl(tileSource.getAttributionLinkURL());
-                            } else if (attrToUBounds != null && attrToUBounds.contains(e.getPoint())) {
-                                OpenBrowser.displayUrl(tileSource.getTermsOfUseURL());
-                            }
+                            attribution.handleAttribution(e.getPoint(), true);
                         }
                     }
@@ -920,4 +894,5 @@
         return new Coordinate(ll.lat(),ll.lon());
     }
+
     private final TileSet nullTileSet = new TileSet((LatLon)null, (LatLon)null, 0);
     private class TileSet {
@@ -1239,47 +1214,6 @@
             this.paintTileText(ts, t, g, mv, displayZoomLevel, t);
         }
-
-        if (tileSource.requiresAttribution()) {
-            // Draw attribution
-            Font font = g.getFont();
-            g.setFont(ATTR_LINK_FONT);
-            g.setColor(Color.white);
-
-            // 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 = mv.getHeight() - textHeight;
-            if(attrTermsUrl != null) {
-                int x = 2;
-                int y = mv.getHeight() - textHeight;
-                attrToUBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
-                myDrawString(g, "Background Terms of Use", x, y);
-            }
-
-            // Draw attribution logo
-            if(attrImage != null) {
-                int x = 2;
-                int imgWidth = attrImage.getWidth(this);
-                int height = attrImage.getHeight(this);
-                int y = termsTextY - height - textHeight - 5;
-                attrImageBounds = new Rectangle(x, y, imgWidth, height);
-                g.drawImage(attrImage, x, y, this);
-            }
-
-            g.setFont(ATTR_FONT);
-            String attributionText = tileSource.getAttributionText(displayZoomLevel,
-                    getShiftedCoord(topLeft), getShiftedCoord(botRight));
-            Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(attributionText, g);
-            {
-                int x = mv.getWidth() - (int) stringBounds.getWidth();
-                int y = mv.getHeight() - textHeight;
-                myDrawString(g, attributionText, x, y);
-                attrTextBounds = new Rectangle(x, y-textHeight, textWidth, textRealHeight);
-            }
-
-            g.setFont(font);
-        }
+        
+        attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(topLeft), getShiftedCoord(botRight), displayZoomLevel, this);
 
         //g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);
Index: trunk/src/org/openstreetmap/josm/tools/I18n.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/I18n.java	(revision 4488)
+++ trunk/src/org/openstreetmap/josm/tools/I18n.java	(revision 4489)
@@ -22,4 +22,5 @@
 import javax.swing.UIManager;
 
+import org.openstreetmap.gui.jmapviewer.FeatureAdapter.TranslationAdapter;
 import org.openstreetmap.josm.Main;
 
@@ -203,5 +204,5 @@
      * For instance, {@code trn("There was an error!", "There were errors!", i)} or
      * {@code trn("Found {0} error in {1}!", "Found {0} errors in {1}!", i, Integer.toString(i), url)}.
-     * 
+     *
      * @param singularText the singular text to translate.
      * Must be a string literal. (No constants or local vars.)
@@ -694,3 +695,12 @@
         return 0;
     }
+
+    public static TranslationAdapter getTranslationAdapter() {
+        return new TranslationAdapter() {
+            @Override
+            public String tr(String text, Object... objects) {
+                return I18n.tr(text, objects);
+            }
+        };
+    }
 }
