Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryDataSet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryDataSet.java	(revision 1708)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryDataSet.java	(revision 1709)
@@ -9,4 +9,13 @@
 
 public class HistoryDataSet {
+
+    private static HistoryDataSet historyDataSet;
+
+    public static HistoryDataSet getInstance() {
+        if (historyDataSet == null) {
+            historyDataSet = new HistoryDataSet();
+        }
+        return  historyDataSet;
+    }
 
     private HashMap<Long, ArrayList<HistoryOsmPrimitive>> data;
@@ -44,6 +53,21 @@
         ArrayList<HistoryOsmPrimitive> versions = data.get(id);
         if (versions == null)
-            throw new NoSuchElementException(tr("Didn't find an historized primitive with id {0} in this dataset", id));
+            return null;
         return new History(id, versions);
     }
+
+    /**
+     * merges the histories from the {@see HistoryDataSet} other in this history data set
+     * 
+     * @param other the other history data set. Ignored if null.
+     */
+    public void mergeInto(HistoryDataSet other) {
+        if (other == null)
+            return;
+        for (Long id : other.data.keySet()) {
+            if (!this.data.keySet().contains(id)) {
+                this.data.put(id, other.data.get(id));
+            }
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java	(revision 1708)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java	(revision 1709)
@@ -2,6 +2,8 @@
 package org.openstreetmap.josm.data.osm.history;
 
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Map;
 
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
@@ -116,4 +118,8 @@
     }
 
+    public Map<String,String> getTags() {
+        return Collections.unmodifiableMap(tags);
+    }
+
     @Override
     public int hashCode() {
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 1708)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 1709)
@@ -3,22 +3,23 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.marktr;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
 import java.awt.Component;
-import java.awt.GridBagLayout;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
-import java.util.Calendar;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Date;
 import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
@@ -27,7 +28,13 @@
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
 import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableColumnModel;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
 
 import org.openstreetmap.josm.Main;
@@ -35,9 +42,15 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.SideButton;
-import org.openstreetmap.josm.tools.DateUtils;
-import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.History;
+import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.history.HistoryBrowserDialog;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmServerHistoryReader;
+import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
 
 /**
@@ -53,58 +66,87 @@
  * @author imi
  */
-public class HistoryDialog extends ToggleDialog implements SelectionChangedListener {
-
-    public static final Date unifyDate(Date d) {
-        Calendar c = Calendar.getInstance();
-        c.setTime(d);
-        c.set(Calendar.MINUTE, 0);
-        c.set(Calendar.SECOND, 0);
-        return c.getTime();
-    }
-
-    private static class HistoryItem implements Comparable<HistoryItem> {
-        OsmPrimitive osm;
-        boolean visible;
-
-        public int compareTo(HistoryItem o) {
-            return unifyDate(osm.getTimestamp()).compareTo(unifyDate(o.osm.getTimestamp()));
-        }
-    }
-
-    private final DefaultTableModel data = new DefaultTableModel(){
-        @Override public boolean isCellEditable(int row, int column) {
-            return false;
-        }
-    };
-
-    /**
-     * Main table. 3 columns:
-     * Object | Date | visible (icon, no text)
-     */
-    private JTable history = new JTable(data);
-    private JScrollPane historyPane = new JScrollPane(history);
-
-    private Map<OsmPrimitive, List<HistoryItem>> cache = new HashMap<OsmPrimitive, List<HistoryItem>>();
-    private JLabel notLoaded = new JLabel("<html><i>"+tr("Click Reload to refresh list")+"</i></html>");
-
-    public HistoryDialog() {
-        super(tr("History"), "history", tr("Display the history of all selected items."),
-        Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
-        Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
-        historyPane.setVisible(false);
-        notLoaded.setVisible(true);
-        notLoaded.setHorizontalAlignment(JLabel.CENTER);
-
-        history.setDefaultRenderer(Object.class, new DefaultTableCellRenderer(){
-            @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
-                return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
-            }
-        });
-        data.setColumnIdentifiers(new Object[]{tr("Object"),tr("Date"),""});
-        history.getColumnModel().getColumn(0).setPreferredWidth(200);
-        history.getColumnModel().getColumn(1).setPreferredWidth(200);
-        history.getColumnModel().getColumn(2).setPreferredWidth(20);
-        final TableCellRenderer oldRenderer = history.getTableHeader().getDefaultRenderer();
-        history.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
+public class HistoryDialog extends ToggleDialog {
+
+    /** the registry of history browser dialogs which are currently displaying */
+    static private HashMap<Long, HistoryBrowserDialog> historyBrowserDialogs;
+
+    /**
+     * registers a {@see HistoryBrowserDialog}
+     * @param id the id of the primitive dialog shows the history for
+     * @param dialog the dialog
+     */
+    public static void registerHistoryBrowserDialog(long id, HistoryBrowserDialog dialog) {
+        if (historyBrowserDialogs == null) {
+            historyBrowserDialogs = new HashMap<Long, HistoryBrowserDialog>();
+        }
+        historyBrowserDialogs.put(id, dialog);
+    }
+
+    /**
+     * unregisters a {@see HistoryBrowserDialog}
+     * @param id the id of the primitive whose history dialog is to be unregistered
+     * 
+     */
+    public static void unregisterHistoryBrowserDialog(long id) {
+        if (historyBrowserDialogs == null)
+            return;
+        historyBrowserDialogs.remove(id);
+    }
+
+    /**
+     * replies the history dialog for the primitive with id <code>id</code>; null, if
+     * no such {@see HistoryBrowserDialog} is currently showing
+     * 
+     * @param id the id of the primitive
+     * @return the dialog; null, if no such dialog is showing
+     */
+    public static HistoryBrowserDialog getHistoryBrowserDialog(long id) {
+        if (historyBrowserDialogs == null)
+            return null;
+        return historyBrowserDialogs.get(id);
+    }
+
+
+    /** the table model */
+    protected HistoryItemDataModel model;
+    /** the table with the history items */
+    protected JTable historyTable;
+
+    protected ShowHistoryAction showHistoryAction;
+    protected ReloadAction reloadAction;
+
+    /**
+     * builds the row with the command buttons
+     * 
+     * @return the rows with the command buttons
+     */
+    protected JPanel buildButtonRow() {
+        JPanel buttons = new JPanel(new GridLayout(1,2));
+
+        JButton btn = new JButton(reloadAction = new ReloadAction());
+        btn.setName("btn.reload");
+        buttons.add(btn);
+
+        btn = new JButton(showHistoryAction = new ShowHistoryAction());
+        btn.setName("btn.showhistory");
+        buttons.add(btn);
+
+        return buttons;
+    }
+
+    /**
+     * builds the GUI
+     */
+    protected void build() {
+        model = new HistoryItemDataModel();
+        //setLayout(new BorderLayout());
+        historyTable = new JTable(
+                model,
+                new HistoryTableColumnModel()
+        );
+        historyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        historyTable.setName("table.historyitems");
+        final TableCellRenderer oldRenderer = historyTable.getTableHeader().getDefaultRenderer();
+        historyTable.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
             @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                 JComponent c = (JComponent)oldRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
@@ -120,64 +162,349 @@
             }
         });
-
-        JPanel centerPanel = new JPanel(new GridBagLayout());
-        centerPanel.add(notLoaded, GBC.eol().fill(GBC.BOTH));
-        centerPanel.add(historyPane, GBC.eol().fill(GBC.BOTH));
-        add(centerPanel, BorderLayout.CENTER);
-
-        JPanel buttons = new JPanel(new GridLayout(1,2));
-        buttons.add(new SideButton(marktr("Reload"), "refresh", "History", tr("Reload all currently selected objects and refresh the list."),
-        new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                reload();
+        historyTable.addMouseListener(
+                new MouseAdapter() {
+                    @Override
+                    public void mouseClicked(MouseEvent e) {
+                        if (e.getClickCount() == 2) {
+                            int row = historyTable.rowAtPoint(e.getPoint());
+                            History h = model.get(row);
+                            showHistory(h);
+                        }
+                    }
+                }
+        );
+
+        JScrollPane pane = new JScrollPane(historyTable);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        historyTable.setTableHeader(null);
+        pane.setColumnHeaderView(null);
+        add(pane, BorderLayout.CENTER);
+
+        add(buildButtonRow(), BorderLayout.SOUTH);
+
+        // wire actions
+        //
+        historyTable.getSelectionModel().addListSelectionListener(showHistoryAction);
+        DataSet.selListeners.add(reloadAction);
+    }
+
+    public HistoryDialog() {
+        super(tr("History"), "history", tr("Display the history of all selected items."),
+                Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
+                        Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
+        build();
+        DataSet.selListeners.add(model);
+    }
+
+    /**
+     * refreshes the current list of history items; reloads history information from the server
+     */
+    protected void refresh() {
+        HistoryLoadTask task = new HistoryLoadTask();
+        Main.worker.execute(task);
+    }
+
+    /**
+     * shows the {@see HistoryBrowserDialog} for a given {@see History}
+     * 
+     * @param h the history. Must not be null.
+     * @exception IllegalArgumentException thrown, if h is null
+     */
+    protected void showHistory(History h) throws IllegalArgumentException {
+        if (h == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "h"));
+        HistoryBrowserDialog dialog = getHistoryBrowserDialog(h.getId());
+        if (dialog == null) {
+            dialog = new HistoryBrowserDialog(h);
+        }
+        dialog.setVisible(true);
+    }
+
+    /**
+     * invoked after the asynchronous {@see HistoryLoadTask} is finished.
+     * 
+     * @param task the task which is calling back.
+     */
+    protected void postRefresh(HistoryLoadTask task) {
+        model.refresh();
+        if (task.isCancelled())
+            return;
+        if (task.getLastException() != null) {
+            task.getLastException().printStackTrace();
+            String msg = task.getLastException().getMessage();
+            if (msg == null) {
+                msg = task.getLastException().toString();
             }
-        }));
-        buttons.add(new SideButton(marktr("Revert"), "revert", "History",
-        tr("Revert the state of all currently selected objects to the version selected in the history list."), new ActionListener(){
-            public void actionPerformed(ActionEvent e) {
-                JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr(
+                            "<html>Failed to load history from the server. Details:<br>{0}</html>",
+                            msg.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;")
+                    ),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+            );
+        }
+    }
+
+    /**
+     * The table model with the history items
+     * 
+     */
+    class HistoryItemDataModel extends DefaultTableModel implements SelectionChangedListener{
+        private ArrayList<History> data;
+
+        public HistoryItemDataModel() {
+            data = new ArrayList<History>();
+        }
+
+        @Override
+        public int getRowCount() {
+            if (data == null)
+                return 0;
+            return data.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return data.get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public void refresh() {
+            data.clear();
+            for (OsmPrimitive primitive: Main.ds.getSelected()) {
+                if (primitive.id == 0) {
+                    continue;
+                }
+                History h = HistoryDataSet.getInstance().getHistory(primitive.id);
+                if (h !=null) {
+                    data.add(h);
+                }
             }
-        }));
-        add(buttons, BorderLayout.SOUTH);
-
-        DataSet.selListeners.add(this);
-    }
-
-
-    @Override public void setVisible(boolean b) {
-        super.setVisible(b);
-        if (b)
-            update();
-    }
-
-
-    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
-        if (isVisible())
-            update();
-    }
-
-    /**
-     * Identify all new objects in the selection and if any, hide the list.
-     * Else, update the list with the selected items shown.
-     */
-    private void update() {
-        Collection<OsmPrimitive> sel = Main.ds.getSelected();
-        if (!cache.keySet().containsAll(sel)) {
-            historyPane.setVisible(false);
-            notLoaded.setVisible(true);
-        } else {
-            SortedSet<HistoryItem> orderedHistory = new TreeSet<HistoryItem>();
-            for (OsmPrimitive osm : sel)
-                orderedHistory.addAll(cache.get(osm));
-            data.setRowCount(0);
-            for (HistoryItem i : orderedHistory)
-                data.addRow(new Object[]{i.osm, DateUtils.fromDate(i.osm.getTimestamp()), i.visible});
-            historyPane.setVisible(true);
-            notLoaded.setVisible(false);
-        }
-    }
-
-    void reload() {
-        JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
+            fireTableDataChanged();
+        }
+
+        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+            refresh();
+        }
+
+        public History get(int idx) throws IndexOutOfBoundsException {
+            if (idx < 0 || idx >= data.size())
+                throw new IndexOutOfBoundsException(tr("index out of bounds Got {0}", idx));
+            return data.get(idx);
+        }
+    }
+
+
+    /**
+     * The table cell renderer for the history items.
+     *
+     */
+    class HistoryTableCellRenderer extends JLabel implements TableCellRenderer {
+
+        public final Color BGCOLOR_SELECTED = new Color(143,170,255);
+
+        private HashMap<OsmPrimitiveType, ImageIcon> icons;
+
+        public HistoryTableCellRenderer() {
+            setOpaque(true);
+            icons = new HashMap<OsmPrimitiveType, ImageIcon>();
+            icons.put(OsmPrimitiveType.NODE, ImageProvider.get("data", "node"));
+            icons.put(OsmPrimitiveType.WAY, ImageProvider.get("data", "way"));
+            icons.put(OsmPrimitiveType.RELATION, ImageProvider.get("data", "relation"));
+        }
+
+        protected void renderIcon(History history) {
+            setIcon(icons.get(history.getEarliest().getType()));
+        }
+
+        protected void renderText(History h) {
+            setText(h.getEarliest().getType().getLocalizedDisplayNameSingular() + " " + h.getId());
+        }
+
+        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+                boolean hasFocus, int row, int column) {
+            History h = (History)value;
+            renderIcon(h);
+            renderText(h);
+            if (isSelected) {
+                setBackground(BGCOLOR_SELECTED);
+            } else {
+                setBackground(Color.WHITE);
+            }
+            return this;
+        }
+    }
+
+    /**
+     * The column model
+     */
+    class HistoryTableColumnModel extends DefaultTableColumnModel {
+        protected void createColumns() {
+            TableColumn col = null;
+            HistoryTableCellRenderer renderer = new HistoryTableCellRenderer();
+            // column 0 - History item
+            col = new TableColumn(0);
+            col.setHeaderValue(tr("History item"));
+            col.setCellRenderer(renderer);
+            addColumn(col);
+        }
+
+        public HistoryTableColumnModel() {
+            createColumns();
+        }
+    }
+
+    /**
+     * The asynchronous task which loads history information for  the currently selected
+     * primitives from the server.
+     *
+     */
+    class HistoryLoadTask extends PleaseWaitRunnable {
+
+        private boolean cancelled = false;
+        private Exception lastException  = null;
+
+        public HistoryLoadTask() {
+            super(tr("Load history"), true);
+        }
+
+        @Override
+        protected void cancel() {
+            OsmApi.getOsmApi().cancel();
+            cancelled = true;
+        }
+
+        @Override
+        protected void finish() {
+            postRefresh(this);
+        }
+
+        /**
+         * update the title of the {@see PleaseWaitDialog} with information about
+         * which primitive is currently loaded
+         * 
+         * @param primitive the primitive to be loaded
+         */
+        protected void notifyStartLoadingHistory(final OsmPrimitive primitive) {
+            SwingUtilities.invokeLater(
+                    new Runnable() {
+                        public void run() {
+                            Main.pleaseWaitDlg.setTitle(
+                                    tr("Loading history for {0} with id {1}",
+                                            OsmPrimitiveType.from(primitive).getLocalizedDisplayNameSingular(),
+                                            Long.toString(primitive.id)
+                                    )
+                            );
+                        }
+                    }
+            );
+        }
+
+        /**
+         * enables/disables interminate progress indication in the {@see PleaseWaitDialog}
+         * 
+         * @param enabled true, if interminate progress indication is to enabled; false, otherwise
+         */
+        protected void setInterminateEnabled(final boolean enabled) {
+            SwingUtilities.invokeLater(
+                    new Runnable() {
+                        public void run() {
+                            Main.pleaseWaitDlg.setIndeterminate(enabled);
+                        }
+                    }
+            );
+        }
+
+        @Override
+        protected void realRun() throws SAXException, IOException, OsmTransferException {
+            Collection<OsmPrimitive> selection = Main.ds.getSelected();
+            Iterator<OsmPrimitive> it = selection.iterator();
+            setInterminateEnabled(true);
+            try {
+                while(it.hasNext()) {
+                    OsmPrimitive primitive = it.next();
+                    if (cancelled) {
+                        break;
+                    }
+                    if (primitive.id == 0) {
+                        continue;
+                    }
+                    notifyStartLoadingHistory(primitive);
+                    OsmServerHistoryReader reader = null;
+                    HistoryDataSet ds = null;
+                    try {
+                        reader = new OsmServerHistoryReader(OsmPrimitiveType.from(primitive), primitive.id);
+                        ds = reader.parseHistory();
+                    } catch(OsmTransferException e) {
+                        if (cancelled)
+                            return;
+                        throw e;
+                    }
+                    HistoryDataSet.getInstance().mergeInto(ds);
+                }
+            } catch(OsmTransferException e) {
+                lastException = e;
+                throw e;
+            } finally {
+                setInterminateEnabled(false);
+            }
+        }
+
+        public boolean isCancelled() {
+            return cancelled;
+        }
+
+        public Exception getLastException() {
+            return lastException;
+        }
+    }
+
+    /**
+     * The action for reloading history information of the currently selected primitives.
+     *
+     */
+    class ReloadAction extends AbstractAction implements SelectionChangedListener {
+        public ReloadAction() {
+            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Reload all currently selected objects and refresh the list."));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            refresh();
+        }
+
+        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+            setEnabled(Main.ds.getSelected().size() > 0);
+
+        }
+    }
+
+    /**
+     * The action for showing history information of the current history item.
+     */
+    class ShowHistoryAction extends AbstractAction implements ListSelectionListener {
+        public ShowHistoryAction() {
+            //putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
+            putValue(Action.NAME, tr("Show"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Display the history of the selected primitive"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            int row = historyTable.getSelectionModel().getMinSelectionIndex();
+            if (row < 0) return;
+            History h = model.get(row);
+            showHistory(h);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(historyTable.getSelectionModel().getMinSelectionIndex() >= 0);
+        }
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/history/AdjustmentSynchronizer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/AdjustmentSynchronizer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/AdjustmentSynchronizer.java	(revision 1709)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Adjustable;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.swing.JCheckBox;
+
+
+/**
+ * Synchronizes scrollbar adjustments between a set of
+ * {@see Adjustable}s. Whenever the adjustment of one of
+ * the registerd Adjustables is updated the adjustment of
+ * the other registered Adjustables is adjusted too.
+ *
+ */
+public class AdjustmentSynchronizer implements AdjustmentListener {
+
+    private final  ArrayList<Adjustable> synchronizedAdjustables;
+    private final  HashMap<Adjustable, Boolean> enabledMap;
+
+    private final Observable observable;
+
+    public AdjustmentSynchronizer() {
+        synchronizedAdjustables = new ArrayList<Adjustable>();
+        enabledMap = new HashMap<Adjustable, Boolean>();
+        observable = new Observable();
+    }
+
+
+    /**
+     * registers an {@see Adjustable} for participation in synchronized
+     * scrolling.
+     *
+     * @param adjustable the adjustable
+     */
+    public void participateInSynchronizedScrolling(Adjustable adjustable) {
+        if (adjustable == null)
+            return;
+        if (synchronizedAdjustables.contains(adjustable))
+            return;
+        synchronizedAdjustables.add(adjustable);
+        setParticipatingInSynchronizedScrolling(adjustable, true);
+        adjustable.addAdjustmentListener(this);
+    }
+
+    /**
+     * event handler for {@see AdjustmentEvent}s
+     *
+     */
+    public void adjustmentValueChanged(AdjustmentEvent e) {
+        if (! enabledMap.get(e.getAdjustable()))
+            return;
+        for (Adjustable a : synchronizedAdjustables) {
+            if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) {
+                a.setValue(e.getValue());
+            }
+        }
+    }
+
+    /**
+     * sets whether adjustable participates in adjustment synchronization
+     * or not
+     *
+     * @param adjustable the adjustable
+     */
+    protected void setParticipatingInSynchronizedScrolling(Adjustable adjustable, boolean isParticipating) {
+        if (adjustable == null)
+            throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "adjustable"));
+
+        if (! synchronizedAdjustables.contains(adjustable))
+            throw new IllegalStateException(tr("adjustable {0} not registered yet. Can't set participation in synchronized adjustment",adjustable));
+
+        enabledMap.put(adjustable, isParticipating);
+        observable.notifyObservers();
+    }
+
+    /**
+     * returns true if an adjustable is participating in synchronized scrolling
+     *
+     * @param adjustable the adjustable
+     * @return true, if the adjustable is participating in synchronized scrolling, false otherwise
+     * @throws IllegalStateException thrown, if adjustable is not registered for synchronized scrolling
+     */
+    protected boolean isParticipatingInSynchronizedScrolling(Adjustable adjustable) throws IllegalStateException {
+        if (! synchronizedAdjustables.contains(adjustable))
+            throw new IllegalStateException(tr("adjustable {0} not registered yet",adjustable));
+
+        return enabledMap.get(adjustable);
+    }
+
+    /**
+     * wires a {@see JCheckBox} to  the adjustment synchronizer, in such a way  that:
+     * <li>
+     *   <ol>state changes in the checkbox control whether the adjustable participates
+     *      in synchronized adjustment</ol>
+     *   <ol>state changes in this {@see AdjustmentSynchronizer} are reflected in the
+     *      {@see JCheckBox}</ol>
+     * </li>
+     *
+     *
+     * @param view  the checkbox to control whether an adjustable participates in synchronized
+     *      adjustment
+     * @param adjustable the adjustable
+     * @exception IllegalArgumentException thrown, if view is null
+     * @exception IllegalArgumentException thrown, if adjustable is null
+     */
+    protected void adapt(final JCheckBox view, final Adjustable adjustable) throws IllegalArgumentException, IllegalStateException {
+        if (adjustable == null)
+            throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "adjustable"));
+        if (view == null)
+            throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "view"));
+
+        if (! synchronizedAdjustables.contains(adjustable)) {
+            participateInSynchronizedScrolling(adjustable);
+        }
+
+        // register an item lister with the check box
+        //
+        view.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent e) {
+                switch(e.getStateChange()) {
+                case ItemEvent.SELECTED:
+                    if (!isParticipatingInSynchronizedScrolling(adjustable)) {
+                        setParticipatingInSynchronizedScrolling(adjustable, true);
+                    }
+                    break;
+                case ItemEvent.DESELECTED:
+                    if (isParticipatingInSynchronizedScrolling(adjustable)) {
+                        setParticipatingInSynchronizedScrolling(adjustable, false);
+                    }
+                    break;
+                }
+            }
+        });
+
+        observable.addObserver(
+                new Observer() {
+                    public void update(Observable o, Object arg) {
+                        boolean sync = isParticipatingInSynchronizedScrolling(adjustable);
+                        if (view.isSelected() != sync) {
+                            view.setSelected(sync);
+                        }
+                    }
+                }
+        );
+        setParticipatingInSynchronizedScrolling(adjustable, true);
+        view.setSelected(true);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java	(revision 1709)
@@ -0,0 +1,141 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.data.osm.history.History;
+
+/**
+ * HistoryBrowser is an UI component which displays history information about an {@see OsmPrimitive}.
+ * 
+ *
+ */
+public class HistoryBrowser extends JPanel {
+
+    /** the model */
+    private HistoryBrowserModel model;
+
+    /**
+     * embedds table in a {@see JScrollPane}
+     * 
+     * @param table the table
+     * @return the {@see JScrollPane} with the embedded table
+     */
+    protected JScrollPane embeddInScrollPane(JTable table) {
+        JScrollPane pane = new JScrollPane(table);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        return pane;
+    }
+
+    /**
+     * creates the table which shows the list of versions
+     * 
+     * @return  the panel with the version table
+     */
+    protected JPanel createVersionTablePanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+
+        VersionTable tbl = new VersionTable(model);
+        pnl.add(embeddInScrollPane(tbl), BorderLayout.CENTER);
+        return pnl;
+    }
+
+    /**
+     * creates the panel which shows information about two different versions
+     * of the same {@see OsmPrimitive}.
+     * 
+     * @return the panel
+     */
+    protected JPanel createVersionComparePanel() {
+        JTabbedPane pane = new JTabbedPane();
+        pane.add(new TagInfoViewer(model));
+        pane.setTitleAt(0, tr("Tags"));
+
+        pane.add(new NodeListViewer(model));
+        pane.setTitleAt(1, tr("Nodes"));
+
+        pane.add(new RelationMemberListViewer(model));
+        pane.setTitleAt(2, tr("Members"));
+
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+        pnl.add(pane, BorderLayout.CENTER);
+        return pnl;
+    }
+
+    /**
+     * builds the GUI
+     */
+    protected void build() {
+        JPanel left;
+        JPanel right;
+        setLayout(new BorderLayout());
+        JSplitPane pane = new JSplitPane(
+                JSplitPane.HORIZONTAL_SPLIT,
+                left = createVersionTablePanel(),
+                right = createVersionComparePanel()
+        );
+        add(pane, BorderLayout.CENTER);
+
+        pane.setOneTouchExpandable(true);
+        pane.setDividerLocation(150);
+
+        Dimension minimumSize = new Dimension(100, 50);
+        left.setMinimumSize(minimumSize);
+        right.setMinimumSize(minimumSize);
+    }
+
+    /**
+     * constructor
+     */
+    public HistoryBrowser() {
+        model = new HistoryBrowserModel();
+        build();
+    }
+
+    /**
+     * constructor
+     * @param history  the history of an {@see OsmPrimitive}
+     */
+    public HistoryBrowser(History history) {
+        this();
+        populate(history);
+    }
+
+    /**
+     * populates the browser with the history of a specific {@see OsmPrimitive}
+     * 
+     * @param history the history
+     */
+    public void populate(History history) {
+        model.setHistory(history);
+    }
+
+    /**
+     * replies the {@see History} currently displayed by this browser
+     * 
+     * @return the current history
+     */
+    public History getHistory() {
+        return model.getHistory();
+    }
+
+    /**
+     * replies the model used by this browser
+     * @return the model
+     */
+    public HistoryBrowserModel getModel() {
+        return model;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialog.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialog.java	(revision 1709)
@@ -0,0 +1,128 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.history.History;
+import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
+
+/**
+ * This is non-modal dialog, always showing on top, which displays history information
+ * about a given {@see OsmPrimitive}.
+ * 
+ */
+public class HistoryBrowserDialog extends JDialog {
+
+    /** the embedded browser */
+    private HistoryBrowser browser;
+
+    /**
+     * displays the title for this dialog
+     * 
+     * @param h the current history
+     */
+    protected void renderTitle(History h) {
+        String title = tr(
+                "History for {0} {1}",
+                h.getEarliest().getType().getLocalizedDisplayNameSingular(),
+                Long.toString(h.getId())
+        );
+        setTitle(title);
+    }
+
+    /**
+     * builds the GUI
+     * 
+     */
+    protected void build() {
+        setLayout(new BorderLayout());
+        browser = new HistoryBrowser();
+        add(browser, BorderLayout.CENTER);
+
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.RIGHT));
+
+        JButton btn = new JButton(new CloseAction());
+        btn.setName("btn.close");
+        pnl.add(btn);
+        add(pnl, BorderLayout.SOUTH);
+
+        setSize(800, 500);
+    }
+
+    /**
+     * constructor
+     * 
+     * @param history  the history to be displayed
+     */
+    public HistoryBrowserDialog(History history) {
+        super(JOptionPane.getFrameForComponent(Main.parent), false);
+        setAlwaysOnTop(true);
+        build();
+        setHistory(history);
+        renderTitle(history);
+    }
+
+    /**
+     * sets the current history
+     * @param history
+     */
+    protected void setHistory(History history) {
+        browser.populate(history);
+    }
+
+    /**
+     * registers this dialog with the registry of history dialogs
+     * 
+     * @see HistoryDialog#registerHistoryBrowserDialog(long, HistoryBrowserDialog)
+     */
+    protected void register() {
+        HistoryDialog.registerHistoryBrowserDialog(browser.getHistory().getId(), this);
+    }
+
+    /**
+     * unregisters this dialog from the registry of history dialogs
+     * 
+     * @see HistoryDialog#unregisterHistoryBrowserDialog(long)
+     */
+    protected void unregister() {
+        HistoryDialog.unregisterHistoryBrowserDialog(browser.getHistory().getId());
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            register();
+            toFront();
+        } else {
+            unregister();
+        }
+        super.setVisible(visible);
+    }
+
+    class CloseAction extends AbstractAction {
+        public CloseAction() {
+            putValue(NAME, tr("Close"));
+            putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            setVisible(false);
+        }
+    }
+
+    public HistoryBrowser getHistoryBrowser() {
+        return browser;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java	(revision 1709)
@@ -0,0 +1,581 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Observable;
+import java.util.logging.Logger;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.History;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.data.osm.history.HistoryRelation;
+import org.openstreetmap.josm.data.osm.history.HistoryWay;
+
+/**
+ * This is the model used by the history browser.
+ * 
+ * The state this model manages consists of the following elements:
+ * <ul>
+ *   <li>the {@see History} of a specific {@see OsmPrimitive}</li>
+ *   <li>a dedicated version in this {@see History} called the {@see PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
+ *   <li>another version in this {@see History} called the {@see PointInTimeType#CURRENT_POINT_IN_TIME}</li>
+ * <ul>
+ * {@see HistoryBrowser} always compares the {@see PointInTimeType#REFERENCE_POINT_IN_TIME} with the
+ * {@see PointInTimeType#CURRENT_POINT_IN_TIME}.
+
+ * This model provides various {@see TableModel}s for {@see JTable}s used in {@see HistoryBrowser}, for
+ * instance:
+ * <ul>
+ *  <li>{@see #getTagTableModel(PointInTimeType)} replies a {@see TableModel} for the tags of either of
+ *   the two selected versions</li>
+ *  <li>{@see #getNodeListTableModel(PointInTimeType)} replies a {@see TableModel} for the list of nodes of
+ *   the two selected versions (if the current history provides information about a {@see Way}</li>
+ *  <li> {@see #getRelationMemberTableModel(PointInTimeType)} replies a {@see TableModel} for the list of relation
+ *  members  of the two selected versions (if the current history provides information about a {@see Relation}</li>
+ *  </ul>
+ * 
+ * @see HistoryBrowser
+ */
+public class HistoryBrowserModel extends Observable {
+
+    private static Logger logger = Logger.getLogger(HistoryBrowserModel.class.getName());
+
+    /** the history of an OsmPrimitive */
+    private History history;
+    private HistoryOsmPrimitive reference;
+    private HistoryOsmPrimitive current;
+
+    private VersionTableModel versionTableModel;
+    private TagTableModel currentTagTableModel;
+    private TagTableModel referenceTagTableModel;
+    private NodeListTableModel currentNodeListTableModel;
+    private NodeListTableModel referenceNodeListTableModel;
+    private RelationMemberTableModel currentRelationMemberTableModel;
+    private RelationMemberTableModel referenceRelationMemberTableModel;
+
+    public HistoryBrowserModel() {
+        versionTableModel = new VersionTableModel();
+        currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
+        referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
+        currentNodeListTableModel = new NodeListTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
+        referenceNodeListTableModel = new NodeListTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
+        currentRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
+        referenceRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
+    }
+
+    public HistoryBrowserModel(History history) {
+        this();
+        setHistory(history);
+    }
+
+    /**
+     * replies the history managed by this model
+     * @return the history
+     */
+    public History getHistory() {
+        return history;
+    }
+
+    /**
+     * sets the history to be managed by this model
+     * 
+     * @param history the history
+     * 
+     */
+    public void setHistory(History history) {
+        this.history = history;
+        if (history.getNumVersions() > 0) {
+            current = history.getEarliest();
+            reference = history.getEarliest();
+        }
+        initTagTableModels();
+        fireModelChange();
+    }
+
+
+    protected void fireModelChange() {
+        setChanged();
+        notifyObservers();
+    }
+
+    /**
+     * Replies the table model to be used in a {@see JTable} which
+     * shows the list of versions in this history.
+     * 
+     * @return the table model
+     */
+    public VersionTableModel getVersionTableModel() {
+        return versionTableModel;
+    }
+
+    protected void initTagTableModels() {
+        currentTagTableModel.initKeyList();
+        referenceTagTableModel.initKeyList();
+    }
+
+    protected void initNodeListTabeModel() {
+        currentNodeListTableModel.fireTableDataChanged();
+        referenceNodeListTableModel.fireTableDataChanged();
+    }
+
+    protected void initMemberListTableModel() {
+        currentRelationMemberTableModel.fireTableDataChanged();
+        referenceRelationMemberTableModel.fireTableDataChanged();
+    }
+
+    /**
+     * replies the tag table model for the respective point in time
+     * 
+     * @param pointInTimeType the type of the point in time (must not be null)
+     * @return the tag table model
+     * @exception IllegalArgumentException thrown, if pointInTimeType is null
+     */
+    public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
+        if (pointInTimeType == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "pointInTimeType"));
+        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
+            return currentTagTableModel;
+        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
+            return referenceTagTableModel;
+
+        // should not happen
+        return null;
+    }
+
+    public NodeListTableModel getNodeListTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
+        if (pointInTimeType == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "pointInTimeType"));
+        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
+            return currentNodeListTableModel;
+        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
+            return referenceNodeListTableModel;
+
+        // should not happen
+        return null;
+    }
+
+    public RelationMemberTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
+        if (pointInTimeType == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "pointInTimeType"));
+        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
+            return currentRelationMemberTableModel;
+        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
+            return referenceRelationMemberTableModel;
+
+        // should not happen
+        return null;
+    }
+
+    public void setReferencePointInTime(HistoryOsmPrimitive reference) throws IllegalArgumentException, IllegalStateException{
+        if (reference == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "reference"));
+        if (history == null)
+            throw new IllegalStateException(tr("history not initialized yet. Failed to set reference primitive."));
+        if (reference.getId() != history.getId())
+            throw new IllegalArgumentException(tr("failed to set reference. reference id {0} doesn't match history id {1}", reference.getId(),  history.getId()));
+        HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion());
+        if (primitive == null)
+            throw new IllegalArgumentException(tr("failed to set reference. reference version {0} not available in history", reference.getVersion()));
+
+        this.reference = reference;
+        initTagTableModels();
+        initNodeListTabeModel();
+        initMemberListTableModel();
+        setChanged();
+        notifyObservers();
+    }
+
+    public void setCurrentPointInTime(HistoryOsmPrimitive current) throws IllegalArgumentException, IllegalStateException{
+        if (current == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "current"));
+        if (history == null)
+            throw new IllegalStateException(tr("history not initialized yet. Failed to set current primitive."));
+        if (current.getId() != history.getId())
+            throw new IllegalArgumentException(tr("failed to set reference. reference id {0} doesn't match history id {1}", current.getId(),  history.getId()));
+        HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion());
+        if (primitive == null)
+            throw new IllegalArgumentException(tr("failed to set current. current version {0} not available in history", current.getVersion()));
+        this.current = current;
+        initTagTableModels();
+        initNodeListTabeModel();
+        initMemberListTableModel();
+        setChanged();
+        notifyObservers();
+    }
+
+    /**
+     * Replies the history OSM primitive for the {@see PointInTimeType#CURRENT_POINT_IN_TIME}
+     * 
+     * @return the history OSM primitive for the {@see PointInTimeType#CURRENT_POINT_IN_TIME} (may be null)
+     */
+    public HistoryOsmPrimitive getCurrentPointInTime() {
+        return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME);
+    }
+
+    /**
+     * Replies the history OSM primitive for the {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
+     * 
+     * @return the history OSM primitive for the {@see PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null)
+     */
+    public HistoryOsmPrimitive getReferencePointInTime() {
+        return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME);
+    }
+
+    /**
+     * replies the history OSM primitive for a given point in time
+     * 
+     * @param type the type of the point in time (must not be null)
+     * @return the respective primitive. Can be null.
+     * @exception IllegalArgumentException thrown, if type is null
+     */
+    public HistoryOsmPrimitive getPointInTime(PointInTimeType type) throws IllegalArgumentException  {
+        if (type == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "type"));
+        if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
+            return current;
+        else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
+            return reference;
+
+        // should not happen
+        return null;
+    }
+
+    /**
+     * The table model for the list of versions in the current history
+     *
+     */
+    public class VersionTableModel extends DefaultTableModel {
+
+        private VersionTableModel() {
+        }
+
+        @Override
+        public int getRowCount() {
+            if (history == null)
+                return 0;
+            return history.getNumVersions();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            if(history == null)
+                return null;
+            return history.get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public void setReferencePointInTime(int row) {
+            if (history == null) return;
+            if (row < 0 || row > history.getNumVersions()) return;
+            HistoryOsmPrimitive reference = history.get(row);
+            HistoryBrowserModel.this.setReferencePointInTime(reference);
+        }
+
+        public void setCurrentPointInTime(int row) {
+            if (history == null) return;
+            if (row < 0 || row > history.getNumVersions()) return;
+            HistoryOsmPrimitive current = history.get(row);
+            HistoryBrowserModel.this.setCurrentPointInTime(current);
+        }
+
+        public boolean isReferencePointInTime(int row) {
+            if (history == null) return false;
+            if (row < 0 || row > history.getNumVersions()) return false;
+            HistoryOsmPrimitive p = history.get(row);
+            return p.equals(reference);
+        }
+    }
+
+
+    /**
+     * The table model for the tags of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
+     * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
+     * 
+     */
+    public class TagTableModel extends DefaultTableModel {
+
+        private ArrayList<String> keys;
+        private PointInTimeType pointInTimeType;
+
+        protected void initKeyList() {
+            HashSet<String> keySet = new HashSet<String>();
+            if (current != null) {
+                keySet.addAll(current.getTags().keySet());
+            }
+            if (reference != null) {
+                keySet.addAll(reference.getTags().keySet());
+            }
+            keys = new ArrayList<String>(keySet);
+            Collections.sort(keys);
+            fireTableDataChanged();
+        }
+
+        protected TagTableModel(PointInTimeType type) {
+            pointInTimeType = type;
+            initKeyList();
+        }
+
+        @Override
+        public int getRowCount() {
+            if (keys == null) return 0;
+            return keys.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return keys.get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public boolean hasTag(String key) {
+            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
+            if (primitive == null)
+                return false;
+            return primitive.hasTag(key);
+        }
+
+        public String getValue(String key) {
+            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
+            if (primitive == null)
+                return null;
+            return primitive.get(key);
+        }
+
+        public boolean oppositeHasTag(String key) {
+            PointInTimeType opposite = pointInTimeType.opposite();
+            HistoryOsmPrimitive primitive = getPointInTime(opposite);
+            if (primitive == null)
+                return false;
+            return primitive.hasTag(key);
+        }
+
+        public String getOppositeValue(String key) {
+            PointInTimeType opposite = pointInTimeType.opposite();
+            HistoryOsmPrimitive primitive = getPointInTime(opposite);
+            if (primitive == null)
+                return null;
+            return primitive.get(key);
+        }
+
+        public boolean hasSameValueAsOpposite(String key) {
+            String value = getValue(key);
+            String oppositeValue = getOppositeValue(key);
+            if (value == null || oppositeValue == null)
+                return false;
+            return value.equals(oppositeValue);
+        }
+
+        public PointInTimeType getPointInTimeType() {
+            return pointInTimeType;
+        }
+
+        public boolean isCurrentPointInTime() {
+            return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME);
+        }
+
+        public boolean isReferencePointInTime() {
+            return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
+        }
+    }
+
+    /**
+     * The table model for the nodes of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
+     * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
+     * 
+     */
+    public class NodeListTableModel extends DefaultTableModel {
+
+        private PointInTimeType pointInTimeType;
+
+        private NodeListTableModel(PointInTimeType pointInTimeType) {
+            this.pointInTimeType = pointInTimeType;
+        }
+
+        @Override
+        public int getRowCount() {
+            int n = 0;
+            if (current != null && current.getType().equals(OsmPrimitiveType.WAY)) {
+                n = ((HistoryWay)current).getNumNodes();
+            }
+            if (reference != null && reference.getType().equals(OsmPrimitiveType.WAY)) {
+                n = Math.max(n,((HistoryWay)reference).getNumNodes());
+            }
+            return n;
+        }
+
+        protected HistoryWay getWay() {
+            if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
+                if (! current.getType().equals(OsmPrimitiveType.WAY))
+                    return null;
+                return (HistoryWay)current;
+            }
+            if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
+                if (! reference.getType().equals(OsmPrimitiveType.WAY))
+                    return null;
+                return (HistoryWay)reference;
+            }
+
+            // should not happen
+            return null;
+        }
+
+        protected HistoryWay getOppositeWay() {
+            PointInTimeType opposite = pointInTimeType.opposite();
+            if (opposite.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
+                if (! current.getType().equals(OsmPrimitiveType.WAY))
+                    return null;
+                return (HistoryWay)current;
+            }
+            if (opposite.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
+                if (! reference.getType().equals(OsmPrimitiveType.WAY))
+                    return null;
+                return (HistoryWay)reference;
+            }
+
+            // should not happen
+            return null;
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            HistoryWay way = getWay();
+            if (way == null)
+                return null;
+            if (row >= way.getNumNodes())
+                return null;
+            return way.getNodes().get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public boolean isSameInOppositeWay(int row) {
+            HistoryWay thisWay = getWay();
+            HistoryWay oppositeWay = getOppositeWay();
+            if (thisWay == null || oppositeWay == null)
+                return false;
+            if (row >= oppositeWay.getNumNodes())
+                return false;
+            return thisWay.getNodeId(row) == oppositeWay.getNodeId(row);
+        }
+
+        public boolean isInOppositeWay(int row) {
+            HistoryWay thisWay = getWay();
+            HistoryWay oppositeWay = getOppositeWay();
+            if (thisWay == null || oppositeWay == null)
+                return false;
+            return oppositeWay.getNodes().contains(thisWay.getNodeId(row));
+        }
+    }
+
+    /**
+     * The table model for the relation members of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
+     * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
+     * 
+     */
+
+    public class RelationMemberTableModel extends DefaultTableModel {
+
+        private PointInTimeType pointInTimeType;
+
+        private RelationMemberTableModel(PointInTimeType pointInTimeType) {
+            this.pointInTimeType = pointInTimeType;
+        }
+
+        @Override
+        public int getRowCount() {
+            int n = 0;
+            if (current != null && current.getType().equals(OsmPrimitiveType.RELATION)) {
+                n = ((HistoryRelation)current).getNumMembers();
+            }
+            if (reference != null && reference.getType().equals(OsmPrimitiveType.RELATION)) {
+                n = Math.max(n,((HistoryRelation)reference).getNumMembers());
+            }
+            return n;
+        }
+
+        protected HistoryRelation getRelation() {
+            if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
+                if (! current.getType().equals(OsmPrimitiveType.RELATION))
+                    return null;
+                return (HistoryRelation)current;
+            }
+            if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
+                if (! reference.getType().equals(OsmPrimitiveType.RELATION))
+                    return null;
+                return (HistoryRelation)reference;
+            }
+
+            // should not happen
+            return null;
+        }
+
+        protected HistoryRelation getOppositeRelation() {
+            PointInTimeType opposite = pointInTimeType.opposite();
+            if (opposite.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
+                if (! current.getType().equals(OsmPrimitiveType.RELATION))
+                    return null;
+                return (HistoryRelation)current;
+            }
+            if (opposite.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
+                if (! reference.getType().equals(OsmPrimitiveType.RELATION))
+                    return null;
+                return (HistoryRelation)reference;
+            }
+
+            // should not happen
+            return null;
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            HistoryRelation relation = getRelation();
+            if (relation == null)
+                return null;
+            if (row >= relation.getNumMembers())
+                return null;
+            return relation.getMembers().get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public boolean isSameInOppositeWay(int row) {
+            HistoryRelation thisRelation = getRelation();
+            HistoryRelation oppositeRelation = getOppositeRelation();
+            if (thisRelation == null || oppositeRelation == null)
+                return false;
+            if (row >= oppositeRelation.getNumMembers())
+                return false;
+            return
+            thisRelation.getMembers().get(row).getPrimitiveId() == oppositeRelation.getMembers().get(row).getPrimitiveId()
+            &&  thisRelation.getMembers().get(row).getRole().equals(oppositeRelation.getMembers().get(row).getRole());
+        }
+
+        public boolean isInOppositeWay(int row) {
+            HistoryRelation thisRelation = getRelation();
+            HistoryRelation oppositeRelation = getOppositeRelation();
+            if (thisRelation == null || oppositeRelation == null)
+                return false;
+            return oppositeRelation.getMembers().contains(thisRelation.getMembers().get(row));
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java	(revision 1709)
@@ -0,0 +1,72 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * The {@see TableCellRenderer} for a list of nodes in [@see HistoryBrower}
+ * 
+ *
+ */
+public class NodeListTableCellRenderer extends JLabel implements TableCellRenderer {
+
+    public final static Color BGCOLOR_EMPTY_ROW = new Color(234,234,234);
+    public final static Color BGCOLOR_NOT_IN_OPPOSITE = new Color(255,197,197);
+    public final static Color BGCOLOR_IN_OPPOSITE = new Color(255,234,213);
+    public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
+
+    private ImageIcon nodeIcon;
+
+    public NodeListTableCellRenderer(){
+        setOpaque(true);
+        nodeIcon = ImageProvider.get("data", "node");
+        setIcon(nodeIcon);
+    }
+
+    protected void renderNode(HistoryBrowserModel.NodeListTableModel model, Long nodeId, int row, boolean isSelected) {
+        String text = "";
+        Color bgColor = Color.WHITE;
+        if (nodeId == null) {
+            text = "";
+            bgColor = BGCOLOR_EMPTY_ROW;
+            setIcon(null);
+        } else {
+            text = tr("Node {0}", nodeId.toString());
+            setIcon(nodeIcon);
+            if (model.isSameInOppositeWay(row)) {
+                bgColor = Color.WHITE;
+            } else if (model.isInOppositeWay(row)) {
+                bgColor = BGCOLOR_IN_OPPOSITE;
+            } else {
+                bgColor = BGCOLOR_NOT_IN_OPPOSITE;
+            }
+        }
+        if (isSelected) {
+            bgColor = BGCOLOR_SELECTED;
+        }
+        setText(text);
+        setBackground(bgColor);
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+        HistoryBrowserModel.NodeListTableModel model = getNodeListTableModel(table);
+        Long nodeId = (Long)value;
+        renderNode(model, nodeId, row, isSelected);
+        return this;
+    }
+
+    protected HistoryBrowserModel.NodeListTableModel getNodeListTableModel(JTable table) {
+        return (HistoryBrowserModel.NodeListTableModel) table.getModel();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java	(revision 1709)
@@ -0,0 +1,29 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+/**
+ * The {@see TableColumnModel} for the table with the list of nodes.
+ * 
+ *
+ */
+public class NodeListTableColumnModel extends DefaultTableColumnModel {
+    protected void createColumns() {
+        TableColumn col = null;
+        NodeListTableCellRenderer renderer = new NodeListTableCellRenderer();
+
+        // column 0 - Version
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Nodes"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+    }
+
+    public NodeListTableColumnModel() {
+        createColumns();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/NodeListViewer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/NodeListViewer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/NodeListViewer.java	(revision 1709)
@@ -0,0 +1,146 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+
+/**
+ * NodeListViewer is a UI component which displays the node list of two
+ * version of a {@see OsmPrimitive} in a {@see History}.
+ * 
+ * <ul>
+ *   <li>on the left, it displays the node list for the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
+ *   <li>on the right, it displays the node list for the version at {@see PointInTimeType#CURRENT_POINT_IN_TIME}</li>
+ * </ul>
+ *
+ */
+public class NodeListViewer extends JPanel {
+
+    private HistoryBrowserModel model;
+    private VersionInfoPanel referenceInfoPanel;
+    private VersionInfoPanel currentInfoPanel;
+    private AdjustmentSynchronizer adjustmentSynchronizer;
+    private SelectionSynchronizer selectionSynchronizer;
+
+    protected JScrollPane embeddInScrollPane(JTable table) {
+        JScrollPane pane = new JScrollPane(table);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        adjustmentSynchronizer.participateInSynchronizedScrolling(pane.getVerticalScrollBar());
+        return pane;
+    }
+
+    protected JTable buildReferenceNodeListTable() {
+        JTable table = new JTable(
+                model.getNodeListTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME),
+                new NodeListTableColumnModel()
+        );
+        table.setName("table.referencenodelisttable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected JTable buildCurrentNodeListTable() {
+        JTable table = new JTable(
+                model.getNodeListTableModel(PointInTimeType.CURRENT_POINT_IN_TIME),
+                new NodeListTableColumnModel()
+        );
+        table.setName("table.currentnodelisttable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,5,5,0);
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
+        add(referenceInfoPanel,gc);
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
+        add(currentInfoPanel,gc);
+
+        adjustmentSynchronizer = new AdjustmentSynchronizer();
+        selectionSynchronizer = new SelectionSynchronizer();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildReferenceNodeListTable()),gc);
+
+        gc.gridx = 1;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildCurrentNodeListTable()),gc);
+    }
+
+    public NodeListViewer(HistoryBrowserModel model) {
+        setModel(model);
+        build();
+    }
+
+    protected void unregisterAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.deleteObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.deleteObserver(referenceInfoPanel);
+        }
+    }
+    protected void registerAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.addObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.addObserver(referenceInfoPanel);
+        }
+    }
+
+    public void setModel(HistoryBrowserModel model) {
+        if (this.model != null) {
+            unregisterAsObserver(model);
+        }
+        this.model = model;
+        if (this.model != null) {
+            registerAsObserver(model);
+        }
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/PointInTimeType.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/PointInTimeType.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/PointInTimeType.java	(revision 1709)
@@ -0,0 +1,24 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+
+/**
+ * PointInTimeType enumerates two points in time in the {@see History} of an {@see OsmPrimitive}.
+ * @author karl
+ *
+ */
+public enum PointInTimeType {
+    /** the point in time selected as reference point when comparing two version */
+    REFERENCE_POINT_IN_TIME,
+
+    /** the point in time selected as current point when comparing two version */
+    CURRENT_POINT_IN_TIME;
+
+    public PointInTimeType opposite() {
+        if (this.equals(REFERENCE_POINT_IN_TIME))
+            return CURRENT_POINT_IN_TIME;
+        else
+            return REFERENCE_POINT_IN_TIME;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java	(revision 1709)
@@ -0,0 +1,110 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.HashMap;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.RelationMember;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+
+/**
+ * The {@see TableCellRenderer} for a list of relation members in {@see HistoryBrower}
+ * 
+ *
+ */
+public class RelationMemberListTableCellRenderer extends JLabel implements TableCellRenderer {
+
+    public final static Color BGCOLOR_EMPTY_ROW = new Color(234,234,234);
+    public final static Color BGCOLOR_NOT_IN_OPPOSITE = new Color(255,197,197);
+    public final static Color BGCOLOR_IN_OPPOSITE = new Color(255,234,213);
+    public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
+
+    private HashMap<OsmPrimitiveType, ImageIcon> icons;
+
+    public RelationMemberListTableCellRenderer(){
+        setOpaque(true);
+        icons = new HashMap<OsmPrimitiveType, ImageIcon>();
+        icons.put(OsmPrimitiveType.NODE, ImageProvider.get("data", "node"));
+        icons.put(OsmPrimitiveType.WAY, ImageProvider.get("data", "way"));
+        icons.put(OsmPrimitiveType.RELATION, ImageProvider.get("data", "relation"));
+    }
+
+
+    protected void renderIcon(RelationMember member) {
+        if (member == null) {
+            setIcon(null);
+        } else {
+            setIcon(icons.get(member.getPrimitiveType()));
+        }
+    }
+
+    protected void renderRole( HistoryBrowserModel.RelationMemberTableModel model, RelationMember member, int row, boolean isSelected) {
+        String text = "";
+        Color bgColor = Color.WHITE;
+        if (member == null) {
+            bgColor = BGCOLOR_EMPTY_ROW;
+        } else {
+            text = member.getRole();
+            if (model.isSameInOppositeWay(row)) {
+                bgColor = Color.WHITE;
+            } else if (model.isInOppositeWay(row)) {
+                bgColor = BGCOLOR_IN_OPPOSITE;
+            } else {
+                bgColor = BGCOLOR_NOT_IN_OPPOSITE;
+            }
+        }
+        setText(text);
+        setToolTipText(text);
+        setBackground(bgColor);
+    }
+
+    protected void renderPrimitive( HistoryBrowserModel.RelationMemberTableModel model, RelationMember member, int row, boolean isSelected) {
+        String text = "";
+        Color bgColor = Color.WHITE;
+        if (member == null) {
+            bgColor = BGCOLOR_EMPTY_ROW;
+        } else {
+            text = member.getPrimitiveType().getLocalizedDisplayNameSingular() + " " + member.getPrimitiveId();
+            if (model.isSameInOppositeWay(row)) {
+                bgColor = Color.WHITE;
+            } else if (model.isInOppositeWay(row)) {
+                bgColor = BGCOLOR_IN_OPPOSITE;
+            } else {
+                bgColor = BGCOLOR_NOT_IN_OPPOSITE;
+            }
+        }
+        setText(text);
+        setToolTipText(text);
+        setBackground(bgColor);
+    }
+
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+        HistoryBrowserModel.RelationMemberTableModel model = gteRelationMemberTableModel(table);
+        RelationMember member = (RelationMember)value;
+        renderIcon(member);
+        switch(column) {
+        case 0:
+            renderRole(model, member, row, isSelected);
+            break;
+        case 1:
+            renderPrimitive(model, member, row, isSelected);
+            break;
+        }
+
+        return this;
+    }
+
+    protected HistoryBrowserModel.RelationMemberTableModel gteRelationMemberTableModel(JTable table) {
+        return (HistoryBrowserModel.RelationMemberTableModel) table.getModel();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListViewer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListViewer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberListViewer.java	(revision 1709)
@@ -0,0 +1,147 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+/**
+ * RelationMemberListViewer is a UI component which displays the  list of relation members of two
+ * version of a {@see Relation} in a {@see History}.
+ * 
+ * <ul>
+ *   <li>on the left, it displays the list of relation members for the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
+ *   <li>on the right, it displays the list of relation members for the version at {@see PointInTimeType#CURRENT_POINT_IN_TIME}</li>
+ * </ul>
+ *
+ */
+
+public class RelationMemberListViewer extends JPanel{
+
+    private HistoryBrowserModel model;
+    private VersionInfoPanel referenceInfoPanel;
+    private VersionInfoPanel currentInfoPanel;
+    private AdjustmentSynchronizer adjustmentSynchronizer;
+    private SelectionSynchronizer selectionSynchronizer;
+
+    protected JScrollPane embeddInScrollPane(JTable table) {
+        JScrollPane pane = new JScrollPane(table);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        adjustmentSynchronizer.participateInSynchronizedScrolling(pane.getVerticalScrollBar());
+        return pane;
+    }
+
+    protected JTable buildReferenceMemberListTable() {
+        JTable table = new JTable(
+                model.getRelationMemberTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME),
+                new RelationMemberTableColumnModel()
+        );
+        table.setName("table.referencememberlisttable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected JTable buildCurrentMemberListTable() {
+        JTable table = new JTable(
+                model.getRelationMemberTableModel(PointInTimeType.CURRENT_POINT_IN_TIME),
+                new RelationMemberTableColumnModel()
+        );
+        table.setName("table.currentmemberlisttable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,5,5,0);
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
+        add(referenceInfoPanel,gc);
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
+        add(currentInfoPanel,gc);
+
+        adjustmentSynchronizer = new AdjustmentSynchronizer();
+        selectionSynchronizer = new SelectionSynchronizer();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildReferenceMemberListTable()),gc);
+
+        gc.gridx = 1;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildCurrentMemberListTable()),gc);
+    }
+
+    public RelationMemberListViewer(HistoryBrowserModel model) {
+        setModel(model);
+        build();
+    }
+
+    protected void unregisterAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.deleteObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.deleteObserver(referenceInfoPanel);
+        }
+    }
+    protected void registerAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.addObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.addObserver(referenceInfoPanel);
+        }
+    }
+
+    public void setModel(HistoryBrowserModel model) {
+        if (this.model != null) {
+            unregisterAsObserver(model);
+        }
+        this.model = model;
+        if (this.model != null) {
+            registerAsObserver(model);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberTableColumnModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberTableColumnModel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/RelationMemberTableColumnModel.java	(revision 1709)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * The {@see TableColumnModel} for the table with the list of relation members.
+ * 
+ */
+public class RelationMemberTableColumnModel extends DefaultTableColumnModel {
+    protected void createColumns() {
+        TableColumn col = null;
+        RelationMemberListTableCellRenderer renderer = new RelationMemberListTableCellRenderer();
+
+        // column 0 - Version
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Role"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 0 - Version
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Primitive"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+    }
+
+    public RelationMemberTableColumnModel() {
+        createColumns();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/SelectionSynchronizer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/SelectionSynchronizer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/SelectionSynchronizer.java	(revision 1709)
@@ -0,0 +1,38 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.util.ArrayList;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+public class SelectionSynchronizer implements ListSelectionListener {
+
+    private ArrayList<ListSelectionModel> participants;
+
+    public SelectionSynchronizer() {
+        participants = new ArrayList<ListSelectionModel>();
+    }
+
+    public void participateInSynchronizedSelection(ListSelectionModel model) {
+        if (model == null)
+            return;
+        if (participants.contains(model))
+            return;
+        participants.add(model);
+        model.addListSelectionListener(this);
+    }
+
+    public void valueChanged(ListSelectionEvent e) {
+        DefaultListSelectionModel referenceModel = (DefaultListSelectionModel)e.getSource();
+        int i = referenceModel.getMinSelectionIndex();
+        for (ListSelectionModel model : participants) {
+            if (model == e.getSource()) {
+                continue;
+            }
+            model.setSelectionInterval(i,i);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java	(revision 1709)
@@ -0,0 +1,147 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+
+/**
+ * TagInfoViewer is a UI component which displays the  list of tags of two
+ * version of a {@see OsmPrimitive} in a {@see History}.
+ * 
+ * <ul>
+ *   <li>on the left, it displays the list of tags for the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
+ *   <li>on the right, it displays the list of tags for the version at {@see PointInTimeType#CURRENT_POINT_IN_TIME}</li>
+ * </ul>
+ *
+ */
+public class TagInfoViewer extends JPanel{
+
+    private HistoryBrowserModel model;
+    private VersionInfoPanel referenceInfoPanel;
+    private VersionInfoPanel currentInfoPanel;
+    private AdjustmentSynchronizer adjustmentSynchronizer;
+    private SelectionSynchronizer selectionSynchronizer;
+
+    protected JScrollPane embeddInScrollPane(JTable table) {
+        JScrollPane pane = new JScrollPane(table);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        adjustmentSynchronizer.participateInSynchronizedScrolling(pane.getVerticalScrollBar());
+        return pane;
+    }
+
+    protected JTable buildReferenceTagTable() {
+        JTable table = new JTable(
+                model.getTagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME),
+                new TagTableColumnModel()
+        );
+        table.setName("table.referencetagtable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected JTable buildCurrentTagTable() {
+        JTable table = new JTable(
+                model.getTagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME),
+                new TagTableColumnModel()
+        );
+        table.setName("table.currenttagtable");
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel());
+        return table;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,5,5,0);
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
+        add(referenceInfoPanel,gc);
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.5;
+        gc.weighty = 0.0;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
+        add(currentInfoPanel,gc);
+
+        adjustmentSynchronizer = new AdjustmentSynchronizer();
+        selectionSynchronizer = new SelectionSynchronizer();
+
+        // ---------------------------
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildReferenceTagTable()),gc);
+
+        gc.gridx = 1;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(embeddInScrollPane(buildCurrentTagTable()),gc);
+    }
+
+    public TagInfoViewer(HistoryBrowserModel model) {
+        setModel(model);
+        build();
+    }
+
+    protected void unregisterAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.deleteObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.deleteObserver(referenceInfoPanel);
+        }
+    }
+    protected void registerAsObserver(HistoryBrowserModel model) {
+        if (currentInfoPanel != null) {
+            model.addObserver(currentInfoPanel);
+        }
+        if (referenceInfoPanel != null) {
+            model.addObserver(referenceInfoPanel);
+        }
+    }
+
+    public void setModel(HistoryBrowserModel model) {
+        if (this.model != null) {
+            unregisterAsObserver(model);
+        }
+        this.model = model;
+        if (this.model != null) {
+            registerAsObserver(model);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java	(revision 1709)
@@ -0,0 +1,91 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.logging.Logger;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * The {@see TableCellRenderer} for a list of tagsin {@see HistoryBrower}
+ * 
+ */
+public class TagTableCellRenderer extends JLabel implements TableCellRenderer {
+
+    static private Logger logger = Logger.getLogger(TagTableCellRenderer.class.getName());
+
+    public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
+    public final static Color BGCOLOR_DIFFERENCE = new Color(255,197,197);
+
+    public TagTableCellRenderer() {
+        setOpaque(true);
+        setForeground(Color.BLACK);
+    }
+
+    protected void renderName(String key, HistoryBrowserModel.TagTableModel model, boolean isSelected) {
+        String text = key;
+        Color bgColor = Color.WHITE;
+        if (! model.hasTag(key)) {
+            text = tr("<undefined>");
+            bgColor = BGCOLOR_DIFFERENCE;
+        } else if (!model.oppositeHasTag(key)) {
+            bgColor = BGCOLOR_DIFFERENCE;
+        }
+        if (isSelected) {
+            bgColor = BGCOLOR_SELECTED;
+        }
+        setText(text);
+        setToolTipText(text);
+        setBackground(bgColor);
+    }
+
+    protected void renderValue(String key, HistoryBrowserModel.TagTableModel model, boolean isSelected) {
+        String text = "";
+        Color bgColor = Color.WHITE;
+        if (! model.hasTag(key)) {
+            text = tr("<undefined>");
+            bgColor = BGCOLOR_DIFFERENCE;
+        } else {
+            text = model.getValue(key);
+            if (!model.hasSameValueAsOpposite(key)) {
+                bgColor = BGCOLOR_DIFFERENCE;
+            }
+        }
+        if (isSelected) {
+            bgColor = BGCOLOR_SELECTED;
+        }
+
+        setText(text);
+        setToolTipText(text);
+        setBackground(bgColor);
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+
+        String key = (String)value;
+        HistoryBrowserModel.TagTableModel model = getTagTableModel(table);
+
+        switch(column) {
+        case 0:
+            // the name column
+            renderName(key, model, isSelected);
+            break;
+        case 1:
+            // the value column
+            renderValue(key, model, isSelected);
+            break;
+        }
+
+        return this;
+    }
+
+    protected HistoryBrowserModel.TagTableModel getTagTableModel(JTable table) {
+        return (HistoryBrowserModel.TagTableModel) table.getModel();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java	(revision 1709)
@@ -0,0 +1,35 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * The {@see TableColumnModel} for the table with the list of tags
+ * 
+ */
+public class TagTableColumnModel extends DefaultTableColumnModel{
+    protected void createColumns() {
+        TableColumn col = null;
+
+        TagTableCellRenderer renderer = new TagTableCellRenderer();
+
+        // column 0 - Name
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Name"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 1 - Value
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Value"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+    }
+
+    public TagTableColumnModel() {
+        createColumns();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java	(revision 1709)
@@ -0,0 +1,83 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.text.SimpleDateFormat;
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+
+/**
+ * VersionInfoPanel is an UI component which displays the basic properties of a version
+ * of a {@see OsmPrimitive}.
+ * 
+ */
+public class VersionInfoPanel extends JPanel implements Observer{
+
+    private PointInTimeType pointInTimeType;
+    private HistoryBrowserModel model;
+    private JLabel lblInfo;
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        lblInfo = new JLabel();
+        lblInfo.setHorizontalAlignment(JLabel.LEFT);
+        add(lblInfo, BorderLayout.CENTER);
+    }
+
+    protected HistoryOsmPrimitive getPrimitive() {
+        if (model == null || pointInTimeType == null)
+            return null;
+        return model.getPointInTime(pointInTimeType);
+    }
+
+    protected String getInfoText() {
+        HistoryOsmPrimitive primitive = getPrimitive();
+        if (primitive == null)
+            return "";
+        String text = tr(
+                "<html>Version <strong>{0}</strong> created on <strong>{1}</strong> by <strong>{2}</strong></html>",
+                Long.toString(primitive.getVersion()),
+                new SimpleDateFormat().format(primitive.getTimestamp()),
+                primitive.getUser()
+        );
+        return text;
+    }
+
+    public VersionInfoPanel() {
+        pointInTimeType = null;
+        model = null;
+        build();
+    }
+
+    /**
+     * constructor
+     * 
+     * @param model  the model (must not be null)
+     * @param pointInTimeType the point in time this panel visualizes (must not be null)
+     * @exception IllegalArgumentException thrown, if model is null
+     * @exception IllegalArgumentException thrown, if pointInTimeType is null
+     *
+     */
+    public VersionInfoPanel(HistoryBrowserModel model, PointInTimeType pointInTimeType) throws IllegalArgumentException {
+        if (pointInTimeType == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "pointInTimeType"));
+        if (model == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "model"));
+
+        this.model = model;
+        this.pointInTimeType = pointInTimeType;
+        model.addObserver(this);
+        build();
+    }
+
+    public void update(Observable o, Object arg) {
+        lblInfo.setText(getInfoText());
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/VersionTable.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/VersionTable.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/VersionTable.java	(revision 1709)
@@ -0,0 +1,75 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.logging.Logger;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/**
+ * VersionTable shows a list of version in a {@see History} of an {@see OsmPrimitive}.
+ * 
+ *
+ */
+public class VersionTable extends JTable implements Observer{
+
+    private static Logger logger = Logger.getLogger(VersionTable.class.getName());
+
+    protected void build() {
+        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        addMouseListener(new MouseHandler());
+        getSelectionModel().addListSelectionListener(new SelectionHandler());
+    }
+
+    public VersionTable(HistoryBrowserModel model) {
+        super(model.getVersionTableModel(), new VersionTableColumnModel());
+        model.addObserver(this);
+        build();
+    }
+
+    protected void handleSelectReferencePointInTime(int row) {
+        getVesionTableModel().setReferencePointInTime(row);
+    }
+
+    protected void handleSelectCurrentPointInTime(int row) {
+        getVesionTableModel().setCurrentPointInTime(row);
+    }
+
+    protected HistoryBrowserModel.VersionTableModel getVesionTableModel() {
+        return (HistoryBrowserModel.VersionTableModel) getModel();
+    }
+
+    class MouseHandler extends MouseAdapter {
+        protected void handleDoubleClick(MouseEvent e) {
+            int row = rowAtPoint(e.getPoint());
+            handleSelectReferencePointInTime(row);
+        }
+
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            switch(e.getClickCount()) {
+            case 2: handleDoubleClick(e); break;
+            }
+        }
+    }
+
+    class SelectionHandler implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent e) {
+            DefaultListSelectionModel model = (DefaultListSelectionModel)e.getSource();
+            if (model.getMinSelectionIndex() >= 0) {
+                handleSelectCurrentPointInTime(model.getMinSelectionIndex());
+            }
+        }
+    }
+
+    public void update(Observable o, Object arg) {
+        repaint();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/VersionTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/VersionTableCellRenderer.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/VersionTableCellRenderer.java	(revision 1709)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * The {@see TableCellRenderer} for a list of versions in {@see HistoryBrower}
+ * 
+ */
+public class VersionTableCellRenderer extends JLabel implements TableCellRenderer {
+
+    static private Logger logger = Logger.getLogger(VersionTableCellRenderer.class.getName());
+
+    public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
+    public final static Color BGCOLOR_IS_REFERENCE_POINT = new Color(255,197,197);
+
+    protected HashMap<OsmPrimitiveType, ImageIcon> icons = null;
+
+    public VersionTableCellRenderer() {
+        loadIcons();
+        setOpaque(true);
+    }
+
+    protected void loadIcons() {
+        icons = new HashMap<OsmPrimitiveType, ImageIcon>();
+        icons.put(OsmPrimitiveType.NODE, ImageProvider.get("data", "node"));
+        icons.put(OsmPrimitiveType.WAY, ImageProvider.get("data", "way"));
+        icons.put(OsmPrimitiveType.RELATION, ImageProvider.get("data", "relation"));
+    }
+
+    protected void renderIcon(HistoryOsmPrimitive primitive) {
+        ImageIcon icon = null;
+        if (primitive != null) {
+            icon = icons.get(primitive.getType());
+        }
+        setIcon(icon);
+    }
+
+    protected void renderText(HistoryOsmPrimitive primitive) {
+        // render lable text
+        //
+        StringBuilder sb = new StringBuilder();
+        if (primitive == null) {
+            sb.append("");
+        } else {
+            sb.append(tr("Version {0}", Long.toString(primitive.getVersion())));
+        }
+        setText(sb.toString());
+
+        // render tooltip text
+        //
+        sb = new StringBuilder();
+        if (primitive == null) {
+            sb.append("");
+        } else {
+            sb.append(
+                    tr("Version {0} created on {1} by {2}",
+                            Long.toString(primitive.getVersion()),
+                            new SimpleDateFormat().format(primitive.getTimestamp()),
+                            primitive.getUser()
+                    )
+            );
+        }
+        setToolTipText(sb.toString());
+    }
+
+    protected void renderBackground(JTable table, int row, boolean isSelected) {
+        Color bgColor = Color.WHITE;
+        if (isSelected) {
+            bgColor = BGCOLOR_SELECTED;
+        } else if (getModel(table).isReferencePointInTime(row)) {
+            bgColor = BGCOLOR_IS_REFERENCE_POINT;
+        }
+        setBackground(bgColor);
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+
+        HistoryOsmPrimitive primitive = (HistoryOsmPrimitive)value;
+        renderIcon(primitive);
+        renderText(primitive);
+        renderBackground(table, row, isSelected);
+        return this;
+    }
+
+    protected HistoryBrowserModel.VersionTableModel getModel(JTable table) {
+        return (HistoryBrowserModel.VersionTableModel)table.getModel();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/history/VersionTableColumnModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/VersionTableColumnModel.java	(revision 1709)
+++ /trunk/src/org/openstreetmap/josm/gui/history/VersionTableColumnModel.java	(revision 1709)
@@ -0,0 +1,27 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.history;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import static org.openstreetmap.josm.tools.I18n.tr;
+/**
+ * The {@see TableColumnModel} for the table with the list of versions
+ * 
+ */
+public class VersionTableColumnModel extends DefaultTableColumnModel {
+    protected void createColumns() {
+        TableColumn col = null;
+        TableCellRenderer renderer = new VersionTableCellRenderer();
+
+        // column 0 - Version
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Version"));
+        col.setCellRenderer(renderer);
+        addColumn(col);
+    }
+
+    public VersionTableColumnModel() {
+        createColumns();
+    }
+}
Index: /trunk/test/functional/org/openstreetmap/josm/gui/history/HistoryBrowserTest.java
===================================================================
--- /trunk/test/functional/org/openstreetmap/josm/gui/history/HistoryBrowserTest.java	(revision 1708)
+++ /trunk/test/functional/org/openstreetmap/josm/gui/history/HistoryBrowserTest.java	(revision 1709)
@@ -85,5 +85,8 @@
     public HistoryBrowserTest(){
         build();
-        populate(OsmPrimitiveType.NODE,354117);
+        //populate(OsmPrimitiveType.NODE,354117);
+        //populate(OsmPrimitiveType.WAY,37951);
+        populate(OsmPrimitiveType.RELATION,5055);
+
     }
 
