Index: trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java	(revision 2033)
+++ trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java	(revision 2034)
@@ -5,4 +5,5 @@
 
 import java.awt.event.ActionEvent;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -31,10 +32,10 @@
      * @return the base URL, i.e. http://api.openstreetmap.org/browse
      */
-    protected String getBaseURL() {
+    protected String getBaseBrowseUrl() {
         String baseUrl = Main.pref.get("osm-server.url", "http://api.openstreetmap.org/api");
         Pattern pattern = Pattern.compile("/api/?$");
         String ret =  pattern.matcher(baseUrl).replaceAll("/browse");
         if (ret.equals(baseUrl)) {
-            System.out.println(tr("WARNING: unexpected format of API base URL. Redirection to history page for OSM primitive will probably fail. API base URL is: ''{0}''",baseUrl));
+            System.out.println(tr("WARNING: unexpected format of API base URL. Redirection to info or history page for OSM primitive will probably fail. API base URL is: ''{0}''",baseUrl));
         }
         if (ret.startsWith("http://api.openstreetmap.org/")) {
@@ -45,5 +46,36 @@
     }
 
-    protected void launchBrowser() {
+    /**
+     * replies the base URL for browsing information about a user
+     *
+     * @return the base URL, i.e. http://ww.openstreetmap.org/user
+     */
+    protected String getBaseUserUrl() {
+        String baseUrl = Main.pref.get("osm-server.url", "http://api.openstreetmap.org/api");
+        Pattern pattern = Pattern.compile("/api/?$");
+        String ret =  pattern.matcher(baseUrl).replaceAll("/user");
+        if (ret.equals(baseUrl)) {
+            System.out.println(tr("WARNING: unexpected format of API base URL. Redirection to user page for OSM user will probably fail. API base URL is: ''{0}''",baseUrl));
+        }
+        if (ret.startsWith("http://api.openstreetmap.org/")) {
+            ret = ret.substring("http://api.openstreetmap.org/".length());
+            ret = "http://www.openstreetmap.org/" + ret;
+        }
+        return ret;
+    }
+
+    protected void launchBrowser(URL url) {
+        OpenBrowser.displayUrl(
+                url.toString()
+        );
+    }
+
+    protected void launchBrowser(String url) {
+        OpenBrowser.displayUrl(
+                url
+        );
+    }
+
+    protected void launchInfoBrowsersForSelectedPrimitives() {
         ArrayList<OsmPrimitive> primitivesToShow = new ArrayList<OsmPrimitive>(getCurrentDataSet().getSelected());
 
@@ -74,15 +106,13 @@
         }
         for(int i = 0; i < max; i++) {
-            OpenBrowser.displayUrl(
-                    createInfoUrl(primitivesToShow.get(i))
-            );
+            launchBrowser(createInfoUrl(primitivesToShow.get(i)));
         }
     }
 
     public void actionPerformed(ActionEvent e) {
-        launchBrowser();
+        launchInfoBrowsersForSelectedPrimitives();
     }
 
-    protected abstract String createInfoUrl(OsmPrimitive primitive);
+    protected abstract String createInfoUrl(Object infoObject);
 
     @Override
Index: trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 2033)
+++ trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 2034)
@@ -20,6 +20,7 @@
 
     @Override
-    protected  String createInfoUrl(OsmPrimitive primitive) {
-        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId() + "/history";
+    protected  String createInfoUrl(Object infoObject) {
+        OsmPrimitive primitive = (OsmPrimitive)infoObject;
+        return getBaseBrowseUrl() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId() + "/history";
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/InfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/InfoAction.java	(revision 2033)
+++ trunk/src/org/openstreetmap/josm/actions/InfoAction.java	(revision 2034)
@@ -20,6 +20,7 @@
 
     @Override
-    protected  String createInfoUrl(OsmPrimitive primitive) {
-        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId();
+    protected  String createInfoUrl(Object infoObject) {
+        OsmPrimitive primitive = (OsmPrimitive)infoObject;
+        return getBaseBrowseUrl() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId();
     }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/User.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2033)
+++ trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2034)
@@ -39,3 +39,34 @@
         return user;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((uid == null) ? 0 : uid.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        User other = (User) obj;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (uid == null) {
+            if (other.uid != null)
+                return false;
+        } else if (!uid.equals(other.uid))
+            return false;
+        return true;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 2033)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 2034)
@@ -6,26 +6,41 @@
 
 import java.awt.BorderLayout;
+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.MouseListener;
-import java.util.Arrays;
+import java.text.NumberFormat;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Comparator;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
-
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
 import javax.swing.table.DefaultTableModel;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AbstractInfoAction;
 import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.User;
+import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -34,23 +49,14 @@
  * selection area, along with the number of objects.
  *
- * @author Frederik Ramm <frederik@remote.org>
  */
-public class UserListDialog extends ToggleDialog implements SelectionChangedListener, MouseListener, LayerChangeListener {
+public class UserListDialog extends ToggleDialog implements SelectionChangedListener, LayerChangeListener {
 
     /**
      * The display list.
      */
-    private final DefaultTableModel data = new DefaultTableModel() {
-        @Override public boolean isCellEditable(int row, int column) {
-            return false;
-        }
-        @Override public Class<?> getColumnClass(int columnIndex) {
-            return columnIndex == 0 ? String.class : Integer.class;
-        }
-    };
-
-    private JTable userTable = new JTable(data);
-
-    private static User anonymousUser = User.get("(anonymous users)");
+    private JTable userTable;
+    private UserTableModel model;
+    private SelectUsersPrimitivesAction selectionUsersPrimitivesAction;
+    private ShowUserInfoAction showUserInfoAction;
 
     public UserListDialog() {
@@ -58,20 +64,39 @@
                 Shortcut.registerShortcut("subwindow:authors", tr("Toggle: {0}", tr("Authors")), KeyEvent.VK_A, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
 
-        data.setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%"});
-        userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-        add(new JScrollPane(userTable), BorderLayout.CENTER);
-        if (Main.main.getCurrentDataSet() != null) {
-            selectionChanged(Main.main.getCurrentDataSet().getSelected());
-        }
-        userTable.addMouseListener(this);
+        build();
         DataSet.selListeners.add(this);
         Layer.listeners.add(this);
     }
 
-    @Override public void setVisible(boolean b) {
-        super.setVisible(b);
-        if (b && Main.main.getCurrentDataSet() != null) {
-            selectionChanged(Main.main.getCurrentDataSet().getSelected());
-        }
+    protected JPanel buildButtonRow() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+        // -- select users primitives action
+        //
+        selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction();
+        userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction);
+        pnl.add(new SideButton(selectionUsersPrimitivesAction));
+
+        // -- info action
+        //
+        showUserInfoAction = new ShowUserInfoAction();
+        userTable.getSelectionModel().addListSelectionListener(showUserInfoAction);
+        pnl.add(new SideButton(showUserInfoAction));
+        return pnl;
+    }
+
+    protected void build() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+        model = new UserTableModel();
+        userTable = new JTable(model);
+        userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        pnl.add(new JScrollPane(userTable), BorderLayout.CENTER);
+
+        // -- the button row
+        pnl.add(buildButtonRow(), BorderLayout.SOUTH);
+        userTable.addMouseListener(new DoubleClickAdapter());
+        add(pnl, BorderLayout.CENTER);
     }
 
@@ -81,46 +106,27 @@
      */
     public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
-        if (!isVisible())
-            return;
-
-        class UserCount {
-            User user;
-            int count;
-            UserCount(User user, int count) { this.user=user; this.count=count; }
-        }
-
-        if (data == null)
-            return; // selection changed may be received in base class constructor before init
-
-        data.setRowCount(0);
-
-        HashMap<User,UserCount> counters = new HashMap<User,UserCount>();
-        int all = 0;
-        for (OsmPrimitive p : newSelection) {
-            User u = p.user;
-            if (u == null) {
-                u = anonymousUser;
-            }
-            UserCount uc = counters.get(u);
-            if (uc == null) {
-                counters.put(u, uc = new UserCount(u, 0));
-            }
-            uc.count++;
-            all++;
-        }
-        UserCount[] ucArr = new UserCount[counters.size()];
-        counters.values().toArray(ucArr);
-        Arrays.sort(ucArr, new Comparator<UserCount>() {
-            public int compare(UserCount a, UserCount b) {
-                return (a.count<b.count) ? 1 : (a.count>b.count) ? -1 : 0;
-            }
-        });
-
-        for (UserCount uc : ucArr) {
-            data.addRow(new Object[] { uc.user.name, uc.count, uc.count * 100 / all });
-        }
-
-        if(ucArr.length != 0) {
-            setTitle(trn("{0} Author", "{0} Authors", ucArr.length, ucArr.length));
+        refresh(newSelection);
+    }
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+        if (newLayer instanceof OsmDataLayer) {
+            refresh(((OsmDataLayer) newLayer).data.getSelected());
+        } else {
+            refresh(null);
+        }
+    }
+
+    public void layerAdded(Layer newLayer) {
+        // do nothing
+    }
+
+    public void layerRemoved(Layer oldLayer) {
+        // do nothing
+    }
+
+    public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) {
+        model.populate(fromPrimitives);
+        if(model.getRowCount() != 0) {
+            setTitle(trn("{0} Author", "{0} Authors", model.getRowCount() , model.getRowCount()));
         } else {
             setTitle(tr("Authors"));
@@ -128,14 +134,190 @@
     }
 
-    public void mouseClicked(MouseEvent e) {
-        if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) {
-            int index = userTable.getSelectedRow();
-            String userName = (String) data.getValueAt(index, 0);
-            if (userName==null)
-                return;
+    class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener{
+        public SelectUsersPrimitivesAction() {
+            putValue(NAME, tr("Select"));
+            putValue(SHORT_DESCRIPTION, tr("Select primitives submitted by this user"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
+            updateEnabledState();
+        }
+
+        public void select() {
+            int indexes[] = userTable.getSelectedRows();
+            if (indexes == null || indexes.length == 0) return;
+            model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            select();
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    /**
+     * Action for launching the info page of a user
+     */
+    class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
+
+        public ShowUserInfoAction() {
+            putValue(NAME, tr("Show info"));
+            putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
+            putValue(SMALL_ICON, ImageProvider.get("about"));
+            updateEnabledState();
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int rows[] = userTable.getSelectedRows();
+            if (rows == null || rows.length == 0) return;
+            List<User> users = model.getSelectedUsers(rows);
+            if (users.isEmpty()) return;
+            if (users.size() > 10) {
+                System.out.println(tr("Warning: only launching info browsers for the first {0} of {1} selected users", 10, users.size()));
+            }
+            int num = Math.min(10, users.size());
+            Iterator<User> it = users.iterator();
+            while(it.hasNext() && num > 0) {
+                launchBrowser(createInfoUrl(it.next()));
+                num--;
+            }
+        }
+
+        @Override
+        protected String createInfoUrl(Object infoObject) {
+            User user = (User)infoObject;
+            return getBaseUserUrl() + "/" + user.name;
+        }
+
+        @Override
+        protected void updateEnabledState() {
+            setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+
+    class DoubleClickAdapter extends MouseAdapter {
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) {
+                selectionUsersPrimitivesAction.select();
+            }
+        }
+    }
+
+    /**
+     * Action for selecting the primitives contributed by the currently selected
+     * users.
+     * 
+     */
+    private static class UserInfo implements Comparable<UserInfo> {
+        public User user;
+        public int count;
+        public double percent;
+        UserInfo(User user, int count, double percent) {
+            this.user=user;
+            this.count=count;
+            this.percent = percent;
+        }
+        public int compareTo(UserInfo o) {
+            if (count < o.count) return 1;
+            if (count > o.count) return -1;
+            if (user== null || user.name == null) return 1;
+            if (o.user == null || o.user.name == null) return -1;
+            return user.name.compareTo(o.user.name);
+        }
+
+        public String getName() {
+            if (user == null) return null;
+            return user.name;
+        }
+    }
+
+    /**
+     * The table model for the users
+     *
+     */
+    class UserTableModel extends DefaultTableModel {
+        private ArrayList<UserInfo> data;
+
+        public UserTableModel() {
+            setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%"});
+            data = new ArrayList<UserInfo>();
+        }
+
+        protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
+            HashMap<User, Integer> ret = new HashMap<User, Integer>();
+            if (primitives == null || primitives.isEmpty()) return ret;
+            for (OsmPrimitive primitive: primitives) {
+                if (primitive.user == null) {
+                    continue;
+                }
+                if (ret.containsKey(primitive.user)) {
+                    ret.put(primitive.user, ret.get(primitive.user) + 1);
+                } else {
+                    ret.put(primitive.user, 1);
+                }
+            }
+            return ret;
+        }
+
+        public void populate(Collection<? extends OsmPrimitive> primitives) {
+            Map<User,Integer> statistics = computeStatistics(primitives);
+            data.clear();
+            if (primitives != null) {
+                for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
+                    data.add(new UserInfo(entry.getKey(), entry.getValue(), (double)entry.getValue() /  (double)primitives.size()));
+                }
+            }
+            Collections.sort(data);
+            fireTableDataChanged();
+        }
+
+        @Override
+        public int getRowCount() {
+            if (data == null) return 0;
+            return data.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            UserInfo info = data.get(row);
+            switch(column) {
+                case 0: /* author */ return info.getName() == null ? "" : info.getName();
+                case 1: /* count */ return info.count;
+                case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        public void selectPrimitivesOwnedBy(int [] rows) {
+            Set<User> users= new HashSet<User>();
+            for (int index: rows) {
+                if (data.get(index).user == null) {
+                    continue;
+                }
+                users.add(data.get(index).user);
+            }
             Collection<OsmPrimitive> selected = Main.main.getCurrentDataSet().getSelected();
             Collection<OsmPrimitive> byUser = new LinkedList<OsmPrimitive>();
             for (OsmPrimitive p : selected) {
-                if (p.user!= null && userName.equals(p.user.name)) {
+                if (p.user == null) {
+                    continue;
+                }
+                if (users.contains(p.user)) {
                     byUser.add(p);
                 }
@@ -143,32 +325,16 @@
             Main.main.getCurrentDataSet().setSelected(byUser);
         }
-    }
-
-    public void mouseEntered(MouseEvent e) {
-    }
-
-    public void mouseExited(MouseEvent e) {
-    }
-
-    public void mousePressed(MouseEvent e) {
-    }
-
-    public void mouseReleased(MouseEvent e) {
-    }
-
-    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
-        if (newLayer instanceof OsmDataLayer) {
-            OsmDataLayer dataLayer = (OsmDataLayer)newLayer;
-            selectionChanged(dataLayer.data.getSelected());
-
-        }
-    }
-
-    public void layerAdded(Layer newLayer) {
-        // do nothing
-    }
-
-    public void layerRemoved(Layer oldLayer) {
-        // do nothing
+
+        public List<User> getSelectedUsers(int rows[]) {
+            LinkedList<User> ret = new LinkedList<User>();
+            if (rows == null || rows.length == 0) return ret;
+            for (int row: rows) {
+                if (data.get(row).user == null) {
+                    continue;
+                }
+                ret.add(data.get(row).user);
+            }
+            return ret;
+        }
     }
 }
