001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.gui.dialogs; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 import static org.openstreetmap.josm.tools.I18n.trn; 006 007 import java.awt.event.ActionEvent; 008 import java.awt.event.KeyEvent; 009 import java.awt.event.MouseAdapter; 010 import java.awt.event.MouseEvent; 011 import java.io.UnsupportedEncodingException; 012 import java.net.URLEncoder; 013 import java.text.NumberFormat; 014 import java.util.ArrayList; 015 import java.util.Arrays; 016 import java.util.Collection; 017 import java.util.Collections; 018 import java.util.HashMap; 019 import java.util.HashSet; 020 import java.util.Iterator; 021 import java.util.LinkedList; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Set; 025 026 import javax.swing.AbstractAction; 027 import javax.swing.JOptionPane; 028 import javax.swing.JTable; 029 import javax.swing.ListSelectionModel; 030 import javax.swing.event.ListSelectionEvent; 031 import javax.swing.event.ListSelectionListener; 032 import javax.swing.table.DefaultTableModel; 033 034 import org.openstreetmap.josm.Main; 035 import org.openstreetmap.josm.actions.AbstractInfoAction; 036 import org.openstreetmap.josm.data.SelectionChangedListener; 037 import org.openstreetmap.josm.data.osm.DataSet; 038 import org.openstreetmap.josm.data.osm.OsmPrimitive; 039 import org.openstreetmap.josm.data.osm.User; 040 import org.openstreetmap.josm.gui.MapView; 041 import org.openstreetmap.josm.gui.SideButton; 042 import org.openstreetmap.josm.gui.layer.Layer; 043 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 044 import org.openstreetmap.josm.tools.ImageProvider; 045 import org.openstreetmap.josm.tools.Shortcut; 046 047 /** 048 * Displays a dialog with all users who have last edited something in the 049 * selection area, along with the number of objects. 050 * 051 */ 052 public class UserListDialog extends ToggleDialog implements SelectionChangedListener, MapView.LayerChangeListener { 053 054 /** 055 * The display list. 056 */ 057 private JTable userTable; 058 private UserTableModel model; 059 private SelectUsersPrimitivesAction selectionUsersPrimitivesAction; 060 private ShowUserInfoAction showUserInfoAction; 061 062 public UserListDialog() { 063 super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."), 064 Shortcut.registerShortcut("subwindow:authors", tr("Toggle: {0}", tr("Authors")), KeyEvent.VK_A, Shortcut.ALT_SHIFT), 150); 065 066 build(); 067 } 068 069 @Override 070 public void showNotify() { 071 DataSet.addSelectionListener(this); 072 MapView.addLayerChangeListener(this); 073 } 074 075 @Override 076 public void hideNotify() { 077 MapView.removeLayerChangeListener(this); 078 DataSet.removeSelectionListener(this); 079 } 080 081 protected void build() { 082 model = new UserTableModel(); 083 userTable = new JTable(model); 084 userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 085 userTable.addMouseListener(new DoubleClickAdapter()); 086 087 // -- select users primitives action 088 // 089 selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction(); 090 userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction); 091 092 // -- info action 093 // 094 showUserInfoAction = new ShowUserInfoAction(); 095 userTable.getSelectionModel().addListSelectionListener(showUserInfoAction); 096 097 createLayout(userTable, true, Arrays.asList(new SideButton[] { 098 new SideButton(selectionUsersPrimitivesAction), 099 new SideButton(showUserInfoAction) 100 })); 101 } 102 103 /** 104 * Called when the selection in the dataset changed. 105 * @param newSelection The new selection array. 106 */ 107 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 108 refresh(newSelection); 109 } 110 111 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 112 if (newLayer instanceof OsmDataLayer) { 113 refresh(((OsmDataLayer) newLayer).data.getAllSelected()); 114 } else { 115 refresh(null); 116 } 117 } 118 119 public void layerAdded(Layer newLayer) { 120 // do nothing 121 } 122 123 public void layerRemoved(Layer oldLayer) { 124 // do nothing 125 } 126 127 public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) { 128 model.populate(fromPrimitives); 129 if(model.getRowCount() != 0) { 130 setTitle(trn("{0} Author", "{0} Authors", model.getRowCount() , model.getRowCount())); 131 } else { 132 setTitle(tr("Authors")); 133 } 134 } 135 136 @Override 137 public void showDialog() { 138 super.showDialog(); 139 Layer layer = Main.main.getActiveLayer(); 140 if (layer instanceof OsmDataLayer) { 141 refresh(((OsmDataLayer)layer).data.getAllSelected()); 142 } 143 144 } 145 146 class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener{ 147 public SelectUsersPrimitivesAction() { 148 putValue(NAME, tr("Select")); 149 putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user")); 150 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); 151 updateEnabledState(); 152 } 153 154 public void select() { 155 int indexes[] = userTable.getSelectedRows(); 156 if (indexes == null || indexes.length == 0) return; 157 model.selectPrimitivesOwnedBy(userTable.getSelectedRows()); 158 } 159 160 public void actionPerformed(ActionEvent e) { 161 select(); 162 } 163 164 protected void updateEnabledState() { 165 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0); 166 } 167 168 public void valueChanged(ListSelectionEvent e) { 169 updateEnabledState(); 170 } 171 } 172 173 /* 174 * Action for launching the info page of a user 175 */ 176 class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener { 177 178 public ShowUserInfoAction() { 179 super(false); 180 putValue(NAME, tr("Show info")); 181 putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user")); 182 putValue(SMALL_ICON, ImageProvider.get("about")); 183 updateEnabledState(); 184 } 185 186 @Override 187 public void actionPerformed(ActionEvent e) { 188 int rows[] = userTable.getSelectedRows(); 189 if (rows == null || rows.length == 0) return; 190 List<User> users = model.getSelectedUsers(rows); 191 if (users.isEmpty()) return; 192 if (users.size() > 10) { 193 System.out.println(tr("Warning: only launching info browsers for the first {0} of {1} selected users", 10, users.size())); 194 } 195 int num = Math.min(10, users.size()); 196 Iterator<User> it = users.iterator(); 197 while(it.hasNext() && num > 0) { 198 String url = createInfoUrl(it.next()); 199 if (url == null) { 200 break; 201 } 202 launchBrowser(url); 203 num--; 204 } 205 } 206 207 @Override 208 protected String createInfoUrl(Object infoObject) { 209 User user = (User)infoObject; 210 try { 211 return getBaseUserUrl() + "/" + URLEncoder.encode(user.getName(), "UTF-8").replaceAll("\\+", "%20"); 212 } catch(UnsupportedEncodingException e) { 213 e.printStackTrace(); 214 JOptionPane.showMessageDialog( 215 Main.parent, 216 tr("<html>Failed to create an URL because the encoding ''{0}''<br>" 217 + "was missing on this system.</html>", "UTF-8"), 218 tr("Missing encoding"), 219 JOptionPane.ERROR_MESSAGE 220 ); 221 return null; 222 } 223 } 224 225 @Override 226 protected void updateEnabledState() { 227 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0); 228 } 229 230 public void valueChanged(ListSelectionEvent e) { 231 updateEnabledState(); 232 } 233 } 234 235 class DoubleClickAdapter extends MouseAdapter { 236 @Override 237 public void mouseClicked(MouseEvent e) { 238 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) { 239 selectionUsersPrimitivesAction.select(); 240 } 241 } 242 } 243 244 /** 245 * Action for selecting the primitives contributed by the currently selected 246 * users. 247 * 248 */ 249 private static class UserInfo implements Comparable<UserInfo> { 250 public User user; 251 public int count; 252 public double percent; 253 UserInfo(User user, int count, double percent) { 254 this.user=user; 255 this.count=count; 256 this.percent = percent; 257 } 258 public int compareTo(UserInfo o) { 259 if (count < o.count) return 1; 260 if (count > o.count) return -1; 261 if (user== null || user.getName() == null) return 1; 262 if (o.user == null || o.user.getName() == null) return -1; 263 return user.getName().compareTo(o.user.getName()); 264 } 265 266 public String getName() { 267 if (user == null) 268 return tr("<new object>"); 269 return user.getName(); 270 } 271 } 272 273 /** 274 * The table model for the users 275 * 276 */ 277 static class UserTableModel extends DefaultTableModel { 278 private ArrayList<UserInfo> data; 279 280 public UserTableModel() { 281 setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%"}); 282 data = new ArrayList<UserInfo>(); 283 } 284 285 protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) { 286 HashMap<User, Integer> ret = new HashMap<User, Integer>(); 287 if (primitives == null || primitives.isEmpty()) return ret; 288 for (OsmPrimitive primitive: primitives) { 289 if (ret.containsKey(primitive.getUser())) { 290 ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1); 291 } else { 292 ret.put(primitive.getUser(), 1); 293 } 294 } 295 return ret; 296 } 297 298 public void populate(Collection<? extends OsmPrimitive> primitives) { 299 Map<User,Integer> statistics = computeStatistics(primitives); 300 data.clear(); 301 if (primitives != null) { 302 for (Map.Entry<User, Integer> entry: statistics.entrySet()) { 303 data.add(new UserInfo(entry.getKey(), entry.getValue(), (double)entry.getValue() / (double)primitives.size())); 304 } 305 } 306 Collections.sort(data); 307 fireTableDataChanged(); 308 } 309 310 @Override 311 public int getRowCount() { 312 if (data == null) return 0; 313 return data.size(); 314 } 315 316 @Override 317 public Object getValueAt(int row, int column) { 318 UserInfo info = data.get(row); 319 switch(column) { 320 case 0: /* author */ return info.getName() == null ? "" : info.getName(); 321 case 1: /* count */ return info.count; 322 case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent); 323 } 324 return null; 325 } 326 327 @Override 328 public boolean isCellEditable(int row, int column) { 329 return false; 330 } 331 332 public void selectPrimitivesOwnedBy(int [] rows) { 333 Set<User> users= new HashSet<User>(); 334 for (int index: rows) { 335 users.add(data.get(index).user); 336 } 337 Collection<OsmPrimitive> selected = Main.main.getCurrentDataSet().getAllSelected(); 338 Collection<OsmPrimitive> byUser = new LinkedList<OsmPrimitive>(); 339 for (OsmPrimitive p : selected) { 340 if (users.contains(p.getUser())) { 341 byUser.add(p); 342 } 343 } 344 Main.main.getCurrentDataSet().setSelected(byUser); 345 } 346 347 public List<User> getSelectedUsers(int rows[]) { 348 LinkedList<User> ret = new LinkedList<User>(); 349 if (rows == null || rows.length == 0) return ret; 350 for (int row: rows) { 351 if (data.get(row).user == null) { 352 continue; 353 } 354 ret.add(data.get(row).user); 355 } 356 return ret; 357 } 358 } 359 }