Index: trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 2689)
@@ -20,4 +20,9 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.history.HistoryNameFormatter;
+import org.openstreetmap.josm.data.osm.history.HistoryNode;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.data.osm.history.HistoryRelation;
+import org.openstreetmap.josm.data.osm.history.HistoryWay;
 
 /**
@@ -25,5 +30,5 @@
  *
  */
-public class DefaultNameFormatter implements NameFormatter {
+public class DefaultNameFormatter implements NameFormatter, HistoryNameFormatter {
 
     static private DefaultNameFormatter instance;
@@ -238,3 +243,168 @@
         return sb.toString();
     }
+
+
+    /**
+     * Decorates the name of primitive with its id, if the preference
+     * <tt>osm-primitives.showid</tt> is set.
+     * 
+     * The id is append to the {@see StringBuilder} passed in in <code>name</code>.
+     *
+     * @param name  the name without the id
+     * @param primitive the primitive
+     */
+    protected void decorateNameWithId(StringBuilder name, HistoryOsmPrimitive primitive) {
+        if (Main.pref.getBoolean("osm-primitives.showid")) {
+            name.append(tr(" [id: {0}]", primitive.getId()));
+        }
+    }
+
+
+    /**
+     * Formats a name for a history node
+     *
+     * @param node the node
+     * @return the name
+     */
+    public String format(HistoryNode node) {
+        StringBuilder sb = new StringBuilder();
+        String name;
+        if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+            name = node.getLocalName();
+        } else {
+            name = node.getName();
+        }
+        if (name == null) {
+            sb.append(node.getId());
+        } else {
+            sb.append(name);
+        }
+        sb.append(" (")
+        .append(node.getCoords().latToString(CoordinateFormat.getDefaultFormat()))
+        .append(", ")
+        .append(node.getCoords().lonToString(CoordinateFormat.getDefaultFormat()))
+        .append(")");
+        decorateNameWithId(sb, node);
+        return sb.toString();
+    }
+
+    /**
+     * Formats a name for a way
+     *
+     * @param way the way
+     * @return the name
+     */
+    public String format(HistoryWay way) {
+        StringBuilder sb = new StringBuilder();
+        String name;
+        if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+            name = way.getLocalName();
+        } else {
+            name = way.getName();
+        }
+        if (name != null) {
+            sb.append(name);
+        }
+        if (sb.length() == 0 && way.get("ref") != null) {
+            sb.append(way.get("ref"));
+        }
+        if (sb.length() == 0) {
+            sb.append(
+                    (way.get("highway") != null) ? tr("highway") :
+                        (way.get("railway") != null) ? tr("railway") :
+                            (way.get("waterway") != null) ? tr("waterway") :
+                                (way.get("landuse") != null) ? tr("landuse") : ""
+            );
+        }
+
+        int nodesNo = way.isClosed() ? way.getNumNodes() -1 : way.getNumNodes();
+        String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
+        sb.append((sb.length() > 0) ? " ("+nodes+")" : nodes);
+        decorateNameWithId(sb, way);
+        return sb.toString();
+    }
+
+
+    /**
+     * Formats a name for a {@see HistoryRelation})
+     *
+     * @param relation the relation
+     * @return the name
+     */
+    public String format(HistoryRelation relation) {
+        StringBuilder sb = new StringBuilder();
+        if (relation.get("type") != null) {
+            sb.append(relation.get("type"));
+        } else {
+            sb.append(tr("relation"));
+        }
+        sb.append(" (");
+        String nameTag = null;
+        Set<String> namingTags = new HashSet<String>(getNamingtagsForRelations());
+        for (String n : relation.getTags().keySet()) {
+            // #3328: "note " and " note" are name tags too
+            if (namingTags.contains(n.trim())) {
+                if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+                    nameTag = relation.getLocalName();
+                } else {
+                    nameTag = relation.getName();
+                }
+                if (nameTag == null) {
+                    nameTag = relation.get(n);
+                }
+            }
+            if (nameTag != null) {
+                break;
+            }
+        }
+        if (nameTag == null) {
+            sb.append(Long.toString(relation.getId())).append(", ");
+        } else {
+            sb.append("\"").append(nameTag).append("\", ");
+        }
+
+        int mbno = relation.getNumMembers();
+        sb.append(trn("{0} member", "{0} members", mbno, mbno)).append(")");
+
+        decorateNameWithId(sb, relation);
+        return sb.toString();
+    }
+
+    /**
+     * Builds a default tooltip text for an HistoryOsmPrimitive <code>primitive</code>.
+     * 
+     * @param primitive the primitmive
+     * @return the tooltip text
+     */
+    public String buildDefaultToolTip(HistoryOsmPrimitive primitive) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<html>");
+        sb.append("<strong>id</strong>=")
+        .append(primitive.getId())
+        .append("<br>");
+        ArrayList<String> keyList = new ArrayList<String>(primitive.getTags().keySet());
+        Collections.sort(keyList);
+        for (int i = 0; i < keyList.size(); i++) {
+            if (i > 0) {
+                sb.append("<br>");
+            }
+            String key = keyList.get(i);
+            sb.append("<strong>")
+            .append(key)
+            .append("</strong>")
+            .append("=");
+            String value = primitive.get(key);
+            while(value.length() != 0) {
+                sb.append(value.substring(0,Math.min(50, value.length())));
+                if (value.length() > 50) {
+                    sb.append("<br>");
+                    value = value.substring(50);
+                } else {
+                    value = "";
+                }
+            }
+        }
+        sb.append("</html>");
+        return sb.toString();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java	(revision 2689)
@@ -0,0 +1,222 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * JosmUserStateManager is a global object which keeps track of what JOSM knows about
+ * the identity of the current user.
+ * 
+ * JOSM can be operated anonymously provided the current user never invokes an operation
+ * on the OSM server which required authentication. In this case JOSM neither knows
+ * the user name of the OSM account of the current user nor its unique id. Perhaps the
+ * user doesn't have one.
+ * 
+ * If the current user supplies a user name and a password in the JOSM preferences JOSM
+ * can partially identify the user.
+ * 
+ * The current user is fully identified if JOSM knows both the user name and the unique
+ * id of the users OSM account. The later is retrieved from the OSM server with a
+ * <tt>GET /api/0.6/user/details</tt> request, submitted with the user name and password
+ * of the current user.
+ * 
+ * The global JosmUserStateManager listens to {@see PreferenceChangeEvent}s and keeps track
+ * of what the current JOSM instance knows about the current user. Other subsystems can
+ * let the global JosmUserStateManager know in case they fully identify the current user, see
+ * {@see #setFullyIdentified(String, long)}.
+ * 
+ * The information kept by the JosmUserStateManager can be used to
+ * <ul>
+ *   <li>safely query changesets owned by the current user based on its user id, not on its user name</li>
+ *   <li>safely search for objects last touched by the current user  based on its user id, not on its user name</li>
+ * </ul>
+ *
+ */
+public class JosmUserIdentityManager implements PreferenceChangedListener{
+
+    static private JosmUserIdentityManager instance;
+
+    /**
+     * Replies the unique instance of teh JOSM user identity manager
+     * 
+     * @return the unique instance of teh JOSM user identity manager
+     */
+    static public JosmUserIdentityManager getInstance() {
+        if (instance == null) {
+            instance = new JosmUserIdentityManager();
+            instance.initFromPreferences();
+            Main.pref.addPreferenceChangeListener(instance);
+        }
+        return instance;
+    }
+
+    private String userName;
+    private UserInfo userInfo;
+
+    private JosmUserIdentityManager() {
+    }
+
+    /**
+     * Remembers the fact that the current JOSM user is anonymous.
+     */
+    public void setAnonymous() {
+        userName = null;
+        userInfo = null;
+    }
+
+    /**
+     * Remebers the fact that the current JOSM user is partially identified
+     * by the user name of its OSM account.
+     * 
+     * @param userName the user name. Must not be null. Must not be empty (whitespace only).
+     * @throws IllegalArgumentException thrown if userName is null
+     * @throws IllegalArgumentException thrown if userName is empty
+     */
+    public void setPartiallyIdentified(String userName) throws IllegalArgumentException {
+        CheckParameterUtil.ensureParameterNotNull(userName, "userName");
+        if (userName.trim().equals(""))
+            throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
+        this.userName = userName;
+        userInfo = null;
+    }
+
+    /**
+     * Remembers the fact that the current JOSM user is fully identified with a
+     * verified pair of user name and user id.
+     * 
+     * @param userName the user name. Must not be null. Must not be empty.
+     * @param userinfo additional information about the user, retrieved from the OSM server and including the user id
+     * @throws IllegalArgumentException thrown if userName is null
+     * @throws IllegalArgumentException thrown if userName is empty
+     * @throws IllegalArgumentException thrown if userinfo is null
+     */
+    public void setFullyIdentified(String username, UserInfo userinfo) throws IllegalArgumentException {
+        CheckParameterUtil.ensureParameterNotNull(username, "username");
+        if (username.trim().equals(""))
+            throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
+        CheckParameterUtil.ensureParameterNotNull(userinfo, "userinfo");
+        this.userName = username;
+        this.userInfo = userinfo;
+    }
+
+    /**
+     * Replies true if the current JOSM user is anonymous.
+     * 
+     * @return true if the current user is anonymous.
+     */
+    public boolean isAnonymous() {
+        return userName == null && userInfo == null;
+    }
+
+    /**
+     * Replies true if the current JOSM user is partially identified.
+     * 
+     * @return true if the current JOSM user is partially identified.
+     */
+    public boolean isPartiallyIdentified() {
+        return userName != null && userInfo == null;
+    }
+
+
+    /**
+     * Replies true if the current JOSM user is fully identified.
+     * 
+     * @return true if the current JOSM user is fully identified.
+     */
+    public boolean isFullyIdentified() {
+        return userName != null && userInfo != null;
+    }
+
+    /**
+     * Replies the user name of the current JOSM user. null, if {@see #isAnonymous()} is true.
+     * 
+     * @return  the user name of the current JOSM user
+     */
+    public String getUserName() {
+        return userName;
+    }
+
+    /**
+     * Replies the user id of the current JOSM user. 0, if {@see #isAnonymous()} or
+     * {@see #isPartiallyIdentified()} is true.
+     * 
+     * @return  the user id of the current JOSM user
+     */
+    public int getUserId() {
+        if (userInfo == null) return 0;
+        return userInfo.getId();
+    }
+
+    /**
+     * Replies verified additional information about the current user if the user is
+     * {@see #isFullyIdentified()}.
+     * 
+     * @return verified additional information about the current user
+     */
+    public UserInfo getUserInfo() {
+        return userInfo;
+    }
+    /**
+     * Initializes the user identity manager from values in the {@see org.openstreetmap.josm.data.Preferences}
+     */
+    public void initFromPreferences() {
+        String userName = Main.pref.get("osm-server.username");
+        if (isAnonymous()) {
+            if (userName != null && ! userName.trim().equals("")) {
+                setPartiallyIdentified(userName);
+            }
+        } else {
+            if (!userName.equals(this.userName)) {
+                setPartiallyIdentified(userName);
+            } else {
+                // same name in the preferences as JOSM already knows about;
+                // keep the state, be it partially or fully identified
+            }
+        }
+    }
+
+    /**
+     * Replies true if the user with name <code>username</code> is the current
+     * user
+     * 
+     * @param username the user name
+     * @return true if the user with name <code>username</code> is the current
+     * user
+     */
+    public boolean isCurrentUser(String username) {
+        if (username == null || this.userName == null) return false;
+        return this.userName.equals(username);
+    }
+
+    /* ------------------------------------------------------------------- */
+    /* interface PreferenceChangeListener                                  */
+    /* ------------------------------------------------------------------- */
+    public void preferenceChanged(PreferenceChangeEvent evt) {
+        if (evt.getKey().equals("osm-server.username")) {
+            String newValue = evt.getNewValue();
+            if (newValue == null || newValue.trim().length() == 0) {
+                setAnonymous();
+            } else {
+                if (! newValue.equals(userName)) {
+                    setPartiallyIdentified(newValue);
+                }
+            }
+            return;
+        }
+
+        if (evt.getKey().equals("osm-server.url")) {
+            String newValue = evt.getNewValue();
+            if (newValue == null || newValue.trim().equals("")) {
+                setAnonymous();
+            } else if (isFullyIdentified()) {
+                setPartiallyIdentified(getUserName());
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 2689)
@@ -21,4 +21,5 @@
 import org.openstreetmap.josm.actions.AlignInLineAction;
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.ChangesetManagerToggleAction;
 import org.openstreetmap.josm.actions.CloseChangesetAction;
 import org.openstreetmap.josm.actions.CombineWayAction;
@@ -251,8 +252,16 @@
         add(viewMenu, new ZoomOutAction());
         viewMenu.addSeparator();
-        for (String mode : AutoScaleAction.modes) {
+        for (String mode : AutoScaleAction.MODES) {
             JosmAction autoScaleAction = new AutoScaleAction(mode);
             add(viewMenu, autoScaleAction);
         }
+
+        // -- changeset manager toggle action
+        ChangesetManagerToggleAction changesetManagerToggleAction = new ChangesetManagerToggleAction();
+        final JCheckBoxMenuItem mi = new JCheckBoxMenuItem(changesetManagerToggleAction);
+        viewMenu.addSeparator();
+        viewMenu.add(mi);
+        mi.setAccelerator(changesetManagerToggleAction.getShortcut().getKeyStroke());
+        changesetManagerToggleAction.addButtonModel(mi.getModel());
 
         // -- fullscreen toggle action
Index: trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java	(revision 2689)
@@ -3,6 +3,4 @@
 
 import java.awt.Component;
-import java.util.ArrayList;
-import java.util.Collections;
 
 import javax.swing.DefaultListCellRenderer;
@@ -15,5 +13,5 @@
 
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -27,4 +25,6 @@
  */
 public class OsmPrimitivRenderer implements ListCellRenderer, TableCellRenderer {
+    private DefaultNameFormatter formatter = new DefaultNameFormatter();
+
     /**
      * Default list cell renderer - delegate for ListCellRenderer operation
@@ -50,5 +50,10 @@
     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
         Component def = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
-        return renderer(def, (OsmPrimitive) value);
+        if (value instanceof OsmPrimitive)
+            return renderer(def, (OsmPrimitive) value);
+        else if (value instanceof HistoryOsmPrimitive)
+            return renderer(def, (HistoryOsmPrimitive) value);
+        else
+            return def;
     }
 
@@ -63,6 +68,6 @@
         if (def != null && value != null && def instanceof JLabel) {
             ((JLabel)def).setText(value.getDisplayName(DefaultNameFormatter.getInstance()));
-            ((JLabel)def).setIcon(ImageProvider.get(OsmPrimitiveType.from(value)));
-            ((JLabel)def).setToolTipText(buildToolTipText(value));
+            ((JLabel)def).setIcon(ImageProvider.get(value.getType()));
+            ((JLabel)def).setToolTipText(formatter.buildDefaultToolTip(value));
         }
         return def;
@@ -70,49 +75,17 @@
 
     /**
-     * build the tool tip text for an {@see OsmPrimitive}. It consist of the formatted
-     * key/value pairs for this primitive.
-     *
-     * @param primitive
-     * @return the tool tip text
+     * Internal method that stuffs information into the rendering component
+     * provided that it's a kind of JLabel.
+     * @param def the rendering component
+     * @param value the HistoryOsmPrimtive to render
+     * @return the modified rendering component
      */
-    public String buildToolTipText(OsmPrimitive primitive) {
-        StringBuilder sb = new StringBuilder();
-
-        sb.append("<html>");
-        // show the id
-        //
-        sb.append("<strong>id</strong>=")
-        .append(primitive.getId())
-        .append("<br>");
-
-        // show the key/value-pairs, sorted by key
-        //
-        ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
-        Collections.sort(keyList);
-        for (int i = 0; i < keyList.size(); i++) {
-            if (i > 0) {
-                sb.append("<br>");
-            }
-            String key = keyList.get(i);
-            sb.append("<strong>")
-            .append(key)
-            .append("</strong>")
-            .append("=");
-            // make sure long values are split into several rows. Otherwise
-            // the tool tip window can become to wide
-            //
-            String value = primitive.get(key);
-            while(value.length() != 0) {
-                sb.append(value.substring(0,Math.min(50, value.length())));
-                if (value.length() > 50) {
-                    sb.append("<br>");
-                    value = value.substring(50);
-                } else {
-                    value = "";
-                }
-            }
+    private Component renderer(Component def, HistoryOsmPrimitive value) {
+        if (def != null && value != null && def instanceof JLabel) {
+            ((JLabel)def).setText(value.getDisplayName(DefaultNameFormatter.getInstance()));
+            ((JLabel)def).setIcon(ImageProvider.get(value.getType()));
+            ((JLabel)def).setToolTipText(formatter.buildDefaultToolTip(value));
         }
-        sb.append("</html>");
-        return sb.toString();
+        return def;
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 2689)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm.gui;
 
+import java.awt.Component;
 import java.awt.EventQueue;
 import java.io.IOException;
@@ -12,4 +13,5 @@
 import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
 import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.xml.sax.SAXException;
 
@@ -46,4 +48,21 @@
     public PleaseWaitRunnable(String title, boolean ignoreException) {
         this(title, new PleaseWaitProgressMonitor(title), ignoreException);
+    }
+
+    /**
+     * Create the runnable object with a given message for the user
+     *
+     * @param parent the parent component for the please wait dialog. Must not be null.
+     * @param title message for the user
+     * @param ignoreException If true, exception will be propagated to calling code. If false then
+     * exception will be thrown directly in EDT. When this runnable is executed using executor framework
+     * then use false unless you read result of task (because exception will get lost if you don't)
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    public PleaseWaitRunnable(Component parent, String title, boolean ignoreException) throws IllegalArgumentException{
+        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+        this.title = title;
+        this.progressMonitor = new PleaseWaitProgressMonitor(parent, title);
+        this.ignoreException = ignoreException;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java	(revision 2689)
@@ -6,4 +6,5 @@
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
+import java.awt.Frame;
 import java.awt.event.ActionEvent;
 import java.awt.event.ItemEvent;
@@ -11,7 +12,10 @@
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import javax.swing.AbstractAction;
@@ -37,11 +41,14 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetHeaderDownloadTask;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
-import org.openstreetmap.josm.gui.dialogs.changeset.DownloadChangesetsTask;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.io.CloseChangesetTask;
+import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.OpenBrowser;
@@ -75,4 +82,5 @@
     private ShowChangesetInfoAction showChangsetInfoAction;
     private CloseOpenChangesetsAction closeChangesetAction;
+    private LaunchChangesetManagerAction launchChangesetManagerAction;
 
 
@@ -93,28 +101,56 @@
         lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
 
-        ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
-        MapView.addLayerChangeListener(inSelectionModel);
-        DataSet.selListeners.add(inSelectionModel);
-
-        ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
-
         DblClickHandler dblClickHandler = new DblClickHandler();
         lstInSelection.addMouseListener(dblClickHandler);
         lstInActiveDataLayer.addMouseListener(dblClickHandler);
 
-        PopupMenuLauncher popupMenuLauncher = new PopupMenuLauncher();
+        ChangesetPopupMenuLauncher popupMenuLauncher = new ChangesetPopupMenuLauncher();
         lstInSelection.addMouseListener(popupMenuLauncher);
         lstInActiveDataLayer.addMouseListener(popupMenuLauncher);
     }
 
+    protected void registerAsListener() {
+        // let the model for changesets in the current selection listen to various
+        // events
+        ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
+        MapView.addEditLayerChangeListener(inSelectionModel);
+        DataSet.selListeners.add(inSelectionModel);
+
+
+        // let the model for changesets in the current layer listen to various
+        // events and bootstrap it's content
+        ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
+        MapView.addEditLayerChangeListener(inActiveDataLayerModel);
+        if (Main.main.getEditLayer() != null) {
+            Main.main.getEditLayer().data.addDataSetListener(inActiveDataLayerModel);
+            inActiveDataLayerModel.initFromDataSet(Main.main.getEditLayer().data);
+            inSelectionModel.initFromPrimitives(Main.main.getEditLayer().data.getSelected());
+        }
+    }
+
+    protected void unregisterAsListener() {
+        // remove the list model for the current edit layer as listener
+        //
+        ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
+        MapView.removeEditLayerChangeListener(inActiveDataLayerModel);
+        if (Main.main.getEditLayer() != null) {
+            Main.main.getEditLayer().data.removeDataSetListener(inActiveDataLayerModel);
+        }
+
+        // remove the list model for the changesets in the current selection as
+        // listener
+        //
+        MapView.removeEditLayerChangeListener(inSelectionModel);
+        DataSet.selListeners.remove(inSelectionModel);
+    }
+
     @Override
     public void tearDown() {
-        ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
-        MapView.removeLayerChangeListener(inSelectionModel);
-        DataSet.selListeners.remove(inSelectionModel);
+        unregisterAsListener();
     }
 
     @Override
     public void showNotify() {
+        registerAsListener();
         DatasetEventManager.getInstance().addDatasetListener(inActiveDataLayerModel, true);
     }
@@ -122,4 +158,5 @@
     @Override
     public void hideNotify() {
+        unregisterAsListener();
         DatasetEventManager.getInstance().removeDatasetListener(inActiveDataLayerModel);
     }
@@ -179,4 +216,11 @@
         lstInActiveDataLayer.getSelectionModel().addListSelectionListener(showChangsetInfoAction);
         lstInSelection.getSelectionModel().addListSelectionListener(showChangsetInfoAction);
+
+        // -- launch changeset manager action
+        launchChangesetManagerAction = new LaunchChangesetManagerAction();
+        tp.add(launchChangesetManagerAction);
+        cbInSelectionOnly.addItemListener(launchChangesetManagerAction);
+        lstInActiveDataLayer.getSelectionModel().addListSelectionListener(launchChangesetManagerAction);
+        lstInSelection.getSelectionModel().addListSelectionListener(launchChangesetManagerAction);
 
         pnl.add(tp);
@@ -334,5 +378,5 @@
             if (sel.isEmpty())
                 return;
-            DownloadChangesetsTask task = new DownloadChangesetsTask(sel);
+            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
             Main.worker.submit(task);
         }
@@ -424,8 +468,101 @@
     }
 
-    class PopupMenuLauncher extends MouseAdapter {
-        protected void showPopup(MouseEvent evt) {
-            if (!evt.isPopupTrigger())
-                return;
+    /**
+     * Show information about the currently selected changesets
+     * 
+     */
+    class LaunchChangesetManagerAction extends AbstractAction implements ListSelectionListener, ItemListener {
+        public LaunchChangesetManagerAction() {
+            putValue(NAME, tr("Details"));
+            putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "changesetmanager"));
+            updateEnabledState();
+        }
+
+        protected void launchChangesetManager(Collection<Integer> toSelect) {
+            ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
+            if (cm.isVisible()) {
+                cm.setExtendedState(Frame.NORMAL);
+                cm.toFront();
+                cm.requestFocus();
+            } else {
+                cm.setVisible(true);
+                cm.toFront();
+                cm.requestFocus();
+            }
+            cm.setSelectedChangesetsById(toSelect);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            ChangesetListModel model = getCurrentChangesetListModel();
+            Set<Integer> sel = model.getSelectedChangesetIds();
+            if (sel.isEmpty())
+                return;
+            final Set<Integer> toDownload = new HashSet<Integer>();
+            ChangesetCache cc = ChangesetCache.getInstance();
+            for (int id: sel) {
+                if (!cc.contains(id)) {
+                    toDownload.add(id);
+                }
+            }
+
+            final ChangesetHeaderDownloadTask task;
+            final Future<?> future;
+            if (toDownload.isEmpty()) {
+                task = null;
+                future = null;
+            } else {
+                task = new ChangesetHeaderDownloadTask(toDownload);
+                future = Main.worker.submit(task);
+            }
+
+            Runnable r = new Runnable() {
+                public void run() {
+                    // first, wait for the download task to finish, if a download
+                    // task was launched
+                    if (future != null) {
+                        try {
+                            future.get();
+                        } catch(InterruptedException e) {
+                            e.printStackTrace();
+                        } catch(ExecutionException e){
+                            e.printStackTrace();
+                            BugReportExceptionHandler.handleException(e.getCause());
+                            return;
+                        }
+                    }
+                    if (task != null) {
+                        if (task.isCanceled())
+                            // don't launch the changeset manager if the download task
+                            // was canceled
+                            return;
+                        if (task.isFailed()) {
+                            toDownload.clear();
+                        }
+                    }
+                    // launch the task
+                    launchChangesetManager(toDownload);
+                }
+            };
+            Main.worker.submit(r);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
+        }
+
+        public void itemStateChanged(ItemEvent arg0) {
+            updateEnabledState();
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+
+    class ChangesetPopupMenuLauncher extends PopupMenuLauncher {
+        @Override
+        public void launch(MouseEvent evt) {
             JList lst = getCurrentChangesetList();
             if (lst.getSelectedIndices().length == 0) {
@@ -438,16 +575,4 @@
             popup.show(lst, evt.getX(), evt.getY());
 
-        }
-        @Override
-        public void mouseClicked(MouseEvent evt) {
-            showPopup(evt);
-        }
-        @Override
-        public void mousePressed(MouseEvent evt) {
-            showPopup(evt);
-        }
-        @Override
-        public void mouseReleased(MouseEvent evt) {
-            showPopup(evt);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java	(revision 2689)
@@ -0,0 +1,638 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog;
+import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryTask;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.io.CloseChangesetTask;
+import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+/**
+ * ChangesetCacheManager manages the local cache of changesets
+ * retrieved from the OSM API. It displays both a table of the locally cached changesets
+ * and detail information about an individual changeset. It also provides actions for
+ * downloading, querying, closing changesets, in addition to removing changesets from
+ * the local cache.
+ * 
+ */
+public class ChangesetCacheManager extends JFrame {
+
+    /** the unique instance of the cache manager  */
+    private static ChangesetCacheManager instance;
+
+    /**
+     * Replies the unique instance of the changeset cache manager
+     * 
+     * @return the unique instance of the changeset cache manager
+     */
+    public static ChangesetCacheManager getInstance() {
+        if (instance == null) {
+            instance = new ChangesetCacheManager();
+        }
+        return instance;
+    }
+
+    /**
+     * Hides and destroys the unique instance of the changeset cache
+     * manager.
+     * 
+     */
+    public static void destroyInstance() {
+        if (instance != null) {
+            instance.setVisible(true);
+            instance.dispose();
+            instance = null;
+        }
+    }
+
+
+    private ChangesetCacheManagerModel model;
+    private JSplitPane spContent;
+    private boolean needsSplitPaneAdjustment;
+
+    private RemoveFromCacheAction actRemoveFromCacheAction;
+    private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction;
+    private DownloadSelectedChangesetsAction actDownloadSelectedChangesets;
+    private DownloadSelectedChangesetContentAction actDownloadSelectedContent;
+    private JTable tblChangesets;
+
+
+    /**
+     * Creates the various models required
+     */
+    protected void buildModel() {
+        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
+        selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        model = new ChangesetCacheManagerModel(selectionModel);
+
+        actRemoveFromCacheAction = new RemoveFromCacheAction();
+        actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction();
+        actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction();
+        actDownloadSelectedContent = new DownloadSelectedChangesetContentAction();
+    }
+
+    /**
+     * builds the toolbar panel in the heading of the dialog
+     * 
+     * @return the toolbar panel
+     */
+    protected JPanel buildToolbarPanel() {
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+        SideButton btn = new SideButton(new QueryAction());
+        pnl.add(btn);
+        pnl.add(new SingleChangesetDownloadPanel());
+        pnl.add(new SideButton(new DownloadMyChangesets()));
+
+        return pnl;
+    }
+
+    /**
+     * builds the button panel in the footer of the dialog
+     * 
+     * @return the button row pane
+     */
+    protected JPanel buildButtonPanel() {
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+        //-- cancel and close action
+        pnl.add(new SideButton(new CancelAction()));
+
+        //-- help action
+        pnl.add(new SideButton(
+                new ContextSensitiveHelpAction(
+                        HelpUtil.ht("/Dialog/ChangesetCacheManager"))
+        )
+        );
+
+        return pnl;
+    }
+
+    /**
+     * Builds the panel with the changeset details
+     * 
+     * @return the panel with the changeset details
+     */
+    protected JPanel buildChangesetDetailPanel() {
+        JPanel pnl = new JPanel(new BorderLayout());
+        JTabbedPane tp = new JTabbedPane();
+
+        // -- add the details panel
+        ChangesetDetailPanel pnlChangesetDetail;
+        tp.add(pnlChangesetDetail = new ChangesetDetailPanel());
+        model.addPropertyChangeListener(pnlChangesetDetail);
+
+        // -- add the tags panel
+        ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel();
+        tp.add(pnlChangesetTags);
+        model.addPropertyChangeListener(pnlChangesetTags);
+
+        // -- add the panel for the changeset content
+        ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel();
+        tp.add(pnlChangesetContent);
+        model.addPropertyChangeListener(pnlChangesetContent);
+
+        tp.setTitleAt(0, tr("Properties"));
+        tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset"));
+        tp.setTitleAt(1, tr("Tags"));
+        tp.setToolTipTextAt(1, tr("Display the the tags of the changeset"));
+        tp.setTitleAt(2, tr("Content"));
+        tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset"));
+
+        pnl.add(tp, BorderLayout.CENTER);
+        return pnl;
+    }
+
+    /**
+     * builds the content panel of the dialog
+     * 
+     * @return the content panel
+     */
+    protected JPanel buildContentPanel() {
+        JPanel pnl = new JPanel(new BorderLayout());
+
+        spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+        spContent.setLeftComponent(buildChangesetTablePanel());
+        spContent.setRightComponent(buildChangesetDetailPanel());
+        spContent.setOneTouchExpandable(true);
+        spContent.setDividerLocation(0.5);
+
+        pnl.add(spContent, BorderLayout.CENTER);
+        return pnl;
+    }
+
+    /**
+     * Builds the table with actions which can be applied to the currently visible changesets
+     * in the changeset table.
+     * 
+     * @return
+     */
+    protected JPanel buildChangesetTableActionPanel() {
+        JPanel pnl = new JPanel(new BorderLayout());
+
+        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+        tb.setFloatable(false);
+
+        // -- remove from cache action
+        model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction);
+        tb.add(actRemoveFromCacheAction);
+
+        // -- close selected changesets action
+        model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction);
+        tb.add(actCloseSelectedChangesetsAction);
+
+        // -- download selected changesets
+        model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets);
+        tb.add(actDownloadSelectedChangesets);
+
+        // -- download the content of the selected changesets
+        model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent);
+        tb.add(actDownloadSelectedContent);
+
+        pnl.add(tb, BorderLayout.CENTER);
+        return pnl;
+    }
+
+    /**
+     * Builds the panel with the table of changesets
+     * 
+     * @return the panel with the table of changesets
+     */
+    protected JPanel buildChangesetTablePanel() {
+        JPanel pnl = new JPanel(new BorderLayout());
+        tblChangesets = new JTable(
+                model,
+                new ChangesetCacheTableColumnModel(),
+                model.getSelectionModel()
+        );
+        tblChangesets.addMouseListener(new ChangesetTablePopupMenuLauncher());
+        tblChangesets.addMouseListener(new DblClickHandler());
+        tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "showDetails");
+        tblChangesets.getActionMap().put("showDetails", new ShowDetailAction());
+        model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer());
+
+        // activate DEL on the table
+        tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "removeFromCache");
+        tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction);
+
+        pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER);
+        pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST);
+        return pnl;
+    }
+
+
+    protected void build() {
+        setTitle(tr(("Changeset Management Dialog")));
+        setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage());
+        Container cp = getContentPane();
+
+        cp.setLayout(new BorderLayout());
+
+        buildModel();
+        cp.add(buildToolbarPanel(), BorderLayout.NORTH);
+        cp.add(buildContentPanel(), BorderLayout.CENTER);
+        cp.add(buildButtonPanel(), BorderLayout.SOUTH);
+
+        // the help context
+        HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetCacheManager"));
+
+        // make the dialog respond to ESC
+        getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancelAndClose");
+        getRootPane().getActionMap().put("cancelAndClose", new CancelAction());
+
+        // install a window event handler
+        addWindowListener(new WindowEventHandler());
+    }
+
+
+    public ChangesetCacheManager() {
+        build();
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            new WindowGeometry(
+                    getClass().getName() + ".geometry",
+                    WindowGeometry.centerInWindow(
+                            getParent(),
+                            new Dimension(1000,600)
+                    )
+            ).apply(this);
+            needsSplitPaneAdjustment = true;
+            model.init();
+
+        } else if (!visible && isShowing()){
+            model.tearDown();
+            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+        }
+        super.setVisible(visible);
+    }
+
+    /**
+     * Handler for window events
+     *
+     */
+    class WindowEventHandler extends WindowAdapter {
+        @Override
+        public void windowClosing(WindowEvent e) {
+            new CancelAction().cancelAndClose();
+        }
+
+        @Override
+        public void windowActivated(WindowEvent arg0) {
+            if (needsSplitPaneAdjustment) {
+                spContent.setDividerLocation(0.5);
+                needsSplitPaneAdjustment = false;
+            }
+        }
+    }
+
+    /**
+     * the cancel / close action
+     */
+    class CancelAction extends AbstractAction {
+        public CancelAction() {
+            putValue(NAME, tr("Close"));
+            putValue(SMALL_ICON, ImageProvider.get("cancel"));
+            putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
+        }
+
+        public void cancelAndClose() {
+            destroyInstance();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            cancelAndClose();
+        }
+    }
+
+    /**
+     * The action to query and download changesets
+     */
+    class QueryAction extends AbstractAction {
+        public QueryAction() {
+            putValue(NAME, tr("Query"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs","search"));
+            putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            ChangesetQueryDialog dialog = new ChangesetQueryDialog(ChangesetCacheManager.this);
+            dialog.initForUserInput();
+            dialog.setVisible(true);
+            if (dialog.isCanceled())
+                return;
+
+            ChangesetQuery query = dialog.getChangesetQuery();
+            if (query == null) return;
+            ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+    }
+
+    /**
+     * Removes the selected changesets from the local changeset cache
+     *
+     */
+    class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener{
+        public RemoveFromCacheAction() {
+            putValue(NAME, tr("Remove from cache"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+            putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            List<Changeset> selected = model.getSelectedChangesets();
+            ChangesetCache.getInstance().remove(selected);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(model.hasSelectedChangesets());
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+
+        }
+    }
+
+    /**
+     * Closes the selected changesets
+     *
+     */
+    class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
+        public CloseSelectedChangesetsAction() {
+            putValue(NAME, tr("Close"));
+            putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
+            putValue(SHORT_DESCRIPTION, tr("Close the selected changesets"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            List<Changeset> selected = model.getSelectedChangesets();
+            Main.worker.submit(new CloseChangesetTask(selected));
+        }
+
+        protected void updateEnabledState() {
+            List<Changeset> selected = model.getSelectedChangesets();
+            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+            for (Changeset cs: selected) {
+                if (cs.isOpen()) {
+                    if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) {
+                        setEnabled(true);
+                        return;
+                    }
+                    if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) {
+                        setEnabled(true);
+                        return;
+                    }
+                }
+            }
+            setEnabled(false);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    /**
+     * Downloads the selected changesets
+     *
+     */
+    class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
+        public DownloadSelectedChangesetsAction() {
+            putValue(NAME, tr("Update changeset"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "updatechangeset"));
+            putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            List<Changeset> selected = model.getSelectedChangesets();
+            ChangesetHeaderDownloadTask task =ChangesetHeaderDownloadTask.buildTaskForChangesets(ChangesetCacheManager.this,selected);
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(model.hasSelectedChangesets());
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    /**
+     * Downloads the content of selected changesets from the OSM server
+     *
+     */
+    class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener{
+        public DownloadSelectedChangesetContentAction() {
+            putValue(NAME, tr("Download changeset content"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+            putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetCacheManager.this,model.getSelectedChangesetIds());
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(model.hasSelectedChangesets());
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    class ShowDetailAction extends AbstractAction {
+
+        public void showDetails() {
+            List<Changeset> selected = model.getSelectedChangesets();
+            if (selected.size() != 1) return;
+            model.setChangesetInDetailView(selected.get(0));
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            showDetails();
+        }
+    }
+
+    class DownloadMyChangesets extends AbstractAction {
+        public DownloadMyChangesets() {
+            putValue(NAME, tr("My changesets"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangeset"));
+            putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)"));
+        }
+
+        protected void alertAnonymousUser() {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetCacheManager.this,
+                    tr("<html>JOSM is currently running with an anonymous user. It can't download<br>"
+                            + "your changesets from the OSM server unless you enter your OSM user name<br>"
+                            + "in the JOSM preferences.</html>"
+                    ),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetCacheManager#CanDownloadMyChangesets")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+            im.initFromPreferences();
+            if (im.isAnonymous()) {
+                alertAnonymousUser();
+                return;
+            }
+            ChangesetQuery query = new ChangesetQuery();
+            if (im.isFullyIdentified()) {
+                query = query.forUser(im.getUserId());
+            } else {
+                query = query.forUser(im.getUserName());
+            }
+            ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+    }
+
+    class DblClickHandler extends MouseAdapter {
+        @Override
+        public void mouseClicked(MouseEvent evt) {
+            if (! SwingUtilities.isLeftMouseButton(evt) || evt.getClickCount()<2)
+                return;
+            new ShowDetailAction().showDetails();
+        }
+    }
+
+    class ChangesetTablePopupMenuLauncher extends PopupMenuLauncher {
+        ChangesetTablePopupMenu menu = new ChangesetTablePopupMenu();
+        @Override
+        public void launch(MouseEvent evt) {
+            if (! model.hasSelectedChangesets()) {
+                int row = tblChangesets.rowAtPoint(evt.getPoint());
+                if (row >= 0) {
+                    model.setSelectedByIdx(row);
+                }
+            }
+            menu.show(tblChangesets, evt.getPoint().x, evt.getPoint().y);
+        }
+    }
+
+    class ChangesetTablePopupMenu extends JPopupMenu {
+        public ChangesetTablePopupMenu() {
+            add(actRemoveFromCacheAction);
+            add(actCloseSelectedChangesetsAction);
+            add(actDownloadSelectedChangesets);
+            add(actDownloadSelectedContent);
+        }
+    }
+
+    class ChangesetDetailViewSynchronizer implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent e) {
+            List<Changeset> selected = model.getSelectedChangesets();
+            if (selected.size() == 1) {
+                model.setChangesetInDetailView(selected.get(0));
+            } else {
+                model.setChangesetInDetailView(null);
+            }
+        }
+    }
+
+    /**
+     * Selects the changesets  in <code>changests</code>, provided the
+     * respective changesets are already present in the local changeset cache.
+     * 
+     * @param ids the collection of changesets. If null, the selection is cleared.
+     */
+    public void setSelectedChangesets(Collection<Changeset> changesets) {
+        model.setSelectedChangesets(changesets);
+        int idx = model.getSelectionModel().getMinSelectionIndex();
+        if (idx < 0) return;
+        tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
+        repaint();
+    }
+
+    /**
+     * Selects the changesets with the ids in <code>ids</code>, provided the
+     * respective changesets are already present in the local changeset cache.
+     * 
+     * @param ids the collection of ids. If null, the selection is cleared.
+     */
+    public void setSelectedChangesetsById(Collection<Integer> ids) {
+        if (ids == null) {
+            setSelectedChangesets(null);
+            return;
+        }
+        Set<Changeset> toSelect = new HashSet<Changeset>();
+        ChangesetCache cc = ChangesetCache.getInstance();
+        for (int id: ids) {
+            if (cc.contains(id)) {
+                toSelect.add(cc.get(id));
+            }
+        }
+        setSelectedChangesets(toSelect);
+    }
+
+    public void runDownloadTask(final ChangesetDownloadTask task) {
+        Main.worker.submit(task);
+        Runnable r = new Runnable() {
+            public void run() {
+                if (task.isCanceled() || task.isFailed()) return;
+                setSelectedChangesets(task.getDownloadedChangesets());
+            }
+        };
+        Main.worker.submit(r);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java	(revision 2689)
@@ -0,0 +1,196 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetCacheEvent;
+import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
+
+/**
+ * This is the model for the changeset cache manager dialog.
+ *
+ */
+public class ChangesetCacheManagerModel extends AbstractTableModel implements ChangesetCacheListener{
+
+    /** the name of the property for the currently selected changeset in the detail view */
+    public final static String CHANGESET_IN_DETAIL_VIEW_PROP = ChangesetCacheManagerModel.class.getName() + ".changesetInDetailView";
+
+    private final ArrayList<Changeset> data = new ArrayList<Changeset>();
+    private DefaultListSelectionModel selectionModel;
+    private Changeset changesetInDetailView;
+    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+    public ChangesetCacheManagerModel(DefaultListSelectionModel selectionModel) {
+        this.selectionModel = selectionModel;
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        support.addPropertyChangeListener(listener);
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        support.removePropertyChangeListener(listener);
+    }
+
+    /**
+     * Sets the changeset currently displayed in the detail view. Fires a property change event
+     * for the property {@see #CHANGESET_IN_DETAIL_VIEW_PROP} if necessary.
+     * 
+     * @param cs the changeset currently displayed in the detail view.
+     */
+    public void setChangesetInDetailView(Changeset cs) {
+        Changeset oldValue = changesetInDetailView;
+        changesetInDetailView = cs;
+        if (oldValue != cs) {
+            support.firePropertyChange(CHANGESET_IN_DETAIL_VIEW_PROP, oldValue, changesetInDetailView);
+        }
+    }
+
+    /**
+     * Replies true if there is at least one selected changeset
+     * 
+     * @return true if there is at least one selected changeset
+     */
+    public boolean hasSelectedChangesets() {
+        return selectionModel.getMinSelectionIndex() >= 0;
+    }
+
+    /**
+     * Replies the list of selected changesets
+     * 
+     * @return the list of selected changesets
+     */
+    public List<Changeset> getSelectedChangesets() {
+        ArrayList<Changeset> ret = new ArrayList<Changeset>();
+        for (int i =0; i< data.size();i++) {
+            Changeset cs = data.get(i);
+            if (selectionModel.isSelectedIndex(i)) {
+                ret.add(cs);
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Replies a set of ids of the selected changesets
+     * 
+     * @return a set of ids of the selected changesets
+     */
+    public Set<Integer> getSelectedChangesetIds() {
+        Set<Integer> ret = new HashSet<Integer>();
+        for (Changeset cs: getSelectedChangesets()) {
+            ret.add(cs.getId());
+        }
+        return ret;
+    }
+
+    /**
+     * Selects the changesets in <code>selected</code>.
+     * 
+     * @param selected the collection of changesets to select. Ignored if empty.
+     */
+    public void setSelectedChangesets(Collection<Changeset> selected) {
+        selectionModel.clearSelection();
+        if (selected == null || selected.isEmpty())
+            return;
+        for (Changeset cs: selected) {
+            int idx = data.indexOf(cs);
+            if (idx >= 0) {
+                selectionModel.addSelectionInterval(idx,idx);
+            }
+        }
+    }
+
+    /**
+     * Selects the changeset displayed at row <code>row</code>
+     * 
+     * @param row the row. Ignored if < 0 or >= {@see #getRowCount()}
+     */
+    public void setSelectedByIdx(int row) {
+        if (row < 0 || row >= getRowCount()) return;
+        selectionModel.setSelectionInterval(row, row);
+    }
+
+    public int getColumnCount() {
+        return 5;
+    }
+
+    public int getRowCount() {
+        return data.size();
+    }
+
+    public Object getValueAt(int row, int column) {
+        return data.get(row);
+    }
+
+    public void init() {
+        ChangesetCache cc = ChangesetCache.getInstance();
+        List<Changeset> selected = getSelectedChangesets();
+        data.clear();
+        data.addAll(cc.getChangesets());
+        sort();
+        fireTableDataChanged();
+        setSelectedChangesets(selected);
+
+        cc.addChangesetCacheListener(this);
+    }
+
+    public void tearDown() {
+        ChangesetCache.getInstance().removeChangesetCacheListener(this);
+    }
+
+    public DefaultListSelectionModel getSelectionModel() {
+        return selectionModel;
+    }
+
+    protected void sort() {
+        Collections.sort(
+                this.data,
+                new Comparator<Changeset>() {
+                    public int compare(Changeset o1, Changeset o2) {
+                        if (o1.getId() < o2.getId()) return 1;
+                        if (o1.getId() == o2.getId()) return 0;
+                        return -1;
+                    }
+                }
+        );
+    }
+
+    /* ------------------------------------------------------------------------------ */
+    /* interface ChangesetCacheListener                                               */
+    /* ------------------------------------------------------------------------------ */
+    public void changesetCacheUpdated(ChangesetCacheEvent event) {
+        List<Changeset> selected = getSelectedChangesets();
+        for (Changeset cs: event.getAddedChangesets()) {
+            data.add(cs);
+        }
+        for (Changeset cs: event.getRemovedChangesets()) {
+            data.remove(cs);
+        }
+        for (Changeset cs: event.getUpdatedChangesets()) {
+            int idx = data.indexOf(cs);
+            if (idx >= 0) {
+                Changeset mine = data.get(idx);
+                if (mine != cs) {
+                    mine.mergeFrom(cs);
+                }
+            }
+        }
+        sort();
+        fireTableDataChanged();
+        setSelectedChangesets(selected);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java	(revision 2689)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+public class ChangesetCacheSortSpecification {
+    static public enum SortCriterium {
+        ID,
+        COMMENT,
+        OPEN,
+        USER,
+        CREATED_AT,
+        CLOSED_AT
+    }
+
+    static public enum SortDirection {
+        ASCENDING,
+        DESCENDING
+    }
+
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java	(revision 2689)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.User;
+
+public class ChangesetCacheTableCellRenderer extends JLabel implements TableCellRenderer{
+
+    public ChangesetCacheTableCellRenderer() {
+        setOpaque(true);
+    }
+
+    protected void reset() {
+        setBackground(UIManager.getColor("Table.background"));
+        setForeground(UIManager.getColor("Table.foreground"));
+        setFont(UIManager.getFont("Table.font"));
+        setToolTipText("");
+    }
+
+    protected void renderColors(boolean isSelected) {
+        if (isSelected) {
+            setBackground(UIManager.getColor("Table.selectionBackground"));
+            setForeground(UIManager.getColor("Table.selectionForeground"));
+        } else {
+            setBackground(UIManager.getColor("Table.background"));
+            setForeground(UIManager.getColor("Table.foreground"));
+        }
+    }
+
+    protected void renderId(Changeset cs) {
+        setText(Integer.toString(cs.getId()));
+        setToolTipText("");
+    }
+
+    protected void renderUploadComment(Changeset cs) {
+        String comment = cs.get("comment");
+        if (comment == null || comment.trim().equals("")) {
+            setText(trc("changeset.upload-comment", "empty"));
+            setFont(UIManager.getFont("Table.font").deriveFont(Font.ITALIC));
+        } else {
+            setText(comment);
+            setToolTipText(comment);
+            setFont(UIManager.getFont("Table.font"));
+        }
+    }
+
+    protected void renderOpen(Changeset cs) {
+        if (cs.isOpen()) {
+            setText(trc("changeset.open", "Open"));
+        } else {
+            setText(trc("changeset.open", "Closed"));
+        }
+        setToolTipText("");
+    }
+
+    protected void renderUser(Changeset cs) {
+        User user = cs.getUser();
+        if (user == null || user.getName().trim().equals("")) {
+            setFont(UIManager.getFont("Table.font").deriveFont(Font.ITALIC));
+            setText(tr("anonymous"));
+        } else {
+            setFont(UIManager.getFont("Table.font"));
+            setText(user.getName());
+            setToolTipText(user.getName());
+        }
+    }
+
+    protected void renderDate(Date d) {
+        if (d == null) {
+            setText("");
+        } else {
+            setText(new SimpleDateFormat().format(d));
+        }
+        setToolTipText("");
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+        reset();
+        renderColors(isSelected);
+        Changeset cs = (Changeset)value;
+        switch(column) {
+        case 0: /* id */ renderId(cs); break;
+        case 1: /* upload comment */ renderUploadComment(cs); break;
+        case 2: /* open/closed */ renderOpen(cs); break;
+        case 3: /* user */ renderUser(cs); break;
+        case 4: /* created at */ renderDate(cs.getCreatedAt()); break;
+        case 5: /* closed at */ renderDate(cs.getClosedAt()); break;
+        }
+        return this;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java	(revision 2689)
@@ -0,0 +1,72 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+/**
+ * The column model for the changeset table
+ * 
+ */
+public class ChangesetCacheTableColumnModel extends DefaultTableColumnModel {
+
+    protected void createColumns() {
+        TableColumn col = null;
+        ChangesetCacheTableCellRenderer renderer = new ChangesetCacheTableCellRenderer();
+
+        // column 0 - Id
+        col = new TableColumn(0);
+        col.setHeaderValue("ID");
+        col.setResizable(true);
+        col.setWidth(20);
+        col.setPreferredWidth(20);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 1 - Upload comment
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Comment"));
+        col.setResizable(true);
+        col.setPreferredWidth(200);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 2 - Open
+        col = new TableColumn(2);
+        col.setHeaderValue(tr("Open"));
+        col.setResizable(true);
+        col.setPreferredWidth(50);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 3 - User
+        col = new TableColumn(3);
+        col.setHeaderValue(tr("User"));
+        col.setResizable(true);
+        col.setPreferredWidth(50);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 4 - Created at
+        col = new TableColumn(4);
+        col.setHeaderValue(tr("Created at"));
+        col.setResizable(true);
+        col.setPreferredWidth(100);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 5 - Closed at
+        col = new TableColumn(4);
+        col.setHeaderValue(tr("Closed at"));
+        col.setResizable(true);
+        col.setPreferredWidth(100);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+    }
+
+    public ChangesetCacheTableColumnModel() {
+        createColumns();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java	(revision 2689)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmTransferCancelledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading the changeset content of a collection of
+ * changesets.
+ *
+ */
+public class ChangesetContentDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+    /** the list of changeset ids to download */
+    private final List<Integer> toDownload = new ArrayList<Integer>();
+    /** true if the task was canceled */
+    private boolean canceled;
+    /** keeps the last exception thrown in the task, if any */
+    private Exception lastException;
+    /** the reader object used to read changesets from the API */
+    private OsmServerChangesetReader reader;
+    /** the set of downloaded changesets */
+    private Set<Changeset> downloadedChangesets;
+
+    /**
+     * Initialize the task with a collection of changeset ids to download
+     * 
+     * @param ids the collection of ids. May be null.
+     */
+    protected void init(Collection<Integer> ids) {
+        if (ids == null) {
+            ids = Collections.emptyList();
+        }
+        for (Integer id: ids) {
+            if (id == null || id <= 0) {
+                continue;
+            }
+            toDownload.add(id);
+        }
+        downloadedChangesets = new HashSet<Changeset>();
+    }
+
+    /**
+     * Creates a download task for a single changeset
+     * 
+     * @param changesetId the changeset id. >0 required.
+     * @throws IllegalArgumentException thrown if changesetId <= 0
+     */
+    public ChangesetContentDownloadTask(int changesetId) throws IllegalArgumentException{
+        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
+        if (changesetId <= 0)
+            throw new IllegalArgumentException(tr("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
+        init(Collections.singleton(changesetId));
+    }
+
+    /**
+     * Creates a download task for a collection of changesets. null values and id <=0 in
+     * the collection are sillently discarded.
+     * 
+     * @param changesetIds the changeset ids. Empty collection assumed, if null.
+     */
+    public ChangesetContentDownloadTask(Collection<Integer> changesetIds) {
+        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
+        init(changesetIds);
+    }
+
+    /**
+     * Creates a download task for a single changeset
+     * 
+     * @param parent the parent component for the {@see PleaseWaitDialog}. Must not be null.
+     * @param changesetId the changeset id. >0 required.
+     * @throws IllegalArgumentException thrown if changesetId <= 0
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    public ChangesetContentDownloadTask(Component parent, int changesetId) throws IllegalArgumentException{
+        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
+        if (changesetId <= 0)
+            throw new IllegalArgumentException(tr("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
+        init(Collections.singleton(changesetId));
+    }
+
+    /**
+     * Creates a download task for a collection of changesets. null values and id <=0 in
+     * the collection are sillently discarded.
+     * 
+     * @param parent the parent component for the {@see PleaseWaitDialog}. Must not be null.
+     * @param changesetIds the changeset ids. Empty collection assumed, if null.
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) throws IllegalArgumentException {
+        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
+        init(changesetIds);
+    }
+
+    /**
+     * Replies true if the local {@see ChangesetCache} already includes the changeset with
+     * id <code>changesetId</code>.
+     * 
+     * @param changsetId the changeset id
+     * @return true if the local {@see ChangesetCache} already includes the changeset with
+     * id <code>changesetId</code>
+     */
+    protected boolean isAvailableLocally(int changsetId) {
+        return ChangesetCache.getInstance().get(changsetId) != null;
+    }
+
+    /**
+     * Downloads the changeset with id <code>changesetId</code> (only "header"
+     * information, no content)
+     * 
+     * @param changesetId the changeset id
+     * @throws OsmTransferException thrown if something went wrong
+     */
+    protected void downloadChangeset(int changesetId) throws OsmTransferException {
+        synchronized(this) {
+            reader = new OsmServerChangesetReader();
+        }
+        Changeset cs = reader.readChangeset(changesetId, getProgressMonitor().createSubTaskMonitor(0, false));
+        synchronized(this) {
+            reader = null;
+        }
+        ChangesetCache.getInstance().update(cs);
+    }
+
+    @Override
+    protected void cancel() {
+        canceled = true;
+        synchronized (this) {
+            if (reader != null) {
+                reader.cancel();
+            }
+        }
+    }
+
+    @Override
+    protected void finish() {
+        if (canceled) return;
+        if (lastException != null) {
+            ExceptionDialogUtil.explainException(lastException);
+        }
+    }
+
+    @Override
+    protected void realRun() throws SAXException, IOException, OsmTransferException {
+        try {
+            getProgressMonitor().setTicksCount(toDownload.size());
+            int i=0;
+            for (int id: toDownload) {
+                i++;
+                if (!isAvailableLocally(id)) {
+                    getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
+                    downloadChangeset(id);
+                }
+                if (canceled) return;
+                synchronized(this) {
+                    reader = new OsmServerChangesetReader();
+                }
+                getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
+                ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
+                synchronized(this) {
+                    reader = null;
+                }
+                Changeset cs = ChangesetCache.getInstance().get(id);
+                cs.setContent(ds);
+                ChangesetCache.getInstance().update(cs);
+                downloadedChangesets.add(cs);
+                getProgressMonitor().worked(1);
+            }
+        } catch(OsmTransferCancelledException e) {
+            // the download was cancelled by the user. This exception is caught if the
+            // user canceled the authentication dialog.
+            //
+            canceled = true;
+            return;
+        } catch(OsmTransferException e) {
+            if (canceled)
+                return;
+            lastException = e;
+        } catch(RuntimeException e) {
+            throw e;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* interface ChangesetDownloadTask                                                 */
+    /* ------------------------------------------------------------------------------- */
+    public Set<Changeset> getDownloadedChangesets() {
+        return downloadedChangesets;
+    }
+
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    public boolean isFailed() {
+        return lastException != null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java	(revision 2689)
@@ -0,0 +1,477 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.history.History;
+import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
+import org.openstreetmap.josm.gui.history.HistoryLoadTask;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * The panel which displays the content of a changeset in a scollable table.
+ *
+ * It listens to property change events for {@see ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
+ * and updates its view accordingly.
+ * 
+ */
+public class ChangesetContentPanel extends JPanel implements PropertyChangeListener{
+
+    private ChangesetContentTableModel model;
+    private Changeset currentChangeset;
+    private JTable tblContent;
+
+    private DonwloadChangesetContentAction actDownloadContentAction;
+    private ShowHistoryAction actShowHistory;
+    private SelectInCurrentLayerAction actSelectInCurrentLayerAction;
+    private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
+
+    private HeaderPanel pnlHeader;
+
+    protected void buildModels() {
+        DefaultListSelectionModel selectionModel =new DefaultListSelectionModel();
+        model = new ChangesetContentTableModel(selectionModel);
+        actDownloadContentAction = new DonwloadChangesetContentAction();
+        actDownloadContentAction.initProperties(currentChangeset);
+        actShowHistory = new ShowHistoryAction();
+        model.getSelectionModel().addListSelectionListener(actShowHistory);
+
+        actSelectInCurrentLayerAction = new SelectInCurrentLayerAction();
+        model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction);
+        MapView.addEditLayerChangeListener(actSelectInCurrentLayerAction);
+
+        actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction();
+        model.getSelectionModel().addListSelectionListener(actZoomInCurrentLayerAction);
+        MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction);
+
+        addComponentListener(
+                new ComponentAdapter() {
+                    @Override
+                    public void componentHidden(ComponentEvent e) {
+                        // make sure the listener is unregistered when the panel becomes
+                        // invisible
+                        MapView.removeEditLayerChangeListener(actSelectInCurrentLayerAction);
+                        MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
+                    }
+                }
+        );
+    }
+
+    protected JPanel buildContentPanel() {
+        JPanel pnl = new JPanel(new BorderLayout());
+        tblContent = new JTable(
+                model,
+                new ChangesetContentTableColumnModel(),
+                model.getSelectionModel()
+        );
+        tblContent.addMouseListener(new ChangesetContentTablePopupMenuLauncher());
+        pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER);
+        return pnl;
+    }
+
+    protected JPanel buildActionButtonPanel() {
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+        tb.setFloatable(false);
+
+        tb.add(actDownloadContentAction);
+        tb.add(actShowHistory);
+        tb.add(actSelectInCurrentLayerAction);
+        tb.add(actZoomInCurrentLayerAction);
+
+        pnl.add(tb);
+        return pnl;
+    }
+
+    protected void build() {
+        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+        setLayout(new BorderLayout());
+        buildModels();
+
+        add(pnlHeader = new HeaderPanel(), BorderLayout.NORTH);
+        add(buildActionButtonPanel(), BorderLayout.WEST);
+        add(buildContentPanel(), BorderLayout.CENTER);
+
+    }
+
+    public ChangesetContentPanel() {
+        build();
+    }
+
+    public ChangesetContentTableModel getModel() {
+        return model;
+    }
+
+    protected void setCurrentChangeset(Changeset cs) {
+        currentChangeset = cs;
+        if (cs == null) {
+            model.populate(null);
+        } else {
+            model.populate(cs.getContent());
+        }
+        actDownloadContentAction.initProperties(cs);
+        pnlHeader.setChangeset(cs);
+    }
+
+    /* ---------------------------------------------------------------------------- */
+    /* interface PropertyChangeListener                                             */
+    /* ---------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if(!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+            return;
+        Changeset cs = (Changeset)evt.getNewValue();
+        setCurrentChangeset(cs);
+    }
+
+
+    /**
+     * Downloads/Updates the content of the changeset
+     *
+     */
+    class DonwloadChangesetContentAction extends AbstractAction{
+        public DonwloadChangesetContentAction() {
+            putValue(NAME, tr("Download content"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+            putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            if (currentChangeset == null) return;
+            ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetContentPanel.this,currentChangeset.getId());
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+
+        public void initProperties(Changeset cs) {
+            if (cs == null) {
+                setEnabled(false);
+                return;
+            } else {
+                setEnabled(true);
+            }
+            if (cs.getContent() == null) {
+                putValue(NAME, tr("Download content"));
+                putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+                putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+            } else {
+                putValue(NAME, tr("Update content"));
+                putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
+                putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
+            }
+        }
+    }
+
+    class ChangesetContentTablePopupMenuLauncher extends MouseAdapter {
+        ChangesetContentTablePopupMenu menu = new ChangesetContentTablePopupMenu();
+
+        protected void launch(MouseEvent evt) {
+            if (! evt.isPopupTrigger()) return;
+            if (! model.hasSelectedPrimitives()) {
+                int row = tblContent.rowAtPoint(evt.getPoint());
+                if (row >= 0) {
+                    model.setSelectedByIdx(row);
+                }
+            }
+            menu.show(tblContent, evt.getPoint().x, evt.getPoint().y);
+        }
+
+        @Override
+        public void mouseClicked(MouseEvent evt) {
+            launch(evt);
+        }
+
+        @Override
+        public void mousePressed(MouseEvent evt) {
+            launch(evt);
+        }
+
+        @Override
+        public void mouseReleased(MouseEvent evt) {
+            launch(evt);
+        }
+    }
+
+    class ChangesetContentTablePopupMenu extends JPopupMenu {
+        public ChangesetContentTablePopupMenu() {
+            add(actDownloadContentAction);
+            add(actShowHistory);
+            add(new JSeparator());
+            add(actSelectInCurrentLayerAction);
+            add(actZoomInCurrentLayerAction);
+        }
+    }
+
+    class ShowHistoryAction extends AbstractAction implements ListSelectionListener{
+        public ShowHistoryAction() {
+            putValue(NAME, tr("Show history"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "history"));
+            putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected primitives"));
+            updateEnabledState();
+        }
+
+        protected List<HistoryOsmPrimitive> filterPrimitivesWithUnloadedHistory(Collection<HistoryOsmPrimitive> primitives) {
+            ArrayList<HistoryOsmPrimitive> ret = new ArrayList<HistoryOsmPrimitive>(primitives.size());
+            for (HistoryOsmPrimitive p: primitives) {
+                if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) {
+                    ret.add(p);
+                }
+            }
+            return ret;
+        }
+
+        public void showHistory(final Collection<HistoryOsmPrimitive> primitives) {
+            List<HistoryOsmPrimitive> toLoad = filterPrimitivesWithUnloadedHistory(primitives);
+            if (!toLoad.isEmpty()) {
+                HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this);
+                for (HistoryOsmPrimitive p: toLoad) {
+                    task.add(p);
+                }
+                Main.worker.submit(task);
+            }
+
+            Runnable r = new Runnable() {
+                public void run() {
+                    try {
+                        for (HistoryOsmPrimitive p : primitives) {
+                            History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId());
+                            if (h == null) {
+                                continue;
+                            }
+                            HistoryBrowserDialogManager.getInstance().show(h);
+                        }
+                    } catch (final Exception e) {
+                        SwingUtilities.invokeLater(new Runnable() {
+                            public void run() {
+                                BugReportExceptionHandler.handleException(e);
+                            }
+                        });
+                    }
+
+                }
+            };
+            Main.worker.submit(r);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(model.hasSelectedPrimitives());
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
+            if (selected.isEmpty()) return;
+            showHistory(selected);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    class SelectInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
+
+        public SelectInCurrentLayerAction() {
+            putValue(NAME, tr("Select in layer"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
+            putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer"));
+            updateEnabledState();
+        }
+
+        protected void alertNoPrimitivesToSelect(Collection<HistoryOsmPrimitive> primitives) {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetContentPanel.this,
+                    trn("<html>The selected object isn''t available in the current<br>"
+                            + "edit layer ''{0}''.</html>",
+                            "<html>None of the selected objects is available in the current<br>"
+                            + "edit layer ''{0}''.</html>",
+                            primitives.size(),
+                            Main.main.getEditLayer().getName()
+                    ),
+                    tr("Nothing to select"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            if (!isEnabled())
+                return;
+            if (Main.main == null || Main.main.getEditLayer() == null) return;
+            OsmDataLayer layer = Main.main.getEditLayer();
+            Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
+            Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
+            for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
+                OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
+                if (op != null) {
+                    target.add(op);
+                }
+            }
+            if (target.isEmpty()) {
+                alertNoPrimitivesToSelect(selected);
+                return;
+            }
+            layer.data.setSelected(target);
+        }
+
+        public void updateEnabledState() {
+            if (Main.main == null || Main.main.getEditLayer() == null){
+                setEnabled(false);
+                return;
+            }
+            setEnabled(model.hasSelectedPrimitives());
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+
+        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+            updateEnabledState();
+        }
+    }
+
+    class ZoomInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
+
+        public ZoomInCurrentLayerAction() {
+            putValue(NAME, tr("Zoom to in layer"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
+            putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding primitives in the current data layer"));
+            updateEnabledState();
+        }
+
+        protected void alertNoPrimitivesToZoomTo(Collection<HistoryOsmPrimitive> primitives) {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetContentPanel.this,
+                    trn("<html>The selected object isn''t available in the current<br>"
+                            + "edit layer ''{0}''.</html>",
+                            "<html>None of the selected objects is available in the current<br>"
+                            + "edit layer ''{0}''.</html>",
+                            primitives.size(),
+                            Main.main.getEditLayer().getName()
+                    ),
+                    tr("Nothing to zoom to"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            if (!isEnabled())
+                return;
+            if (Main.main == null || Main.main.getEditLayer() == null) return;
+            OsmDataLayer layer = Main.main.getEditLayer();
+            Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
+            Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
+            for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
+                OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
+                if (op != null) {
+                    target.add(op);
+                }
+            }
+            if (target.isEmpty()) {
+                alertNoPrimitivesToZoomTo(selected);
+                return;
+            }
+            layer.data.setSelected(target);
+            AutoScaleAction.zoomToSelection();
+        }
+
+        public void updateEnabledState() {
+            if (Main.main == null || Main.main.getEditLayer() == null){
+                setEnabled(false);
+                return;
+            }
+            setEnabled(model.hasSelectedPrimitives());
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+
+        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+            updateEnabledState();
+        }
+    }
+
+    static private class HeaderPanel extends JPanel {
+
+        private JMultilineLabel lblMessage;
+        private Changeset current;
+        private DownloadAction actDownload;
+
+        protected void build() {
+            setLayout(new FlowLayout(FlowLayout.LEFT));
+            lblMessage = new JMultilineLabel(
+                    tr("The content of this changeset is not downloaded yet.")
+            );
+            add(lblMessage);
+            add(new JButton(actDownload = new DownloadAction()));
+
+        }
+
+        public HeaderPanel() {
+            build();
+        }
+
+        public void setChangeset(Changeset cs) {
+            setVisible(cs != null && cs.getContent() == null);
+            this.current = cs;
+        }
+
+        private class DownloadAction extends AbstractAction {
+            public DownloadAction() {
+                putValue(NAME, tr("Download now"));
+                putValue(SHORT_DESCRIPTION, tr("Download the changeset content"));
+                putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+            }
+
+            public void actionPerformed(ActionEvent evt) {
+                if (current == null) return;
+                ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId());
+                ChangesetCacheManager.getInstance().runDownloadTask(task);
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java	(revision 2689)
@@ -0,0 +1,76 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+
+/**
+ * The table cell renderer used in the changeset content table, except for the "name"
+ * column in which we use a {@see OsmPrimitivRenderer}.
+ *
+ */
+public class ChangesetContentTableCellRenderer extends JLabel implements TableCellRenderer{
+
+    public ChangesetContentTableCellRenderer() {
+        setOpaque(true);
+    }
+
+    protected void reset() {
+        setBackground(UIManager.getColor("Table.background"));
+        setForeground(UIManager.getColor("Table.foreground"));
+        setFont(UIManager.getFont("Table.font"));
+    }
+
+    protected void renderColors(boolean isSelected) {
+        if (isSelected) {
+            setBackground(UIManager.getColor("Table.selectionBackground"));
+            setForeground(UIManager.getColor("Table.selectionForeground"));
+        } else {
+            setBackground(UIManager.getColor("Table.background"));
+            setForeground(UIManager.getColor("Table.foreground"));
+        }
+    }
+
+    protected void renderId(HistoryOsmPrimitive primitive) {
+        setText(Long.toString(primitive.getId()));
+        setToolTipText("");
+    }
+
+    protected void renderModificationType(ChangesetModificationType type) {
+        switch(type) {
+        case CREATED: setText(tr("Created")); break;
+        case UPDATED: setText(tr("Updated")); break;
+        case DELETED: setText(tr("Deleted")); break;
+        }
+        setToolTipText("");
+    }
+
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+        reset();
+        renderColors(isSelected);
+        switch(column) {
+        case 0:
+            ChangesetModificationType type = (ChangesetModificationType)value;
+            renderModificationType(type);
+            break;
+        case 1:
+            HistoryOsmPrimitive primitive = (HistoryOsmPrimitive)value;
+            renderId(primitive);
+            break;
+        default:
+            /* do nothing */
+        }
+        return this;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java	(revision 2689)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+
+/**
+ * The column model for the changeset content
+ * 
+ */
+public class ChangesetContentTableColumnModel extends DefaultTableColumnModel {
+
+    protected void createColumns() {
+        TableColumn col = null;
+        ChangesetContentTableCellRenderer renderer = new ChangesetContentTableCellRenderer();
+        // column 0 - type
+        col = new TableColumn(0);
+        col.setHeaderValue("");
+        col.setResizable(true);
+        col.setWidth(50);
+        col.setPreferredWidth(50);
+        col.setMaxWidth(100);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 1 - ID
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("ID"));
+        col.setResizable(true);
+        col.setPreferredWidth(60);
+        col.setMaxWidth(100);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+
+        // column 2 - Name
+        col = new TableColumn(2);
+        col.setHeaderValue(tr("Name"));
+        col.setResizable(true);
+        col.setPreferredWidth(200);
+        col.setCellRenderer(new OsmPrimitivRenderer());
+        addColumn(col);
+    }
+
+    public ChangesetContentTableColumnModel() {
+        createColumns();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java	(revision 2689)
@@ -0,0 +1,164 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.osm.ChangesetDataSet;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetDataSetEntry;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+
+/**
+ * This is the table model for the content of a changeset.
+ *
+ */
+public class ChangesetContentTableModel extends AbstractTableModel {
+
+    private final ArrayList<ChangesetContentEntry> data = new ArrayList<ChangesetContentEntry>();
+    private DefaultListSelectionModel selectionModel;
+
+    public ChangesetContentTableModel(DefaultListSelectionModel selectionModel) {
+        this.selectionModel = selectionModel;
+    }
+
+    /**
+     * Replies true if there is at least one selected primitive in the table model
+     * 
+     * @return true if there is at least one selected primitive in the table model
+     */
+    public boolean hasSelectedPrimitives() {
+        return selectionModel.getMinSelectionIndex() >= 0;
+    }
+
+    public void setSelectedByIdx(int row) {
+        selectionModel.setSelectionInterval(row, row);
+    }
+
+    /**
+     * Replies the selection model
+     * @return the selection model
+     */
+    public DefaultListSelectionModel getSelectionModel() {
+        return selectionModel;
+    }
+
+    public Set<HistoryOsmPrimitive> getSelectedPrimitives() {
+        Set<HistoryOsmPrimitive> ret = new HashSet<HistoryOsmPrimitive>();
+        for (int i=0;i < data.size();i++) {
+            if (selectionModel.isSelectedIndex(i)) {
+                ret.add(data.get(i).getPrimitive());
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Populates the model with the content of a model. If ds is null, the
+     * table is cleared.
+     * 
+     * @param ds the changeset content.
+     */
+    public void populate(ChangesetDataSet ds) {
+        this.data.clear();
+        if (ds == null) {
+            fireTableDataChanged();
+            return;
+        }
+        for (Iterator<ChangesetDataSetEntry> it = ds.iterator(); it.hasNext();) {
+            data.add(new ChangesetContentEntry(it.next()));
+        }
+        sort();
+        fireTableDataChanged();
+    }
+
+    protected void sort() {
+        Collections.sort(
+                data,
+                new Comparator<ChangesetDataSetEntry>() {
+                    public int compare(ChangesetDataSetEntry c1, ChangesetDataSetEntry c2) {
+                        if (c1.getModificationType().equals(c2.getModificationType())) {
+                            long id1 = c1.getPrimitive().getId();
+                            long id2 = c2.getPrimitive().getId();
+
+                            if (id1 == id2)
+                                return 0;
+                            else if (id1 < id2)
+                                return -1;
+                            return 1;
+                        }
+                        switch(c1.getModificationType()) {
+                        case CREATED: return -1;
+                        case UPDATED:
+                            switch(c2.getModificationType()) {
+                            case CREATED: return 1;
+                            default: return -1;
+                            }
+                        case DELETED:
+                            return 1;
+                        }
+                        // should not happen
+                        return 0;
+                    }
+                }
+        );
+    }
+
+    /* -------------------------------------------------------------- */
+    /* interface TableModel                                           */
+    /* -------------------------------------------------------------- */
+    public int getColumnCount() {
+        return 3;
+    }
+
+    public int getRowCount() {
+        return data.size();
+    }
+
+    public Object getValueAt(int row, int col) {
+        switch(col) {
+        case 0: return data.get(row).getModificationType();
+        default: return data.get(row).getPrimitive();
+        }
+    }
+
+    /**
+     * The type used internally to keep information about {@see HistoryOsmPrimitive}
+     * with their {@see ChangesetModificationType}.
+     * 
+     */
+    static private class ChangesetContentEntry implements ChangesetDataSetEntry{
+        private ChangesetModificationType modificationType;
+        private HistoryOsmPrimitive primitive;
+
+        public ChangesetContentEntry(ChangesetModificationType modificationType, HistoryOsmPrimitive primitive) {
+            this.modificationType = modificationType;
+            this.primitive = primitive;
+        }
+
+        public ChangesetContentEntry(ChangesetDataSetEntry entry) {
+            this.modificationType = entry.getModificationType();
+            this.primitive = entry.getPrimitive();
+        }
+
+        public ChangesetModificationType getModificationType() {
+            return modificationType;
+        }
+        public void setModificationType(ChangesetModificationType modificationType) {
+            this.modificationType = modificationType;
+        }
+        public HistoryOsmPrimitive getPrimitive() {
+            return primitive;
+        }
+        public void setPrimitive(HistoryOsmPrimitive primitive) {
+            this.primitive = primitive;
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java	(revision 2689)
@@ -0,0 +1,487 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This panel displays the properties of the currently selected changeset in the
+ * {@see ChangesetCacheManager}.
+ * 
+ */
+public class ChangesetDetailPanel extends JPanel implements PropertyChangeListener{
+
+    private JTextField tfID;
+    private JTextArea taComment;
+    private JTextField tfOpen;
+    private JTextField tfUser;
+    private JTextField tfCreatedOn;
+    private JTextField tfClosedOn;
+    private DonwloadChangesetContentAction actDownloadChangesetContent;
+    private UpdateChangesetAction actUpdateChangesets;
+    private RemoveFromCacheAction actRemoveFromCache;
+    private SelectInCurrentLayerAction actSelectInCurrentLayer;
+    private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
+
+    private Changeset current = null;
+
+    protected JPanel buildActionButtonPanel() {
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+        tb.setFloatable(false);
+
+        // -- remove from cache action
+        tb.add(actRemoveFromCache = new RemoveFromCacheAction());
+        actRemoveFromCache.initProperties(current);
+
+        // -- changeset update
+        tb.add(actUpdateChangesets = new UpdateChangesetAction());
+        actUpdateChangesets.initProperties(current);
+
+        // -- changeset content download
+        tb.add(actDownloadChangesetContent =new DonwloadChangesetContentAction());
+        actDownloadChangesetContent.initProperties(current);
+
+        tb.add(actSelectInCurrentLayer = new SelectInCurrentLayerAction());
+        MapView.addEditLayerChangeListener(actSelectInCurrentLayer);
+
+        tb.add(actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction());
+        MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction);
+
+        addComponentListener(
+                new ComponentAdapter() {
+                    @Override
+                    public void componentHidden(ComponentEvent e) {
+                        // make sure the listener is unregistered when the panel becomes
+                        // invisible
+                        MapView.removeEditLayerChangeListener(actSelectInCurrentLayer);
+                        MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
+                    }
+                }
+        );
+
+        pnl.add(tb);
+        return pnl;
+    }
+
+    protected JPanel buildDetailViewPanel() {
+        JPanel pnl = new JPanel(new GridBagLayout());
+
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        gc.insets = new Insets(0,0,2,3);
+
+        //-- id
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("ID:")), gc);
+
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        gc.gridx = 1;
+        pnl.add(tfID = new JTextField(10), gc);
+        tfID.setEditable(false);
+
+        //-- comment
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("Comment:")), gc);
+
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        gc.gridx = 1;
+        pnl.add(taComment= new JTextArea(5,40), gc);
+        taComment.setEditable(false);
+
+        //-- Open/Closed
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        pnl.add(new JLabel(tr("Open/Closed:")), gc);
+
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.gridx = 1;
+        pnl.add(tfOpen= new JTextField(10), gc);
+        tfOpen.setEditable(false);
+
+        //-- Created by:
+        gc.gridx = 0;
+        gc.gridy = 3;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("Created by:")), gc);
+
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.gridx = 1;
+        pnl.add(tfUser= new JTextField(""), gc);
+        tfUser.setEditable(false);
+
+        //-- Created On:
+        gc.gridx = 0;
+        gc.gridy = 4;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("Created on:")), gc);
+
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.gridx = 1;
+        pnl.add(tfCreatedOn= new JTextField(20), gc);
+        tfCreatedOn.setEditable(false);
+
+        //-- Closed On:
+        gc.gridx = 0;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("Closed on:")), gc);
+
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.gridx = 1;
+        pnl.add(tfClosedOn= new JTextField(20), gc);
+        tfClosedOn.setEditable(false);
+
+        return pnl;
+    }
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
+        add(buildDetailViewPanel(), BorderLayout.CENTER);
+        add(buildActionButtonPanel(), BorderLayout.WEST);
+    }
+
+    protected void clearView() {
+        tfID.setText("");
+        taComment.setText("");
+        tfOpen.setText("");
+        tfUser.setText("");
+        tfCreatedOn.setText("");
+        tfClosedOn.setText("");
+    }
+
+    protected void updateView(Changeset cs) {
+        String msg;
+        if (cs == null) return;
+        tfID.setText(Integer.toString(cs.getId()));
+        String comment = cs.get("comment");
+        taComment.setText(comment == null ? "" : comment);
+
+        if (cs.isOpen()) {
+            msg = trc("changeset.state", "Open");
+        } else {
+            msg = trc("changeset.state", "Closed");
+        }
+        tfOpen.setText(msg);
+
+        if (cs.getUser() == null) {
+            msg = tr("anonymous");
+        } else {
+            msg = cs.getUser().getName();
+        }
+        tfUser.setText(msg);
+        SimpleDateFormat sdf = new SimpleDateFormat();
+
+        tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt()));
+        tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt()));
+    }
+
+    public ChangesetDetailPanel() {
+        build();
+    }
+
+    protected void setCurrentChangeset(Changeset cs) {
+        current = cs;
+        if (cs == null) {
+            clearView();
+        } else {
+            updateView(cs);
+        }
+        actDownloadChangesetContent.initProperties(current);
+        actUpdateChangesets.initProperties(current);
+        actRemoveFromCache.initProperties(current);
+        actSelectInCurrentLayer.updateEnabledState();
+        actZoomInCurrentLayerAction.updateEnabledState();
+    }
+
+    /* ---------------------------------------------------------------------------- */
+    /* interface PropertyChangeListener                                             */
+    /* ---------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (! evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+            return;
+        Changeset cs = (Changeset)evt.getNewValue();
+        setCurrentChangeset(cs);
+    }
+
+    /**
+     * The action for removing the currently selected changeset from the changeset cache
+     */
+    class RemoveFromCacheAction extends AbstractAction {
+        public RemoveFromCacheAction() {
+            putValue(NAME, tr("Remove from cache"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+            putValue(SHORT_DESCRIPTION, tr("Remove the changeset in the detail view panel from the local cache"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            if (current == null)
+                return;
+            ChangesetCache.getInstance().remove(current);
+        }
+
+        public void initProperties(Changeset cs) {
+            setEnabled(cs != null);
+        }
+    }
+
+    /**
+     * Removes the selected changesets from the local changeset cache
+     *
+     */
+    class DonwloadChangesetContentAction extends AbstractAction{
+        public DonwloadChangesetContentAction() {
+            putValue(NAME, tr("Download content"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+            putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            if (current == null) return;
+            ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetDetailPanel.this,current.getId());
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+
+        public void initProperties(Changeset cs) {
+            if (cs == null) {
+                setEnabled(false);
+                return;
+            } else {
+                setEnabled(true);
+            }
+            if (cs.getContent() == null) {
+                putValue(NAME, tr("Download content"));
+                putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+                putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+            } else {
+                putValue(NAME, tr("Update content"));
+                putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
+                putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
+            }
+        }
+    }
+
+
+    /**
+     * Updates the current changeset from the OSM server
+     *
+     */
+    class UpdateChangesetAction extends AbstractAction{
+        public UpdateChangesetAction() {
+            putValue(NAME, tr("Update changeset"));
+            putValue(SMALL_ICON,ImageProvider.get("dialogs/changeset","updatechangeset"));
+            putValue(SHORT_DESCRIPTION, tr("Update the changeset from the OSM server"));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            if (current == null) return;
+            Main.worker.submit(
+                    new ChangesetHeaderDownloadTask(
+                            ChangesetDetailPanel.this,
+                            Collections.singleton(current.getId())
+                    )
+            );
+        }
+
+        public void initProperties(Changeset cs) {
+            if (cs == null) {
+                setEnabled(false);
+                return;
+            } else {
+                setEnabled(true);
+            }
+        }
+    }
+
+    /**
+     * Selects the primitives in the content of this changeset in the the current
+     * data layer.
+     * 
+     */
+    class SelectInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
+
+        public SelectInCurrentLayerAction() {
+            putValue(NAME, tr("Select in layer"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
+            putValue(SHORT_DESCRIPTION, tr("Select the primitives in the content of this changeset in the current data layer"));
+            updateEnabledState();
+        }
+
+        protected void alertNoPrimitivesToSelect(Collection<OsmPrimitive> primitives) {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetDetailPanel.this,
+                    tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
+                            + "edit layer ''{1}''.</html>",
+                            current.getId(),
+                            Main.main.getEditLayer().getName()
+                    ),
+                    tr("Nothing to select"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            if (!isEnabled())
+                return;
+            if (Main.main == null || Main.main.getEditLayer() == null) return;
+            OsmDataLayer layer = Main.main.getEditLayer();
+            Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
+            for (OsmPrimitive p: layer.data.getNodes()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            for (OsmPrimitive p: layer.data.getWays()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            for (OsmPrimitive p: layer.data.getRelations()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            if (target.isEmpty()) {
+                alertNoPrimitivesToSelect(target);
+                return;
+            }
+            layer.data.setSelected(target);
+        }
+
+        public void updateEnabledState() {
+            if (Main.main == null || Main.main.getEditLayer() == null){
+                setEnabled(false);
+                return;
+            }
+            setEnabled(current != null);
+        }
+
+        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+            updateEnabledState();
+        }
+    }
+
+    /**
+     * Zooms to the primitives in the content of this changeset in the the current
+     * data layer.
+     * 
+     */
+    class ZoomInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
+
+        public ZoomInCurrentLayerAction() {
+            putValue(NAME, tr("Zoom to in layer"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
+            putValue(SHORT_DESCRIPTION, tr("Zoom to the primitives in the content of this changeset in the current data layer"));
+            updateEnabledState();
+        }
+
+        protected void alertNoPrimitivesToZoomTo() {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetDetailPanel.this,
+                    tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
+                            + "edit layer ''{1}''.</html>",
+                            current.getId(),
+                            Main.main.getEditLayer().getName()
+                    ),
+                    tr("Nothing to zoom to"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            if (!isEnabled())
+                return;
+            if (Main.main == null || Main.main.getEditLayer() == null) return;
+            OsmDataLayer layer = Main.main.getEditLayer();
+            Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
+            for (OsmPrimitive p: layer.data.getNodes()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            for (OsmPrimitive p: layer.data.getWays()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            for (OsmPrimitive p: layer.data.getRelations()) {
+                if (p.isUsable() && p.getChangesetId() == current.getId()) {
+                    target.add(p);
+                }
+            }
+            if (target.isEmpty()) {
+                alertNoPrimitivesToZoomTo();
+                return;
+            }
+            layer.data.setSelected(target);
+            AutoScaleAction.zoomToSelection();
+        }
+
+        public void updateEnabledState() {
+            if (Main.main == null || Main.main.getEditLayer() == null){
+                setEnabled(false);
+                return;
+            }
+            setEnabled(current != null);
+        }
+
+        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+            updateEnabledState();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java	(revision 2689)
@@ -0,0 +1,12 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.util.Set;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+
+public interface ChangesetDownloadTask extends Runnable{
+    Set<Changeset> getDownloadedChangesets();
+    boolean isCanceled();
+    boolean isFailed();
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java	(revision 2689)
@@ -0,0 +1,206 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading a collection of changests from the OSM
+ * server.
+ * 
+ * The  task only downloads the changeset properties without the changeset content. It
+ * updates the global {@see ChangesetCache}.
+ * 
+ */
+public class ChangesetHeaderDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+    /**
+     * Builds a download task from for a collection of changesets.
+     * 
+     * Ignores null values and changesets with {@see Changeset#isNew()} == true.
+     * 
+     * @param changesets the collection of changesets. Assumes an empty collection if null.
+     * @return the download task
+     */
+    static public ChangesetHeaderDownloadTask buildTaskForChangesets(Collection<Changeset> changesets) {
+        return buildTaskForChangesets(Main.parent, changesets);
+    }
+
+    /**
+     * Builds a download task from for a collection of changesets.
+     * 
+     * Ignores null values and changesets with {@see Changeset#isNew()} == true.
+     * 
+     * @param parent the parent component relative to which the {@see PleaseWaitDialog} is displayed.
+     * Must not be null.
+     * @param changesets the collection of changesets. Assumes an empty collection if null.
+     * @return the download task
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    static public ChangesetHeaderDownloadTask buildTaskForChangesets(Component parent, Collection<Changeset> changesets) {
+        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+        if (changesets == null) {
+            changesets = Collections.emptyList();
+        }
+
+        HashSet<Integer> ids = new HashSet<Integer>();
+        for (Changeset cs: changesets) {
+            if (cs == null || cs.isNew()) {
+                continue;
+            }
+            ids.add(cs.getId());
+        }
+        if (parent == null)
+            return new ChangesetHeaderDownloadTask(ids);
+        else
+            return new ChangesetHeaderDownloadTask(parent, ids);
+
+    }
+
+
+    private Set<Integer> idsToDownload;
+    private OsmServerChangesetReader reader;
+    private boolean canceled;
+    private Exception lastException;
+    private Set<Changeset> downloadedChangesets;
+
+    protected void init(Collection<Integer> ids) {
+        if (ids == null) {
+            ids = Collections.emptyList();
+        }
+        idsToDownload = new HashSet<Integer>();
+        if (ids == null ||  ids.isEmpty())
+            return;
+        for (int id: ids) {
+            if (id <= 0) {
+                continue;
+            }
+            idsToDownload.add(id);
+        }
+    }
+
+    /**
+     * Creates the download task for a collection of changeset ids. Uses a {@see PleaseWaitDialog}
+     * whose parent is {@see Main#parent}.
+     * 
+     * Null ids or or ids <= 0 in the id collection are ignored.
+     * 
+     * @param ids the collection of ids. Empty collection assumed if null.
+     */
+    public ChangesetHeaderDownloadTask(Collection<Integer> ids) {
+        // parent for dialog is Main.parent
+        super(tr("Download changesets"), false /* don't ignore exceptions */);
+        init(ids);
+    }
+
+    /**
+     * Creates the download task for a collection of changeset ids. Uses a {@see PleaseWaitDialog}
+     * whose parent is the parent window of <code>dialogParent</code>.
+     * 
+     * Null ids or or ids <= 0 in the id collection are ignored.
+     * 
+     * @param dialogParent the parent reference component for the {@see PleaseWaitDialog}. Must not be null.
+     * @param ids the collection of ids. Empty collection assumed if null.
+     * @throws IllegalArgumentException thrown if dialogParent is null
+     */
+    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) throws IllegalArgumentException{
+        super(dialogParent,tr("Download changesets"), false /* don't ignore exceptions */);
+        init(ids);
+    }
+
+    @Override
+    protected void cancel() {
+        canceled = true;
+        synchronized (this) {
+            if (reader != null) {
+                reader.cancel();
+            }
+        }
+    }
+
+    @Override
+    protected void finish() {
+        if (canceled)
+            return;
+        if (lastException != null) {
+            ExceptionDialogUtil.explainException(lastException);
+        }
+        Runnable r = new Runnable() {
+            public void run() {
+                ChangesetCache.getInstance().update(downloadedChangesets);
+            }
+        };
+
+        if (SwingUtilities.isEventDispatchThread()) {
+            r.run();
+        } else {
+            try {
+                SwingUtilities.invokeAndWait(r);
+            } catch(InterruptedException e) {
+                e.printStackTrace();
+            } catch(InvocationTargetException e) {
+                Throwable t = e.getTargetException();
+                if (t instanceof RuntimeException) {
+                    BugReportExceptionHandler.handleException(t);
+                } else if (t instanceof Exception){
+                    ExceptionUtil.explainException(e);
+                } else {
+                    BugReportExceptionHandler.handleException(t);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void realRun() throws SAXException, IOException, OsmTransferException {
+        try {
+            synchronized (this) {
+                reader = new OsmServerChangesetReader();
+            }
+            downloadedChangesets = new HashSet<Changeset>();
+            downloadedChangesets.addAll(reader.readChangesets(idsToDownload, getProgressMonitor().createSubTaskMonitor(0, false)));
+        } catch(OsmTransferException e) {
+            if (canceled)
+                // ignore exception if cancelled
+                return;
+            // remember other exceptions
+            lastException = e;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* interface ChangesetDownloadTask                                                 */
+    /* ------------------------------------------------------------------------------- */
+    public Set<Changeset> getDownloadedChangesets() {
+        return downloadedChangesets;
+    }
+
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    public boolean isFailed() {
+        return lastException != null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java	(revision 2689)
@@ -8,9 +8,8 @@
 import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 
-public class ChangesetInSelectionListModel extends ChangesetListModel implements SelectionChangedListener, MapView.LayerChangeListener{
+public class ChangesetInSelectionListModel extends ChangesetListModel implements SelectionChangedListener, EditLayerChangeListener{
 
     public ChangesetInSelectionListModel(DefaultListSelectionModel selectionModel) {
@@ -24,15 +23,14 @@
     }
 
+
     /* ---------------------------------------------------------------------------- */
     /* Interface LayerChangeListener                                                */
     /* ---------------------------------------------------------------------------- */
-    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
-        if (newLayer == null || ! (newLayer instanceof OsmDataLayer)) {
+    public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+        if (newLayer == null) {
             setChangesets(null);
         } else {
-            initFromPrimitives(((OsmDataLayer) newLayer).data.getSelected());
+            initFromPrimitives((newLayer).data.getSelected());
         }
     }
-    public void layerAdded(Layer newLayer) {}
-    public void layerRemoved(Layer oldLayer) {}
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java	(revision 2689)
@@ -0,0 +1,58 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.TagTable;
+
+/**
+ * This panel displays the tags of the currently selected changeset in the {@see ChangesetCacheManager}
+ * 
+ */
+public class ChangesetTagsPanel extends JPanel implements PropertyChangeListener{
+
+    private TagTable tblTags;
+    private TagEditorModel model;
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+        tblTags = new TagTable(model = new TagEditorModel());
+        tblTags.setEnabled(false);
+        add(new JScrollPane(tblTags), BorderLayout.CENTER);
+    }
+
+    public ChangesetTagsPanel() {
+        build();
+    }
+
+    protected void init(Changeset cs) {
+        model.clear();
+        if (cs == null)
+            return;
+        model.initFromTags(cs.getKeys());
+    }
+
+    /* ---------------------------------------------------------------------------- */
+    /* interface PropertyChangeListener                                             */
+    /* ---------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+            return;
+        Changeset cs = (Changeset)evt.getNewValue();
+        if (cs == null) {
+            model.clear();
+        } else {
+            model.initFromPrimitive(cs);
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java	(revision 2689)
@@ -16,6 +16,12 @@
 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 
-public class ChangesetsInActiveDataLayerListModel extends ChangesetListModel implements DataSetListener  {
+/**
+ * This is the list model for the list of changeset in the current edit layer.
+ * 
+ */
+public class ChangesetsInActiveDataLayerListModel extends ChangesetListModel implements DataSetListener, EditLayerChangeListener {
 
     public ChangesetsInActiveDataLayerListModel(DefaultListSelectionModel selectionModel) {
@@ -23,9 +29,10 @@
     }
 
+    /* ------------------------------------------------------------------------------ */
+    /* interface DataSetListener                                                      */
+    /* ------------------------------------------------------------------------------ */
     public void dataChanged(DataChangedEvent event) {
-        initFromPrimitives(event.getPrimitives());
+        initFromDataSet(event.getDataset());
     }
-
-    public void nodeMoved(NodeMovedEvent event) {/* ignored */}
 
     public void primtivesAdded(PrimitivesAddedEvent event) {
@@ -41,8 +48,4 @@
     }
 
-    public void relationMembersChanged(RelationMembersChangedEvent event) {/* ignored */}
-
-    public void tagsChanged(TagsChangedEvent event) {/* ignored */}
-
     public void otherDatasetChange(AbstractDatasetChangedEvent event) {
         if (event instanceof ChangesetIdChangedEvent) {
@@ -53,5 +56,24 @@
     }
 
+    public void nodeMoved(NodeMovedEvent event) {/* ignored */}
+
+    public void relationMembersChanged(RelationMembersChangedEvent event) {/* ignored */}
+
+    public void tagsChanged(TagsChangedEvent event) {/* ignored */}
+
     public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
 
+    /* ------------------------------------------------------------------------------ */
+    /* interface EditLayerListener                                                    */
+    /* ------------------------------------------------------------------------------ */
+    public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+        // just init the model content. Don't register as DataSetListener. The mode
+        // is already registered to receive DataChangedEvents from the current
+        // edit layer
+        if (newLayer != null) {
+            initFromDataSet(newLayer.data);
+        } else {
+            initFromDataSet(null);
+        }
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetsTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetsTask.java	(revision 2688)
+++ 	(revision )
@@ -1,89 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.data.osm.Changeset;
-import org.openstreetmap.josm.data.osm.ChangesetCache;
-import org.openstreetmap.josm.gui.ExceptionDialogUtil;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.io.OsmServerChangesetReader;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.xml.sax.SAXException;
-
-public class DownloadChangesetsTask extends PleaseWaitRunnable{
-
-    private Set<Integer> idsToDownload;
-    private OsmServerChangesetReader reader;
-    private boolean cancelled;
-    private Exception lastException;
-    private List<Changeset> downloadedChangesets;
-
-    public DownloadChangesetsTask(Collection<Integer> ids) {
-        super(tr("Download changesets"));
-        idsToDownload = new HashSet<Integer>();
-        if (ids == null ||  ids.isEmpty())
-            return;
-        for (int id: ids) {
-            if (id <= 0) {
-                continue;
-            }
-            idsToDownload.add(id);
-        }
-    }
-
-    @Override
-    protected void cancel() {
-        cancelled = true;
-        synchronized (this) {
-            if (reader != null) {
-                reader.cancel();
-            }
-        }
-    }
-
-    @Override
-    protected void finish() {
-        if (cancelled)
-            return;
-        if (lastException != null) {
-            ExceptionDialogUtil.explainException(lastException);
-        }
-        Runnable r = new Runnable() {
-            public void run() {
-                ChangesetCache.getInstance().update(downloadedChangesets);
-            }
-        };
-
-        if (SwingUtilities.isEventDispatchThread()) {
-            r.run();
-        } else {
-            SwingUtilities.invokeLater(r);
-        }
-    }
-
-    @Override
-    protected void realRun() throws SAXException, IOException, OsmTransferException {
-        try {
-            synchronized (this) {
-                reader = new OsmServerChangesetReader();
-            }
-            downloadedChangesets = reader.readChangesets(idsToDownload, getProgressMonitor().createSubTaskMonitor(0, false));
-        } catch(Exception e) {
-            if (cancelled)
-                // ignore exception if cancelled
-                return;
-            if (e instanceof RuntimeException)
-                throw (RuntimeException)e;
-            lastException = e;
-        }
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java	(revision 2689)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This panel allows to enter the ID of single changeset and to download
+ * the respective changeset.
+ * 
+ */
+public class SingleChangesetDownloadPanel extends JPanel {
+
+    private JTextField tfChangsetId;
+    private DownloadAction actDownload;
+    private ChangesetIdValidator valChangesetId;
+
+    protected void build() {
+        setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
+        setBorder(
+                BorderFactory.createCompoundBorder(
+                        BorderFactory.createLineBorder(Color.GRAY),
+                        BorderFactory.createEmptyBorder(0,3,0,3)
+                )
+        );
+
+        add(new JLabel(tr("Changeset ID: ")));
+        add(tfChangsetId = new JTextField(10));
+        tfChangsetId.setToolTipText(tr("Enter a changset id"));
+        valChangesetId  =ChangesetIdValidator.decorate(tfChangsetId);
+        SelectAllOnFocusGainedDecorator.decorate(tfChangsetId);
+
+        actDownload = new DownloadAction();
+        SideButton btn = new SideButton(actDownload);
+        tfChangsetId.addActionListener(actDownload);
+        tfChangsetId.getDocument().addDocumentListener(actDownload);
+        add(btn);
+    }
+
+    public SingleChangesetDownloadPanel() {
+        build();
+    }
+
+    /**
+     * Replies the changeset id entered in this panel. 0 if no changeset id
+     * or an invalid changeset id is currently entered.
+     * 
+     * @return the changeset id entered in this panel
+     */
+    public int getChangsetId() {
+        return valChangesetId.getChangesetId();
+    }
+
+    /**
+     * Downloads the single changeset from the OSM API
+     */
+    class DownloadAction extends AbstractAction implements DocumentListener{
+
+        public DownloadAction() {
+            putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+            putValue(SHORT_DESCRIPTION, tr("Download the changeset with the specified id, including the changeset content"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            if (!isEnabled())
+                return;
+            int id = getChangsetId();
+            if (id == 0) return;
+            ChangesetContentDownloadTask task =  new ChangesetContentDownloadTask(
+                    SingleChangesetDownloadPanel.this,
+                    Collections.singleton(id)
+            );
+            ChangesetCacheManager.getInstance().runDownloadTask(task);
+        }
+
+        protected void updateEnabledState() {
+            String v = tfChangsetId.getText();
+            if (v == null || v.trim().length() == 0) {
+                setEnabled(false);
+                return;
+            }
+            setEnabled(valChangesetId.isValid());
+        }
+
+        public void changedUpdate(DocumentEvent arg0) {
+            updateEnabledState();
+        }
+
+        public void insertUpdate(DocumentEvent arg0) {
+            updateEnabledState();
+        }
+
+        public void removeUpdate(DocumentEvent arg0) {
+            updateEnabledState();
+        }
+    }
+
+    /**
+     * Validator for a changeset ID entered in a {@see JTextComponent}.
+     * 
+     */
+    static private class ChangesetIdValidator extends AbstractTextComponentValidator {
+        static public ChangesetIdValidator decorate(JTextComponent tc) {
+            return new ChangesetIdValidator(tc);
+        }
+
+        public ChangesetIdValidator(JTextComponent tc) {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            String value  = getComponent().getText();
+            if (value == null || value.trim().length() == 0)
+                return true;
+            return getChangesetId() > 0;
+        }
+
+        @Override
+        public void validate() {
+            if (!isValid()) {
+                feedbackInvalid(tr("The current value isn't a valid changeset ID. Please enter an integer value > 0"));
+            } else {
+                feedbackValid(tr("Please enter an integer value > 0"));
+            }
+        }
+
+        public int getChangesetId() {
+            String value  = getComponent().getText();
+            if (value == null || value.trim().length() == 0) return 0;
+            try {
+                int changesetId = Integer.parseInt(value.trim());
+                if (changesetId > 0) return changesetId;
+                return 0;
+            } catch(NumberFormatException e) {
+                return 0;
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java	(revision 2689)
@@ -0,0 +1,1204 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.Scrollable;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel;
+import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * This panel allows to specify a changeset query
+ * 
+ */
+public class AdvancedChangesetQueryPanel extends JPanel {
+
+    private JCheckBox cbUserRestriction;
+    private JCheckBox cbOpenAndCloseRestrictions;
+    private JCheckBox cbTimeRestrictions;
+    private JCheckBox cbBoundingBoxRestriction;
+    private UserRestrictionPanel pnlUserRestriction;
+    private OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction;
+    private TimeRestrictionPanel pnlTimeRestriction;
+    private BBoxRestrictionPanel pnlBoundingBoxRestriction;
+
+    protected JPanel buildQueryPanel() {
+        ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler();
+        JPanel pnl  = new QuerySpecificationPanel();
+        pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+        pnl.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        // -- select changesets by a specific user
+        //
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(cbUserRestriction = new JCheckBox(), gc);
+        cbUserRestriction.addItemListener(stateChangeHandler);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")),gc);
+
+        gc.gridy = 1;
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(pnlUserRestriction = new UserRestrictionPanel(), gc);
+
+        // -- restricting the query to open and closed changesets
+        //
+        gc.gridy = 2;
+        gc.gridx = 0;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(cbOpenAndCloseRestrictions = new JCheckBox(), gc);
+        cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")),gc);
+
+        gc.gridy = 3;
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(pnlOpenAndCloseRestriction = new OpenAndCloseStateRestrictionPanel(), gc);
+
+        // -- restricting the query to a specific time
+        //
+        gc.gridy = 4;
+        gc.gridx = 0;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(cbTimeRestrictions = new JCheckBox(), gc);
+        cbTimeRestrictions.addItemListener(stateChangeHandler);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(new JMultilineLabel(tr("Select changesets based on the date/time they have been created or closed")),gc);
+
+        gc.gridy = 5;
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(pnlTimeRestriction = new TimeRestrictionPanel(), gc);
+
+
+        // -- restricting the query to a specific bounding box
+        //
+        gc.gridy = 6;
+        gc.gridx = 0;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(cbBoundingBoxRestriction = new JCheckBox(), gc);
+        cbBoundingBoxRestriction.addItemListener(stateChangeHandler);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(new JMultilineLabel(tr("Select only changesets related to a specific bounding box")),gc);
+
+        gc.gridy = 7;
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        pnl.add(pnlBoundingBoxRestriction = new BBoxRestrictionPanel(), gc);
+
+
+        gc.gridy = 8;
+        gc.gridx = 0;
+        gc.gridwidth = 2;
+        gc.fill  =GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        pnl.add(new JPanel(), gc);
+
+        return pnl;
+    }
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        JScrollPane spQueryPanel = new JScrollPane(buildQueryPanel());
+        add(spQueryPanel, BorderLayout.CENTER);
+        spQueryPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        spQueryPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+    }
+
+    public AdvancedChangesetQueryPanel() {
+        build();
+    }
+
+    public void startUserInput() {
+        restoreFromSettings();
+        pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
+        pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
+        pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
+        pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
+        pnlOpenAndCloseRestriction.startUserInput();
+        pnlUserRestriction.startUserInput();
+        pnlTimeRestriction.startUserInput();
+    }
+
+    public void displayMessageIfInvalid() {
+        if (cbUserRestriction.isSelected()) {
+            if (! pnlUserRestriction.isValidChangesetQuery()) {
+                pnlUserRestriction.displayMessageIfInvalid();
+            }
+        } else if (cbTimeRestrictions.isSelected()) {
+            if (!pnlTimeRestriction.isValidChangesetQuery()) {
+                pnlTimeRestriction.displayMessageIfInvalid();
+            }
+        } else if (cbBoundingBoxRestriction.isSelected()) {
+            if (!pnlBoundingBoxRestriction.isValidChangesetQuery()) {
+                pnlBoundingBoxRestriction.displayMessageIfInvalid();
+            }
+        }
+    }
+
+    /**
+     * Builds the changeset query based on the data entered in the form.
+     * 
+     * @return the changeset query. null, if the data entered doesn't represent
+     * a valid changeset query.
+     */
+    public ChangesetQuery buildChangesetQuery() {
+        ChangesetQuery query = new ChangesetQuery();
+        if (cbUserRestriction.isSelected()) {
+            if (! pnlUserRestriction.isValidChangesetQuery())
+                return null;
+            pnlUserRestriction.fillInQuery(query);
+        }
+        if (cbOpenAndCloseRestrictions.isSelected()) {
+            // don't have to check whether it's valid. It always is.
+            pnlOpenAndCloseRestriction.fillInQuery(query);
+        }
+        if (cbBoundingBoxRestriction.isSelected()) {
+            if (!pnlBoundingBoxRestriction.isValidChangesetQuery())
+                return null;
+            pnlBoundingBoxRestriction.fillInQuery(query);
+        }
+        if (cbTimeRestrictions.isSelected()) {
+            if (!pnlTimeRestriction.isValidChangesetQuery())
+                return null;
+            pnlTimeRestriction.fillInQuery(query);
+        }
+        return query;
+    }
+
+    public void rememberSettings() {
+        Main.pref.put("changeset-query.advanced.user-restrictions", cbUserRestriction.isSelected());
+        Main.pref.put("changeset-query.advanced.open-restrictions", cbOpenAndCloseRestrictions.isSelected());
+        Main.pref.put("changeset-query.advanced.time-restrictions", cbTimeRestrictions.isSelected());
+        Main.pref.put("changeset-query.advanced.bbox-restrictions", cbBoundingBoxRestriction.isSelected());
+
+        pnlUserRestriction.rememberSettings();
+        pnlOpenAndCloseRestriction.rememberSettings();
+        pnlTimeRestriction.rememberSettings();
+    }
+
+    public void restoreFromSettings() {
+        cbUserRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.user-restrictions", false));
+        cbOpenAndCloseRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.open-restrictions", false));
+        cbTimeRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.time-restrictions", false));
+        cbBoundingBoxRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.bbox-restrictions", false));
+    }
+
+    class RestrictionGroupStateChangeHandler implements ItemListener {
+        protected void userRestrictionStateChanged() {
+            if (pnlUserRestriction == null) return;
+            pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
+        }
+
+        protected void openCloseRestrictionStateChanged() {
+            if (pnlOpenAndCloseRestriction == null) return;
+            pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
+        }
+
+        protected void timeRestrictionsStateChanged() {
+            if (pnlTimeRestriction == null) return;
+            pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
+        }
+
+        protected void boundingBoxRestrictionChanged() {
+            if (pnlBoundingBoxRestriction == null) return;
+            pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            if (e.getSource() == cbUserRestriction) {
+                userRestrictionStateChanged();
+            } else if (e.getSource() == cbOpenAndCloseRestrictions) {
+                openCloseRestrictionStateChanged();
+            } else if (e.getSource() == cbTimeRestrictions) {
+                timeRestrictionsStateChanged();
+            } else if (e.getSource() == cbBoundingBoxRestriction) {
+                boundingBoxRestrictionChanged();
+            }
+            validate();
+            repaint();
+        }
+    }
+
+    /**
+     * This is the panel for selecting whether the changeset query should be restricted to
+     * open or closed changesets
+     */
+    static private class OpenAndCloseStateRestrictionPanel extends JPanel {
+
+        private JRadioButton rbOpenOnly;
+        private JRadioButton rbClosedOnly;
+        private JRadioButton rbBoth;
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createEmptyBorder(3,3,3,3),
+                    BorderFactory.createCompoundBorder(
+                            BorderFactory.createLineBorder(Color.GRAY),
+                            BorderFactory.createEmptyBorder(5,5,5,5)
+                    )
+            ));
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbOpenOnly = new JRadioButton(), gc);
+
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Query open changesets only")), gc);
+
+            gc.gridy = 1;
+            gc.gridx = 0;
+            gc.weightx = 0.0;
+            add(rbClosedOnly = new JRadioButton(), gc);
+
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Query closed changesets only")), gc);
+
+            gc.gridy = 2;
+            gc.gridx = 0;
+            gc.weightx = 0.0;
+            add(rbBoth = new JRadioButton(), gc);
+
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Query both open and closed changesets")), gc);
+
+            ButtonGroup bgRestrictions = new ButtonGroup();
+            bgRestrictions.add(rbBoth);
+            bgRestrictions.add(rbClosedOnly);
+            bgRestrictions.add(rbOpenOnly);
+        }
+
+        public OpenAndCloseStateRestrictionPanel() {
+            build();
+        }
+
+        public void startUserInput() {
+            restoreFromSettings();
+        }
+
+        public void fillInQuery(ChangesetQuery query) {
+            if (rbBoth.isSelected()) {
+                query.beingClosed(true);
+                query.beingOpen(true);
+            } else if (rbOpenOnly.isSelected()) {
+                query.beingOpen(true);
+            } else if (rbClosedOnly.isSelected()) {
+                query.beingClosed(true);
+            }
+        }
+
+        public void rememberSettings() {
+            String prefRoot = "changeset-query.advanced.open-restrictions";
+            if (rbBoth.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "both");
+            } else if (rbOpenOnly.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "open");
+            } else if (rbClosedOnly.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "closed");
+            }
+        }
+
+        public void restoreFromSettings() {
+            String prefRoot = "changeset-query.advanced.open-restrictions";
+            String v = Main.pref.get(prefRoot + ".query-type", "open");
+            rbBoth.setSelected(v.equals("both"));
+            rbOpenOnly.setSelected(v.equals("open"));
+            rbClosedOnly.setSelected(v.equals("closed"));
+        }
+    }
+
+    /**
+     * This is the panel for selecting whether the query should be restricted to a specific
+     * user
+     * 
+     */
+    static private class UserRestrictionPanel extends JPanel {
+        private ButtonGroup bgUserRestrictions;
+        private JRadioButton rbRestrictToMyself;
+        private JRadioButton rbRestrictToUid;
+        private JRadioButton rbRestrictToUserName;
+        private JTextField tfUid;
+        private UidInputFieldValidator valUid;
+        private JTextField tfUserName;
+        private UserNameInputValidator valUserName;
+        private JMultilineLabel lblRestrictedToMyself;
+
+        protected JPanel buildUidInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("User ID: ")), gc);
+
+            gc.gridx = 1;
+            pnl.add(tfUid = new JTextField(10),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfUid);
+            valUid = UidInputFieldValidator.decorate(tfUid);
+
+            // grab remaining space
+            gc.gridx = 2;
+            gc.weightx = 1.0;
+            pnl.add(new JPanel(), gc);
+            return pnl;
+        }
+
+        protected JPanel buildUserNameInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("User name: ")), gc);
+
+            gc.gridx = 1;
+            pnl.add(tfUserName = new JTextField(10),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfUserName);
+            valUserName = UserNameInputValidator.decorate(tfUserName);
+
+            // grab remaining space
+            gc.gridx = 2;
+            gc.weightx = 1.0;
+            pnl.add(new JPanel(), gc);
+            return pnl;
+        }
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createEmptyBorder(3,3,3,3),
+                    BorderFactory.createCompoundBorder(
+                            BorderFactory.createLineBorder(Color.GRAY),
+                            BorderFactory.createEmptyBorder(5,5,5,5)
+                    )
+            ));
+
+            ItemListener userRestrictionChangeHandler = new UserRestrictionChangedHandler();
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.gridx = 0;
+            gc.fill= GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbRestrictToMyself = new JRadioButton(), gc);
+            rbRestrictToMyself.addItemListener(userRestrictionChangeHandler);
+
+            gc.gridx = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(lblRestrictedToMyself = new JMultilineLabel(tr("Only changesets owned by myself")), gc);
+
+            gc.gridx = 0;
+            gc.gridy = 1;
+            gc.fill= GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbRestrictToUid = new JRadioButton(), gc);
+            rbRestrictToUid.addItemListener(userRestrictionChangeHandler);
+
+            gc.gridx = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Only changesets owned by the user with the following user ID")),gc);
+
+            gc.gridx = 1;
+            gc.gridy = 2;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(buildUidInputPanel(),gc);
+
+            gc.gridx = 0;
+            gc.gridy = 3;
+            gc.fill= GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbRestrictToUserName = new JRadioButton(), gc);
+            rbRestrictToUserName.addItemListener(userRestrictionChangeHandler);
+
+            gc.gridx = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Only changesets owned by the user with the following user name")),gc);
+
+            gc.gridx = 1;
+            gc.gridy = 4;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(buildUserNameInputPanel(),gc);
+
+            bgUserRestrictions = new ButtonGroup();
+            bgUserRestrictions.add(rbRestrictToMyself);
+            bgUserRestrictions.add(rbRestrictToUid);
+            bgUserRestrictions.add(rbRestrictToUserName);
+        }
+
+        public UserRestrictionPanel() {
+            build();
+        }
+
+        public void startUserInput() {
+            if (JosmUserIdentityManager.getInstance().isAnonymous()) {
+                lblRestrictedToMyself.setText("Only changesets owned by myself (disabled. JOSM is currently run by an anonymous user)");
+                rbRestrictToMyself.setEnabled(false);
+                if (rbRestrictToMyself.isSelected()) {
+                    rbRestrictToUid.setSelected(true);
+                }
+            } else {
+                lblRestrictedToMyself.setText("Only changesets owned by myself");
+                rbRestrictToMyself.setEnabled(true);
+                rbRestrictToMyself.setSelected(true);
+            }
+            restoreFromSettings();
+        }
+
+        /**
+         * Sets the query restrictions on <code>query</code> for changeset owner based
+         * restrictions.
+         * 
+         * @param query the query. Must not be null.
+         * @throws IllegalArgumentException thrown if query is null
+         * @throws IllegalStateException thrown if one of the available values for query parameters in
+         * this panel isn't valid
+         * 
+         */
+        public void fillInQuery(ChangesetQuery query) throws IllegalStateException, IllegalArgumentException  {
+            CheckParameterUtil.ensureParameterNotNull(query, "query");
+            if (rbRestrictToMyself.isSelected()) {
+                JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+                if (im.isPartiallyIdentified()) {
+                    query.forUser(im.getUserName());
+                } else if (im.isFullyIdentified()) {
+                    query.forUser(im.getUserId());
+                } else
+                    throw new IllegalStateException(tr("Can't restrict changeset query to the current user because the current user is anonymous"));
+            } else if (rbRestrictToUid.isSelected()) {
+                int uid  = valUid.getUid();
+                if (uid > 0) {
+                    query.forUser(uid);
+                } else
+                    throw new IllegalStateException(tr("Current value ''{0}'' for user ID isn''t valid", tfUid.getText()));
+            } else if (rbRestrictToUserName.isSelected()) {
+                if (! valUserName.isValid())
+                    throw new IllegalStateException(tr("Can''t restrict the changeset query to the user name ''{0}''", tfUserName.getText()));
+                query.forUser(tfUserName.getText());
+            }
+        }
+
+
+        public boolean isValidChangesetQuery() {
+            if (rbRestrictToUid.isSelected())
+                return valUid.isValid();
+            else if (rbRestrictToUserName.isSelected())
+                return valUserName.isValid();
+            return true;
+        }
+
+        protected void alertInvalidUid() {
+            HelpAwareOptionPane.showOptionDialog(
+                    this,
+                    tr("Please enter a valid user ID"),
+                    tr("Invalid user ID"),
+                    JOptionPane.ERROR_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserId")
+            );
+        }
+
+        protected void alertInvalidUserName() {
+            HelpAwareOptionPane.showOptionDialog(
+                    this,
+                    tr("Please enter a non-empty user name"),
+                    tr("Invalid user name"),
+                    JOptionPane.ERROR_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserName")
+            );
+        }
+
+        public void displayMessageIfInvalid() {
+            if (rbRestrictToUid.isSelected()) {
+                if (!valUid.isValid()) {
+                    alertInvalidUid();
+                }
+            } else if (rbRestrictToUserName.isSelected()) {
+                if (!valUserName.isValid()) {
+                    alertInvalidUserName();
+                }
+            }
+        }
+
+        public void rememberSettings() {
+            String prefRoot = "changeset-query.advanced.user-restrictions";
+            if (rbRestrictToMyself.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "mine");
+            } else if (rbRestrictToUid.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "uid");
+            } else if (rbRestrictToUserName.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "username");
+            }
+            Main.pref.put(prefRoot + ".uid", tfUid.getText());
+            Main.pref.put(prefRoot + ".username", tfUserName.getText());
+        }
+
+        public void restoreFromSettings() {
+            String prefRoot = "changeset-query.advanced.user-restrictions";
+            String v = Main.pref.get(prefRoot + ".query-type", "mine");
+            if (v.equals("mine")) {
+                JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+                if (im.isAnonymous()) {
+                    rbRestrictToUid.setSelected(true);
+                } else {
+                    rbRestrictToMyself.setSelected(true);
+                }
+            } else if (v.equals("uid")) {
+                rbRestrictToUid.setSelected(true);
+            } else if (v.equals("username")) {
+                rbRestrictToUserName.setSelected(true);
+            }
+            tfUid.setText(Main.pref.get(prefRoot + ".uid", ""));
+            if (!valUid.isValid()) {
+                tfUid.setText("");
+            }
+            tfUserName.setText(Main.pref.get(prefRoot + ".username", ""));
+        }
+
+        class UserRestrictionChangedHandler implements ItemListener {
+            public void itemStateChanged(ItemEvent e) {
+                tfUid.setEnabled(rbRestrictToUid.isSelected());
+                tfUserName.setEnabled(rbRestrictToUserName.isSelected());
+                if (rbRestrictToUid.isSelected()) {
+                    tfUid.requestFocusInWindow();
+                } else if (rbRestrictToUserName.isSelected()) {
+                    tfUserName.requestFocusInWindow();
+                }
+            }
+        }
+    }
+
+    /**
+     * This is the panel to apply a time restriction to the changeset query
+     */
+    static private class TimeRestrictionPanel extends JPanel {
+
+        private JRadioButton rbClosedAfter;
+        private JRadioButton rbClosedAfterAndCreatedBefore;
+        private JTextField tfClosedAfterDate1;
+        private DateValidator valClosedAfterDate1;
+        private JTextField tfClosedAfterTime1;
+        private TimeValidator valClosedAfterTime1;
+        private JTextField tfClosedAfterDate2;
+        private DateValidator valClosedAfterDate2;
+        private JTextField tfClosedAfterTime2;
+        private TimeValidator valClosedAfterTime2;
+        private JTextField tfCreatedBeforeDate;
+        private DateValidator valCreatedBeforeDate;
+        private JTextField tfCreatedBeforeTime;
+        private TimeValidator valCreatedBeforeTime;
+
+        protected JPanel buildClosedAfterInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("Date: ")), gc);
+
+            gc.gridx = 1;
+            gc.weightx = 0.7;
+            pnl.add(tfClosedAfterDate1 = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1);
+            valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1);
+            tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml());
+
+            gc.gridx = 2;
+            gc.weightx = 0.0;
+            pnl.add(new JLabel("Time: "),gc);
+
+            gc.gridx = 3;
+            gc.weightx = 0.3;
+            pnl.add(tfClosedAfterTime1 = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1);
+            valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1);
+            tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml());
+            return pnl;
+        }
+
+        protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("Closed after - ")), gc);
+
+            gc.gridx = 1;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("Date: ")), gc);
+
+            gc.gridx = 2;
+            gc.weightx = 0.7;
+            pnl.add(tfClosedAfterDate2 = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2);
+            valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2);
+            tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml());
+            gc.gridx = 3;
+            gc.weightx = 0.0;
+            pnl.add(new JLabel("Time: "),gc);
+
+            gc.gridx = 4;
+            gc.weightx = 0.3;
+            pnl.add(tfClosedAfterTime2 = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2);
+            valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2);
+            tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml());
+
+            gc.gridy = 1;
+            gc.gridx = 0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("Created before - ")), gc);
+
+            gc.gridx = 1;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,0,3);
+            pnl.add(new JLabel(tr("Date: ")), gc);
+
+            gc.gridx = 2;
+            gc.weightx = 0.7;
+            pnl.add(tfCreatedBeforeDate = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate);
+            valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate);
+            tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
+
+            gc.gridx = 3;
+            gc.weightx = 0.0;
+            pnl.add(new JLabel("Time: "),gc);
+
+            gc.gridx = 4;
+            gc.weightx = 0.3;
+            pnl.add(tfCreatedBeforeTime = new JTextField(),gc);
+            SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime);
+            valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime);
+            tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
+
+            return pnl;
+        }
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createEmptyBorder(3,3,3,3),
+                    BorderFactory.createCompoundBorder(
+                            BorderFactory.createLineBorder(Color.GRAY),
+                            BorderFactory.createEmptyBorder(5,5,5,5)
+                    )
+            ));
+
+            // -- changesets closed after a specific date/time
+            //
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.gridx = 0;
+            gc.fill= GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbClosedAfter = new JRadioButton(), gc);
+
+            gc.gridx = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc);
+
+            gc.gridx = 1;
+            gc.gridy = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(buildClosedAfterInputPanel(),gc);
+
+            // -- changesets closed after a specific date/time and created before a specific date time
+            //
+            gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.gridy = 2;
+            gc.gridx = 0;
+            gc.fill= GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            add(rbClosedAfterAndCreatedBefore = new JRadioButton(), gc);
+
+            gc.gridx = 1;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc);
+
+            gc.gridx = 1;
+            gc.gridy = 3;
+            gc.fill =  GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            add(buildClosedAfterAndCreatedBeforeInputPanel(),gc);
+
+            ButtonGroup bg = new ButtonGroup();
+            bg.add(rbClosedAfter);
+            bg.add(rbClosedAfterAndCreatedBefore);
+
+            ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler();
+            rbClosedAfter.addItemListener(restrictionChangeHandler);
+            rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler);
+
+            rbClosedAfter.setSelected(true);
+        }
+
+        public TimeRestrictionPanel() {
+            build();
+        }
+
+        public boolean isValidChangesetQuery() {
+            if (rbClosedAfter.isSelected())
+                return valClosedAfterDate1.isValid() && valClosedAfterTime1.isValid();
+            else if (rbClosedAfterAndCreatedBefore.isSelected())
+                return valClosedAfterDate2.isValid() && valClosedAfterTime2.isValid()
+                && valCreatedBeforeDate.isValid() && valCreatedBeforeTime.isValid();
+            // should not happen
+            return true;
+        }
+
+        class TimeRestrictionChangedHandler implements ItemListener {
+            public void itemStateChanged(ItemEvent e) {
+                tfClosedAfterDate1.setEnabled(rbClosedAfter.isSelected());
+                tfClosedAfterTime1.setEnabled(rbClosedAfter.isSelected());
+
+                tfClosedAfterDate2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+                tfClosedAfterTime2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+                tfCreatedBeforeDate.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+                tfCreatedBeforeTime.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+            }
+        }
+
+        public void startUserInput() {
+            restoreFromSettings();
+        }
+
+        public void fillInQuery(ChangesetQuery query) throws IllegalStateException{
+            if (!isValidChangesetQuery())
+                throw new IllegalStateException(tr("Can't build changeset query with time based restrictions. Input isn't valid."));
+            if (rbClosedAfter.isSelected()) {
+                GregorianCalendar cal = new GregorianCalendar();
+                Date d1 = valClosedAfterDate1.getDate();
+                Date d2 = valClosedAfterTime1.getDate();
+                cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+                query.closedAfter(cal.getTime());
+            } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
+                GregorianCalendar cal = new GregorianCalendar();
+                Date d1 = valClosedAfterDate2.getDate();
+                Date d2 = valClosedAfterTime2.getDate();
+                cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+                Date d3 = cal.getTime();
+
+                d1 = valCreatedBeforeDate.getDate();
+                d2 = valCreatedBeforeTime.getDate();
+                cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+                Date d4 = cal.getTime();
+
+                query.closedAfterAndCreatedBefore(d3, d4);
+            }
+        }
+
+        public void displayMessageIfInvalid() {
+            if (isValidChangesetQuery()) return;
+            HelpAwareOptionPane.showOptionDialog(
+                    this,
+                    tr(
+                            "<html>Please enter valid date/time values to restrict<br>"
+                            + "the query to a specific time range.</html>"
+                    ),
+                    tr("Invalid date/time values"),
+                    JOptionPane.ERROR_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidDateTimeValues")
+            );
+        }
+
+
+        public void rememberSettings() {
+            String prefRoot = "changeset-query.advanced.time-restrictions";
+            if (rbClosedAfter.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "closed-after");
+            } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
+                Main.pref.put(prefRoot + ".query-type", "closed-after-created-before");
+            }
+            Main.pref.put(prefRoot + ".closed-after.date", tfClosedAfterDate1.getText());
+            Main.pref.put(prefRoot + ".closed-after.time", tfClosedAfterTime1.getText());
+            Main.pref.put(prefRoot + ".closed-created.closed.date", tfClosedAfterDate2.getText());
+            Main.pref.put(prefRoot + ".closed-created.closed.time", tfClosedAfterTime2.getText());
+            Main.pref.put(prefRoot + ".closed-created.created.date", tfCreatedBeforeDate.getText());
+            Main.pref.put(prefRoot + ".closed-created.created.time", tfCreatedBeforeTime.getText());
+        }
+
+        public void restoreFromSettings() {
+            String prefRoot = "changeset-query.advanced.open-restrictions";
+            String v = Main.pref.get(prefRoot + ".query-type", "closed-after");
+            rbClosedAfter.setSelected(v.equals("closed-after"));
+            rbClosedAfterAndCreatedBefore.setSelected(v.equals("closed-after-created-before"));
+            if (!rbClosedAfter.isSelected() && !rbClosedAfterAndCreatedBefore.isSelected()) {
+                rbClosedAfter.setSelected(true);
+            }
+            tfClosedAfterDate1.setText(Main.pref.get(prefRoot + ".closed-after.date", ""));
+            tfClosedAfterTime1.setText(Main.pref.get(prefRoot + ".closed-after.time", ""));
+            tfClosedAfterDate2.setText(Main.pref.get(prefRoot + ".closed-created.closed.date", ""));
+            tfClosedAfterTime2.setText(Main.pref.get(prefRoot + ".closed-created.closed.time", ""));
+            tfCreatedBeforeDate.setText(Main.pref.get(prefRoot + ".closed-created.created.date", ""));
+            tfCreatedBeforeTime.setText(Main.pref.get(prefRoot + ".closed-created.created.time", ""));
+            if (!valClosedAfterDate1.isValid()) {
+                tfClosedAfterDate1.setText("");
+            }
+            if (!valClosedAfterTime1.isValid()) {
+                tfClosedAfterTime1.setText("");
+            }
+            if (!valClosedAfterDate2.isValid()) {
+                tfClosedAfterDate2.setText("");
+            }
+            if (!valClosedAfterTime2.isValid()) {
+                tfClosedAfterTime2.setText("");
+            }
+            if (!valCreatedBeforeDate.isValid()) {
+                tfCreatedBeforeDate.setText("");
+            }
+            if (!valCreatedBeforeTime.isValid()) {
+                tfCreatedBeforeTime.setText("");
+            }
+        }
+    }
+
+    static private class BBoxRestrictionPanel extends BoundingBoxSelectionPanel {
+        public BBoxRestrictionPanel() {
+            setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createEmptyBorder(3,3,3,3),
+                    BorderFactory.createCompoundBorder(
+                            BorderFactory.createLineBorder(Color.GRAY),
+                            BorderFactory.createEmptyBorder(5,5,5,5)
+                    )
+            ));
+        }
+
+        public boolean isValidChangesetQuery() {
+            return getBoundingBox() != null;
+        }
+
+        public void fillInQuery(ChangesetQuery query) {
+            if (!isValidChangesetQuery())
+                throw new IllegalStateException(tr("Can''t restrict the changeset query to a specific bounding box. The input is invalid."));
+            query.inBbox(getBoundingBox());
+        }
+
+        public void displayMessageIfInvalid() {
+            if (isValidChangesetQuery()) return;
+            HelpAwareOptionPane.showOptionDialog(
+                    this,
+                    tr(
+                            "<html>Please enter valid longitude/latitude values to restrict<br>" +
+                            "the changeset query to a specific bounding box.</html>"
+                    ),
+                    tr("Invalid bounding box"),
+                    JOptionPane.ERROR_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidBoundingBox")
+            );
+        }
+    }
+
+    static private class QuerySpecificationPanel extends JPanel implements Scrollable {
+        public Dimension getPreferredScrollableViewportSize() {
+            return getPreferredSize();
+        }
+
+        public int getScrollableBlockIncrement(Rectangle arg0, int arg1, int arg2) {
+            return 20;
+        }
+
+        public boolean getScrollableTracksViewportHeight() {
+            return false;
+        }
+
+        public boolean getScrollableTracksViewportWidth() {
+            return true;
+        }
+
+        public int getScrollableUnitIncrement(Rectangle arg0, int arg1, int arg2) {
+            return 10;
+        }
+    }
+
+    /**
+     * Validator for user ids entered in in a {@see JTextComponent}.
+     * 
+     */
+    static private class UidInputFieldValidator extends AbstractTextComponentValidator {
+        static public UidInputFieldValidator decorate(JTextComponent tc) {
+            return new UidInputFieldValidator(tc);
+        }
+
+        public UidInputFieldValidator(JTextComponent tc) {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            return getUid() > 0;
+        }
+
+        @Override
+        public void validate() {
+            String value  = getComponent().getText();
+            if (value == null || value.trim().length() == 0) {
+                feedbackInvalid("");
+                return;
+            }
+            try {
+                int uid = Integer.parseInt(value);
+                if (uid <= 0) {
+                    feedbackInvalid(tr("The current value isn't a valid user ID. Please enter an integer value > 0"));
+                    return;
+                }
+            } catch(NumberFormatException e) {
+                feedbackInvalid(tr("The current value isn't a valid user ID. Please enter an integer value > 0"));
+                return;
+            }
+            feedbackValid(tr("Please enter an integer value > 0"));
+        }
+
+        public int getUid() {
+            String value  = getComponent().getText();
+            if (value == null || value.trim().length() == 0) return 0;
+            try {
+                int uid = Integer.parseInt(value.trim());
+                if (uid > 0) return uid;
+                return 0;
+            } catch(NumberFormatException e) {
+                return 0;
+            }
+        }
+    }
+
+    static private class UserNameInputValidator extends AbstractTextComponentValidator {
+        static public UserNameInputValidator decorate(JTextComponent tc) {
+            return new UserNameInputValidator(tc);
+        }
+
+        public UserNameInputValidator(JTextComponent tc) {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            return getComponent().getText().trim().length() > 0;
+        }
+
+        @Override
+        public void validate() {
+            String value  = getComponent().getText();
+            if (value.trim().length() == 0) {
+                feedbackInvalid(tr("<html>The  current value isn't a valid user name.<br>Please enter an non-empty user name.</html>"));
+                return;
+            }
+            feedbackValid(tr("Please enter an non-empty user name"));
+        }
+    }
+
+    /**
+     * Validates dates entered as text in in a {@see JTextComponent}. Validates the input
+     * on the fly and gives feedback about whether the date is valid or not.
+     * 
+     * Dates can be entered in one of four standard formats defined for the current locale.
+     */
+    static private class DateValidator extends AbstractTextComponentValidator {
+        static public DateValidator decorate(JTextComponent tc) {
+            return new DateValidator(tc);
+        }
+
+        public DateValidator(JTextComponent tc) {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            return getDate() != null;
+        }
+
+        public String getStandardTooltipTextAsHtml() {
+            return "<html>" + getStandardTooltipText() + "</html>";
+        }
+
+        public String getStandardTooltipText() {
+            return  tr(
+                    "Please enter a date in the usual format for your locale.<br>"
+                    + "Example: {0}<br>"
+                    + "Example: {1}<br>"
+                    + "Example: {2}<br>"
+                    + "Example: {3}<br>",
+                    DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
+                    DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
+                    DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
+                    DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
+            );
+        }
+
+        @Override
+        public void validate() {
+            if (!isValid()) {
+                String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>";
+                feedbackInvalid(msg);
+                return;
+            } else {
+                String msg = "<html>" + getStandardTooltipText() + "</html>";
+                feedbackValid(msg);
+            }
+        }
+
+        public Date getDate() {
+            for (int i = 0; i< 4; i++) {
+                try {
+                    DateFormat df = null;
+                    switch(i) {
+                    case 0: df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()); break;
+                    case 1: df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); break;
+                    case 2: df = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()); break;
+                    case 3: df = DateFormat.getDateInstance(DateFormat.FULL,Locale.getDefault()); break;
+                    }
+                    return df.parse(getComponent().getText());
+                } catch(ParseException e) {
+                    continue;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Validates time values entered as text in in a {@see JTextComponent}. Validates the input
+     * on the fly and gives feedback about whether the time value is valid or not.
+     * 
+     * Time values can be entered in one of four standard formats defined for the current locale.
+     */
+    static private class TimeValidator extends AbstractTextComponentValidator {
+        static public TimeValidator decorate(JTextComponent tc) {
+            return new TimeValidator(tc);
+        }
+
+        public TimeValidator(JTextComponent tc) {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            if (getComponent().getText().trim().length() == 0) return true;
+            return getDate() != null;
+        }
+
+        public String getStandardTooltipTextAsHtml() {
+            return "<html>" + getStandardTooltipText() + "</html>";
+        }
+
+        public String getStandardTooltipText() {
+            return tr(
+                    "Please enter a valid time in the usual format for your locale.<br>"
+                    + "Example: {0}<br>"
+                    + "Example: {1}<br>"
+                    + "Example: {2}<br>"
+                    + "Example: {3}<br>",
+                    DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
+                    DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
+                    DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
+                    DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
+            );
+        }
+
+        @Override
+        public void validate() {
+
+            if (!isValid()) {
+                String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>";
+                feedbackInvalid(msg);
+                return;
+            } else {
+                String msg = "<html>" + getStandardTooltipText() + "</html>";
+                feedbackValid(msg);
+            }
+        }
+
+        public Date getDate() {
+            if (getComponent().getText().trim().length() == 0)
+                return null;
+
+            for (int i = 0; i< 4; i++) {
+                try {
+                    DateFormat df = null;
+                    switch(i) {
+                    case 0: df = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()); break;
+                    case 1: df = DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()); break;
+                    case 2: df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()); break;
+                    case 3: df = DateFormat.getTimeInstance(DateFormat.FULL,Locale.getDefault()); break;
+                    }
+                    Date d = df.parse(getComponent().getText());
+                    return d;
+                } catch(ParseException e) {
+                    continue;
+                }
+            }
+            return null;
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java	(revision 2689)
@@ -0,0 +1,260 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.io.ChangesetQuery;
+
+/**
+ * This panel presents a list of basic queries for changests.
+ * 
+ */
+public class BasicChangesetQueryPanel extends JPanel {
+
+    /**
+     * Enumeration of basic, predefined queries
+     */
+    private static enum BasicQuery {
+        MOST_RECENT_CHANGESETS,
+        MY_OPEN_CHANGESETS,
+        CHANGESETS_IN_MAP_VIEW;
+    }
+
+    private ButtonGroup bgQueries;
+    private Map<BasicQuery, JRadioButton> rbQueries;
+    private Map<BasicQuery, JMultilineLabel> lblQueries;
+    private JCheckBox cbMyChangesetsOnly;
+    private HtmlPanel pnlInfos;
+
+
+    protected JPanel buildQueriesPanel() {
+        JPanel pnl = new JPanel(new GridBagLayout());
+
+        bgQueries = new ButtonGroup();
+        rbQueries = new HashMap<BasicQuery, JRadioButton>();
+        lblQueries = new HashMap<BasicQuery, JMultilineLabel>();
+        SelectQueryHandler selectedQueryHandler = new SelectQueryHandler();
+        for (BasicQuery q: BasicQuery.values()) {
+            JRadioButton rb = new JRadioButton();
+            rb.addItemListener(selectedQueryHandler);
+            rbQueries.put(q, rb);
+            bgQueries.add(rb);
+            lblQueries.put(q, new JMultilineLabel(""));
+        }
+
+        GridBagConstraints gc = new GridBagConstraints();
+        // -- most recent changes
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.insets = new Insets(0,0,5,3);
+        pnl.add(rbQueries.get(BasicQuery.MOST_RECENT_CHANGESETS), gc);
+
+        gc.gridx = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        pnl.add(lblQueries.get(BasicQuery.MOST_RECENT_CHANGESETS), gc);
+
+        // -- most recent changes
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = 0.0;
+        pnl.add(rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS), gc);
+
+        gc.gridx = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        pnl.add(lblQueries.get(BasicQuery.MY_OPEN_CHANGESETS), gc);
+
+        // -- changesets in map view
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = 0.0;
+        pnl.add(rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW), gc);
+
+        gc.gridx = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        pnl.add(lblQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW), gc);
+
+        // -- checkbox my changesets only
+        gc.gridx = 0;
+        gc.gridy = 3;
+        gc.gridwidth = 2;
+        gc.insets = new Insets(5,0,3,3);
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        pnl.add(cbMyChangesetsOnly = new JCheckBox(tr("Download my changesets only")), gc);
+        cbMyChangesetsOnly.setToolTipText(tr("<html>Select to restrict the query to your changsets only.<br>Unselect to include all changesets in the query.</html>"));
+
+        // grab remaining space
+        gc.gridx = 0;
+        gc.gridy = 4;
+        gc.gridwidth = 2;
+        gc.insets = new Insets(5,0,3,3);
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        pnl.add(new JPanel(), gc);
+
+        return pnl;
+    }
+
+    protected JPanel buildInfoPanel() {
+        pnlInfos = new HtmlPanel();
+        pnlInfos.setText("<html>Please select one the following <strong>standard queries</strong>."
+                + "Select <strong>Download my changesets only</strong>"
+                + " if you only want to download changesets created by yourself.<br>"
+                + "Note that JOSM will download max. 100 changesets.</html>"
+        );
+        return pnlInfos;
+    }
+
+    protected void build() {
+        setLayout(new BorderLayout(0,5));
+        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+        add(buildInfoPanel(), BorderLayout.NORTH);
+        add(buildQueriesPanel(), BorderLayout.CENTER);
+    }
+
+    public BasicChangesetQueryPanel() {
+        build();
+    }
+
+    public void init() {
+        JMultilineLabel lbl = lblQueries.get(BasicQuery.MOST_RECENT_CHANGESETS);
+        lbl.setText("<html>Download the latest changesets</html>");
+
+        // query for open changesets only possible if we have a current user which is at least
+        // partially identified
+        lbl = lblQueries.get(BasicQuery.MY_OPEN_CHANGESETS);
+        if (JosmUserIdentityManager.getInstance().isAnonymous()) {
+            rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS).setEnabled(false);
+            lbl.setText("<html>Download my open changesets<br><em>Disabled. Please enter your OSM user name in the preferences first.</em></html>");
+        } else {
+            rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS).setEnabled(true);
+            lbl.setText("<html>Download my open changesets</html>");
+        }
+
+        // query for changesets in the current map view only if there *is* a current
+        // map view
+        lbl = lblQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW);
+        if (Main.map == null || Main.map.mapView == null) {
+            rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW).setEnabled(false);
+            lbl.setText("<html>Download changesets in the current map view.<br><em>Disabled. There is currently no map view active.</em></html>");
+        } else {
+            rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW).setEnabled(true);
+            lbl.setText("<html>Download changesets in the current map view</html>");
+        }
+
+        restoreFromPreferences();
+    }
+
+    public void rememberInPreferences() {
+        BasicQuery q = getSelectedQuery();
+        if (q == null) {
+            Main.pref.put("changeset-query.basic.query", null);
+        } else {
+            Main.pref.put("changeset-query.basic.query", q.toString());
+        }
+        Main.pref.put("changeset-query.basic.my-changesets-only", cbMyChangesetsOnly.isSelected());
+    }
+
+    public void restoreFromPreferences() {
+        BasicQuery q;
+        String value =  Main.pref.get("changeset-query.basic.query", null);
+        if (value == null) {
+            q = BasicQuery.MOST_RECENT_CHANGESETS;
+        } else {
+            try {
+                q = BasicQuery.valueOf(BasicQuery.class, value);
+            } catch(IllegalArgumentException e) {
+                System.err.println(tr("Warning: unexpected value for preference ''{0}'', got ''{1}''. Resetting to default query.","changeset-query.basic.query", value));
+                q = BasicQuery.MOST_RECENT_CHANGESETS;
+            }
+        }
+        rbQueries.get(q).setSelected(true);
+        boolean mineOnly = Main.pref.getBoolean("changeset-query.basic.my-changesets-only", false);
+        mineOnly = mineOnly || q.equals(BasicQuery.MY_OPEN_CHANGESETS);
+        cbMyChangesetsOnly.setSelected(mineOnly);
+    }
+
+    protected BasicQuery getSelectedQuery() {
+        for (BasicQuery q : BasicQuery.values()) {
+            if (rbQueries.get(q).isSelected())
+                return q;
+        }
+        return null;
+    }
+
+    public ChangesetQuery buildChangesetQuery() {
+        BasicQuery q = getSelectedQuery();
+        ChangesetQuery query = new ChangesetQuery();
+        JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+        if (q == null)
+            return query;
+        switch(q) {
+        case MOST_RECENT_CHANGESETS:
+            break;
+        case MY_OPEN_CHANGESETS:
+            query = query.beingOpen(true);
+            break;
+        case CHANGESETS_IN_MAP_VIEW:
+            Bounds b = Main.map.mapView.getLatLonBounds(Main.map.mapView.getBounds());
+            query = query.inBbox(b);
+            break;
+        }
+
+        if (cbMyChangesetsOnly.isSelected()) {
+            if (im.isPartiallyIdentified()) {
+                query = query.forUser(im.getUserName());
+            } else if (im.isFullyIdentified()) {
+                query = query.forUser(im.getUserId()).beingOpen(true);
+            } else
+                // anonymous -- should not happen. Message doesn't have to be translated.
+                throw new IllegalStateException("Cannot create changeset query for open changesets of anonymous user");
+        }
+
+        return query;
+    }
+
+    /**
+     * Responds to changes in the selected query
+     * 
+     */
+    class SelectQueryHandler implements ItemListener {
+        public void itemStateChanged(ItemEvent e) {
+            BasicQuery q = getSelectedQuery();
+            if (q == null) return;
+            if (q.equals(BasicQuery.MY_OPEN_CHANGESETS)) {
+                cbMyChangesetsOnly.setSelected(true);
+                cbMyChangesetsOnly.setEnabled(false);
+            } else {
+                if (! cbMyChangesetsOnly.isEnabled()) {
+                    cbMyChangesetsOnly.setEnabled(true);
+                }
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java	(revision 2689)
@@ -0,0 +1,228 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+/**
+ * This is a modal dialog for entering query criteria to search for changesets.
+ * 
+ */
+public class ChangesetQueryDialog extends JDialog {
+
+    private JTabbedPane tpQueryPanels;
+    private BasicChangesetQueryPanel pnlBasicChangesetQueries;
+    private UrlBasedQueryPanel pnlUrlBasedQueries;
+    private AdvancedChangesetQueryPanel pnlAdvancedQueries;
+    private boolean canceled;
+
+    protected JPanel buildContentPanel() {
+        tpQueryPanels = new JTabbedPane();
+        tpQueryPanels.add(pnlBasicChangesetQueries = new BasicChangesetQueryPanel());
+        tpQueryPanels.add(pnlUrlBasedQueries = new UrlBasedQueryPanel());
+        tpQueryPanels.add(pnlAdvancedQueries = new AdvancedChangesetQueryPanel());
+
+        tpQueryPanels.setTitleAt(0, tr("Basic"));
+        tpQueryPanels.setToolTipTextAt(0, tr("Download changesets using a predefined queries"));
+
+        tpQueryPanels.setTitleAt(1, tr("From URL"));
+        tpQueryPanels.setToolTipTextAt(1, tr("Query changesets according to an URL of the OSM server"));
+
+        tpQueryPanels.setTitleAt(2, tr("Advanced"));
+        tpQueryPanels.setToolTipTextAt(2, tr("Query changesets according to advanced query parameters"));
+
+        JPanel pnl = new JPanel(new BorderLayout());
+        pnl.add(tpQueryPanels, BorderLayout.CENTER);
+        return pnl;
+    }
+
+    protected JPanel buildButtonPanel() {
+        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+        // -- query action
+        pnl.add(new SideButton(new QueryAction()));
+
+        // -- cancel action
+        pnl.add(new SideButton(new CancelAction()));
+
+        // -- help action
+        pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/ChangesetQuery"))));
+
+        return pnl;
+    }
+
+
+    protected void build() {
+        setTitle(tr("Query changesets"));
+        Container cp = getContentPane();
+        cp.setLayout(new BorderLayout());
+        cp.add(buildContentPanel(), BorderLayout.CENTER);
+        cp.add(buildButtonPanel(), BorderLayout.SOUTH);
+
+        // cancel on ESC
+        getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
+        getRootPane().getActionMap().put("cancel", new CancelAction());
+
+        // context sensitive help
+        HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetQuery"));
+
+        addWindowListener(new WindowEventHandler());
+    }
+
+
+    public ChangesetQueryDialog(Dialog parent) {
+        super(parent, true /* modal */);
+        build();
+    }
+
+    public ChangesetQueryDialog(Frame parent) {
+        super(parent, true /* modal */);
+        build();
+    }
+
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    public void initForUserInput() {
+        pnlBasicChangesetQueries.init();
+    }
+
+    protected void setCanceled(boolean canceled) {
+        this.canceled = canceled;
+    }
+
+    public ChangesetQuery getChangesetQuery() {
+        if (isCanceled())
+            return null;
+        switch(tpQueryPanels.getSelectedIndex()) {
+        case 0:
+            return pnlBasicChangesetQueries.buildChangesetQuery();
+        case 1:
+            return pnlUrlBasedQueries.buildChangesetQuery();
+        case 2:
+            return pnlAdvancedQueries.buildChangesetQuery();
+        default:
+            // FIXME: extend with advanced queries
+            return null;
+        }
+    }
+
+    public void startUserInput() {
+        pnlUrlBasedQueries.startUserInput();
+        pnlAdvancedQueries.startUserInput();
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            new WindowGeometry(
+                    getClass().getName() + ".geometry",
+                    WindowGeometry.centerInWindow(
+                            getParent(),
+                            new Dimension(400,400)
+                    )
+            ).apply(this);
+            setCanceled(false);
+            startUserInput();
+        } else if (!visible && isShowing()){
+            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+            pnlAdvancedQueries.rememberSettings();
+        }
+        super.setVisible(visible);
+    }
+
+    class QueryAction extends AbstractAction {
+        public QueryAction() {
+            putValue(NAME, tr("Query"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "search"));
+            putValue(SHORT_DESCRIPTION, tr("Query and download changesets"));
+        }
+
+        protected void alertInvalidChangesetQuery() {
+            HelpAwareOptionPane.showOptionDialog(
+                    ChangesetQueryDialog.this,
+                    tr("Please enter a valid changeset query URL first."),
+                    tr("Illegal changeset query URL"),
+                    JOptionPane.WARNING_MESSAGE,
+                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#EnterAValidChangesetQueryUrlFirst")
+            );
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            switch(tpQueryPanels.getSelectedIndex()) {
+            case 0:
+                // currently, query specifications can't be invalid in the basic query panel.
+                // We select from a couple of predefined queries and there is always a query
+                // selected
+                break;
+            case 1:
+                if (getChangesetQuery() == null) {
+                    alertInvalidChangesetQuery();
+                    pnlUrlBasedQueries.startUserInput();
+                    return;
+                }
+                break;
+
+            case 2:
+                if (getChangesetQuery() == null) {
+                    pnlAdvancedQueries.displayMessageIfInvalid();
+                    return;
+                }
+            }
+            setCanceled(false);
+            setVisible(false);
+        }
+    }
+
+    class CancelAction extends AbstractAction {
+
+        public CancelAction() {
+            putValue(NAME, tr("Cancel"));
+            putValue(SMALL_ICON, ImageProvider.get("cancel"));
+            putValue(SHORT_DESCRIPTION, tr("Close the dialog and abort querying of changesets"));
+        }
+
+        public void cancel() {
+            setCanceled(true);
+            setVisible(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            cancel();
+        }
+    }
+
+    class WindowEventHandler extends WindowAdapter {
+        @Override
+        public void windowClosing(WindowEvent arg0) {
+            new CancelAction().cancel();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java	(revision 2689)
@@ -0,0 +1,198 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetDownloadTask;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmServerUserInfoReader;
+import org.openstreetmap.josm.io.OsmTransferCancelledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.xml.sax.SAXException;
+
+/**
+ * Asynchronous task to send a changeset query to the OSM API.
+ *
+ */
+public class ChangesetQueryTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+    /** the changeset query */
+    private ChangesetQuery query;
+    /** true if the task was canceled */
+    private boolean canceled;
+    /** the set of downloaded changsets */
+    private Set<Changeset> downloadedChangesets;
+    /** the last exception remembered, if any */
+    private Exception lastException;
+    /** the reader object used to read information about the current user from the API */
+    private OsmServerUserInfoReader userInfoReader;
+    /** the reader object used to submit the changeset query to the API */
+    private OsmServerChangesetReader changesetReader;
+
+    /**
+     * Creates the task.
+     * 
+     * @param query the query to submit to the OSM server. Must not be null.
+     * @throws IllegalArgumentException thrown if query is null.
+     */
+    public ChangesetQueryTask(ChangesetQuery query) throws IllegalArgumentException {
+        super(tr("Querying and downloading changesets",false /* don't ignore exceptions */));
+        CheckParameterUtil.ensureParameterNotNull(query, "query");
+        this.query = query;
+    }
+
+    /**
+     * Creates the task.
+     * 
+     * @param parent the parent component relative to which the {@see PleaseWaitDialog} is displayed.
+     * Must not be null.
+     * @param query the query to submit to the OSM server. Must not be null.
+     * @throws IllegalArgumentException thrown if query is null.
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    public ChangesetQueryTask(Component parent, ChangesetQuery query) throws IllegalArgumentException {
+        super(parent, tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
+        CheckParameterUtil.ensureParameterNotNull(query, "query");
+        this.query = query;
+    }
+
+    @Override
+    protected void cancel() {
+        canceled = true;
+        synchronized(this) {
+            if (userInfoReader != null) {
+                userInfoReader.cancel();
+            }
+        }
+        synchronized(this) {
+            if (changesetReader != null) {
+                changesetReader.cancel();
+            }
+        }
+    }
+
+    @Override
+    protected void finish() {
+        if (canceled) return;
+        if (lastException != null) {
+            ExceptionUtil.explainException(lastException);
+            return;
+        }
+
+        // update the global changeset cache with the downloaded changesets;
+        // this will trigger change events which views are listening to. They
+        // will update their views accordingly.
+        //
+        // Run on the EDT because UI updates are triggered.
+        //
+        Runnable r = new Runnable() {
+            public void run() {
+                ChangesetCache.getInstance().update(downloadedChangesets);
+            }
+        };
+        if (SwingUtilities.isEventDispatchThread()) {
+            r.run();
+        } else {
+            try {
+                SwingUtilities.invokeAndWait(r);
+            } catch(InterruptedException e) {
+                e.printStackTrace();
+            } catch(InvocationTargetException e) {
+                Throwable t = e.getTargetException();
+                if (t instanceof RuntimeException) {
+                    BugReportExceptionHandler.handleException(t);
+                } else if (t instanceof Exception){
+                    ExceptionUtil.explainException(e);
+                } else {
+                    BugReportExceptionHandler.handleException(t);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tries to fully identify the current JOSM user
+     * 
+     * @throws OsmTransferException thrown if something went wrong
+     */
+    protected void fullyIdentifyCurrentUser() throws OsmTransferException {
+        getProgressMonitor().indeterminateSubTask(tr("Determine user id for current user..."));
+
+        synchronized(this) {
+            userInfoReader = new OsmServerUserInfoReader();
+        }
+        UserInfo info = userInfoReader.fetchUserInfo(getProgressMonitor().createSubTaskMonitor(1,false));
+        synchronized(this) {
+            userInfoReader = null;
+        }
+        JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+        im.setFullyIdentified(im.getUserName(), info);
+    }
+
+    @Override
+    protected void realRun() throws SAXException, IOException, OsmTransferException {
+        try {
+            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+            if (query.isRestrictedToPartiallyIdentifiedUser() && im.isCurrentUser(query.getUserName())) {
+                // if we query changesets for the current user, make sure we query against
+                // its user id, not its user name. If necessary, determine the user id
+                // first.
+                //
+                if (im.isPartiallyIdentified() ) {
+                    fullyIdentifyCurrentUser();
+                }
+                query = query.forUser(JosmUserIdentityManager.getInstance().getUserId());
+            }
+            if (canceled) return;
+            getProgressMonitor().indeterminateSubTask(tr("Query and download changesets ..."));
+            synchronized(this) {
+                changesetReader= new OsmServerChangesetReader();
+            }
+            downloadedChangesets = new HashSet<Changeset>();
+            downloadedChangesets.addAll(changesetReader.queryChangesets(query, getProgressMonitor().createSubTaskMonitor(0, false)));
+            synchronized (this) {
+                changesetReader = null;
+            }
+        } catch(OsmTransferCancelledException e) {
+            // thrown if user cancel the authentication dialog
+            canceled = true;
+            return;
+        }  catch(OsmTransferException e) {
+            if (canceled)
+                return;
+            this.lastException = e;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* interface ChangesetDownloadTask                                                 */
+    /* ------------------------------------------------------------------------------- */
+    public Set<Changeset> getDownloadedChangesets() {
+        return downloadedChangesets;
+    }
+
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    public boolean isFailed() {
+        return lastException != null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java	(revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java	(revision 2689)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.ChangesetQuery.ChangesetQueryUrlException;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+
+public class UrlBasedQueryPanel extends JPanel {
+
+    private JTextField tfUrl;
+    private JLabel lblValid;
+
+    protected JPanel buildURLPanel() {
+        JPanel pnl = new JPanel(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.insets  = new Insets(0,0,0,5);
+        pnl.add(new JLabel(tr("URL: ")), gc);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(tfUrl = new JTextField(), gc);
+        tfUrl.getDocument().addDocumentListener(new ChangetQueryUrlValidator());
+        tfUrl.addFocusListener(
+                new FocusAdapter() {
+                    @Override
+                    public void focusGained(FocusEvent e) {
+                        tfUrl.selectAll();
+                    }
+                }
+        );
+
+        gc.gridx = 2;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        pnl.add(lblValid = new JLabel(), gc);
+        lblValid.setPreferredSize(new Dimension(20,20));
+        return pnl;
+    }
+
+    protected JPanel buildHelpPanel() {
+        HtmlPanel pnl = new HtmlPanel();
+        pnl.setText(
+                tr("<html><body>Please enter or paste an URL to retrieve changesets from the OSM API."
+                        + "<p><strong>Examples</strong></p>"
+                        + "<ul>"
+                        + "<li><a href=\"http://www.openstreetmap.org/browse/changesets?open=true\">http://www.openstreetmap.org/browse/changesets?open=true</a></li>"
+                        + "<li><a href=\"http://api.openstreetmap.org/api/0.6/changesets?open=true\">http://api.openstreetmap.org/api/0.6/changesets?open=true</a></li>"
+                        + "</ul>"
+                        + "Note that changeset queries are currently always submitted to ''{0}'', regardless of the "
+                        + "host, port and path of the URL entered beow."
+                        + "</body></html>",
+                        OsmApi.getOsmApi().getBaseUrl()
+                )
+        );
+        pnl.getEditorPane().addHyperlinkListener(
+                new HyperlinkListener() {
+                    public void hyperlinkUpdate(HyperlinkEvent e) {
+                        if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
+                            tfUrl.setText(e.getDescription());
+                            tfUrl.requestFocusInWindow();
+                        }
+                    }
+                }
+        );
+        return pnl;
+    }
+
+    protected ChangesetQuery buildChangesetQueryFromUrlQuery(String query) {
+        if (query == null)
+            return new ChangesetQuery();
+        query = query.trim();
+        return null;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.weightx = 1.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.insets = new Insets(0,0,10,0);
+        add(buildHelpPanel(),gc);
+
+        gc.gridy = 1;
+        gc.weightx = 1.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        add(buildURLPanel(),gc);
+
+        gc.gridy = 2;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        add(new JPanel(),gc);
+
+    }
+    public UrlBasedQueryPanel() {
+        build();
+    }
+
+    protected boolean isValidChangesetQueryUrl(String text) {
+        return buildChangesetQuery(text) != null;
+    }
+
+    protected ChangesetQuery buildChangesetQuery(String text) {
+        URL url = null;
+        try {
+            url = new URL(text);
+        } catch(MalformedURLException e) {
+            return null;
+        }
+        String path = url.getPath();
+        String query = url.getQuery();
+        if (path == null || ! path.endsWith("/changesets")) return null;
+
+        try {
+            return ChangesetQuery.buildFromUrlQuery(query);
+        } catch(ChangesetQueryUrlException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Replies the {@see ChangesetQuery} specified in this panel. null, if no valid changeset query
+     * is specified.
+     * 
+     * @return the changeset query
+     */
+    public ChangesetQuery buildChangesetQuery() {
+        String value = tfUrl.getText().trim();
+        return buildChangesetQuery(value);
+    }
+
+    public void startUserInput() {
+        tfUrl.requestFocusInWindow();
+    }
+
+    /**
+     * Validates text entered in the changeset query URL field on the fly
+     */
+    class ChangetQueryUrlValidator implements DocumentListener {
+        protected String getCurrentFeedback() {
+            String fb = (String)lblValid.getClientProperty("valid");
+            return fb == null ? "none" : fb;
+        }
+        protected void feedbackValid() {
+            if (getCurrentFeedback().equals("valid")) return;
+            lblValid.setIcon(ImageProvider.get("dialogs/changeset", "valid"));
+            lblValid.setToolTipText("");
+            lblValid.putClientProperty("valid", "valid");
+        }
+
+        protected void feedbackInvalid() {
+            if (getCurrentFeedback().equals("invalid")) return;
+            lblValid.setIcon(ImageProvider.get("warning-small"));
+            lblValid.setToolTipText(tr("This changeset query URL is invalid"));
+            lblValid.putClientProperty("valid", "invalid");
+        }
+
+        protected void feedbackNone() {
+            lblValid.setIcon(null);
+            lblValid.putClientProperty("valid", "none");
+        }
+
+        protected void validate() {
+            String value = tfUrl.getText();
+            if (value.trim().equals("")) {
+                feedbackNone();
+                return;
+            }
+            value = value.trim();
+            if (isValidChangesetQueryUrl(value)) {
+                feedbackValid();
+            } else {
+                feedbackInvalid();
+            }
+        }
+        public void changedUpdate(DocumentEvent e) {
+            validate();
+        }
+
+        public void insertUpdate(DocumentEvent e) {
+            validate();
+        }
+
+        public void removeUpdate(DocumentEvent e) {
+            validate();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(revision 2689)
@@ -7,5 +7,4 @@
 import java.awt.BorderLayout;
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Graphics;
@@ -225,9 +224,9 @@
                         Math.min(l2.getLat(), l1.getLat()),
                         Math.min(l1.getLon(), l2.getLon())
-                        ),
+                ),
                 new LatLon(
                         Math.max(l2.getLat(), l1.getLat()),
                         Math.max(l1.getLon(), l2.getLon()))
-                );
+        );
         iGui.boundingBoxChanged(b, this);
         repaint();
Index: trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java	(revision 2689)
@@ -5,4 +5,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Component;
 import java.io.IOException;
 import java.util.Collection;
@@ -21,4 +22,5 @@
 import org.openstreetmap.josm.io.OsmServerHistoryReader;
 import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.xml.sax.SAXException;
 
@@ -51,4 +53,17 @@
     public HistoryLoadTask() {
         super(tr("Load history"), true);
+        toLoad = new HashSet<PrimitiveId>();
+    }
+
+    /**
+     * Creates a new task
+     * 
+     * @param parent the component to be used as reference to find the parent for {@see PleaseWaitDialog}.
+     * Must not be null.
+     * @throws IllegalArgumentException thrown if parent is null
+     */
+    public HistoryLoadTask(Component parent) {
+        super(parent, tr("Load history"), true);
+        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
         toLoad = new HashSet<PrimitiveId>();
     }
Index: trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java	(revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java	(revision 2689)
@@ -99,5 +99,5 @@
                 return;
             reader = new OsmServerChangesetReader();
-            ChangesetQuery query = new ChangesetQuery().forUser(model.getUserId()).beingOpen();
+            ChangesetQuery query = new ChangesetQuery().forUser((int)model.getUserId()).beingOpen(true);
             changesets = reader.queryChangesets(
                     query,
