source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java@ 3995

Last change on this file since 3995 was 3995, checked in by mjulius, 13 years ago

fix #3590 - Primitives or objects: Pick one

  • Property svn:eol-style set to native
File size: 15.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseAdapter;
12import java.awt.event.MouseEvent;
13import java.io.UnsupportedEncodingException;
14import java.net.URLEncoder;
15import java.text.NumberFormat;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.HashMap;
20import java.util.HashSet;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26
27import javax.swing.AbstractAction;
28import javax.swing.ImageIcon;
29import javax.swing.JLabel;
30import javax.swing.JOptionPane;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JTable;
34import javax.swing.ListSelectionModel;
35import javax.swing.event.ListSelectionEvent;
36import javax.swing.event.ListSelectionListener;
37import javax.swing.table.DefaultTableCellRenderer;
38import javax.swing.table.DefaultTableModel;
39import javax.swing.table.TableColumnModel;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.actions.AbstractInfoAction;
43import org.openstreetmap.josm.data.SelectionChangedListener;
44import org.openstreetmap.josm.data.osm.DataSet;
45import org.openstreetmap.josm.data.osm.OsmPrimitive;
46import org.openstreetmap.josm.data.osm.User;
47import org.openstreetmap.josm.gui.MapView;
48import org.openstreetmap.josm.gui.SideButton;
49import org.openstreetmap.josm.gui.layer.Layer;
50import org.openstreetmap.josm.gui.layer.OsmDataLayer;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.Shortcut;
53
54/**
55 * Displays a dialog with all users who have last edited something in the
56 * selection area, along with the number of objects.
57 *
58 */
59public class UserListDialog extends ToggleDialog implements SelectionChangedListener, MapView.LayerChangeListener {
60
61 /**
62 * The display list.
63 */
64 private JTable userTable;
65 private UserTableModel model;
66 private SelectUsersPrimitivesAction selectionUsersPrimitivesAction;
67 private ShowUserInfoAction showUserInfoAction;
68 private LoadRelicensingInformationAction loadRelicensingInformationAction;
69
70 public UserListDialog() {
71 super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."),
72 Shortcut.registerShortcut("subwindow:authors", tr("Toggle: {0}", tr("Authors")), KeyEvent.VK_A, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
73
74 build();
75 }
76
77 @Override
78 public void showNotify() {
79 DataSet.addSelectionListener(this);
80 MapView.addLayerChangeListener(this);
81 }
82
83 @Override
84 public void hideNotify() {
85 MapView.removeLayerChangeListener(this);
86 DataSet.removeSelectionListener(this);
87 }
88
89 protected JPanel buildButtonRow() {
90 JPanel pnl = getButtonPanel(2);
91
92 // -- select users primitives action
93 //
94 selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction();
95 userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction);
96 pnl.add(new SideButton(selectionUsersPrimitivesAction));
97
98 // -- info action
99 //
100 showUserInfoAction = new ShowUserInfoAction();
101 userTable.getSelectionModel().addListSelectionListener(showUserInfoAction);
102 pnl.add(new SideButton(showUserInfoAction));
103
104 // -- load relicensing info action
105 loadRelicensingInformationAction = new LoadRelicensingInformationAction();
106 pnl.add(new SideButton(loadRelicensingInformationAction));
107 return pnl;
108 }
109
110 protected void build() {
111 JPanel pnl = new JPanel();
112 pnl.setLayout(new BorderLayout());
113 model = new UserTableModel();
114 userTable = new JTable(model);
115 userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
116 TableColumnModel columnModel = userTable.getColumnModel();
117 columnModel.getColumn(3).setPreferredWidth(20);
118 columnModel.getColumn(3).setCellRenderer(new DefaultTableCellRenderer() {
119 @Override
120 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
121 final JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
122 label.setIcon((ImageIcon)value);
123 label.setText("");
124 return label;
125 };
126 });
127 pnl.add(new JScrollPane(userTable), BorderLayout.CENTER);
128
129 // -- the button row
130 pnl.add(buildButtonRow(), BorderLayout.SOUTH);
131 userTable.addMouseListener(new DoubleClickAdapter());
132 add(pnl, BorderLayout.CENTER);
133 }
134
135 /**
136 * Called when the selection in the dataset changed.
137 * @param newSelection The new selection array.
138 */
139 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
140 refresh(newSelection);
141 }
142
143 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
144 if (newLayer instanceof OsmDataLayer) {
145 refresh(((OsmDataLayer) newLayer).data.getSelected());
146 } else {
147 refresh(null);
148 }
149 }
150
151 public void layerAdded(Layer newLayer) {
152 // do nothing
153 }
154
155 public void layerRemoved(Layer oldLayer) {
156 // do nothing
157 }
158
159 public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) {
160 model.populate(fromPrimitives);
161 if(model.getRowCount() != 0) {
162 setTitle(trn("{0} Author", "{0} Authors", model.getRowCount() , model.getRowCount()));
163 } else {
164 setTitle(tr("Authors"));
165 }
166 }
167
168 @Override
169 public void showDialog() {
170 super.showDialog();
171 Layer layer = Main.main.getActiveLayer();
172 if (layer instanceof OsmDataLayer) {
173 refresh(((OsmDataLayer)layer).data.getSelected());
174 }
175
176 }
177
178 class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener{
179 public SelectUsersPrimitivesAction() {
180 putValue(NAME, tr("Select"));
181 putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user"));
182 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
183 updateEnabledState();
184 }
185
186 public void select() {
187 int indexes[] = userTable.getSelectedRows();
188 if (indexes == null || indexes.length == 0) return;
189 model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
190 }
191
192 public void actionPerformed(ActionEvent e) {
193 select();
194 }
195
196 protected void updateEnabledState() {
197 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
198 }
199
200 public void valueChanged(ListSelectionEvent e) {
201 updateEnabledState();
202 }
203 }
204
205 /*
206 * Action for launching the info page of a user
207 */
208 class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
209
210 public ShowUserInfoAction() {
211 super(false);
212 putValue(NAME, tr("Show info"));
213 putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
214 putValue(SMALL_ICON, ImageProvider.get("about"));
215 updateEnabledState();
216 }
217
218 @Override
219 public void actionPerformed(ActionEvent e) {
220 int rows[] = userTable.getSelectedRows();
221 if (rows == null || rows.length == 0) return;
222 List<User> users = model.getSelectedUsers(rows);
223 if (users.isEmpty()) return;
224 if (users.size() > 10) {
225 System.out.println(tr("Warning: only launching info browsers for the first {0} of {1} selected users", 10, users.size()));
226 }
227 int num = Math.min(10, users.size());
228 Iterator<User> it = users.iterator();
229 while(it.hasNext() && num > 0) {
230 String url = createInfoUrl(it.next());
231 if (url == null) {
232 break;
233 }
234 launchBrowser(url);
235 num--;
236 }
237 }
238
239 @Override
240 protected String createInfoUrl(Object infoObject) {
241 User user = (User)infoObject;
242 try {
243 return getBaseUserUrl() + "/" + URLEncoder.encode(user.getName(), "UTF-8").replaceAll("\\+", "%20");
244 } catch(UnsupportedEncodingException e) {
245 e.printStackTrace();
246 JOptionPane.showMessageDialog(
247 Main.parent,
248 tr("<html>Failed to create an URL because the encoding ''{0}''<br>"
249 + "was missing on this system.</html>", "UTF-8"),
250 tr("Missing encoding"),
251 JOptionPane.ERROR_MESSAGE
252 );
253 return null;
254 }
255 }
256
257 @Override
258 protected void updateEnabledState() {
259 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
260 }
261
262 public void valueChanged(ListSelectionEvent e) {
263 updateEnabledState();
264 }
265 }
266
267 /*
268 */
269 class LoadRelicensingInformationAction extends AbstractAction {
270
271 public LoadRelicensingInformationAction() {
272 super();
273 putValue(NAME, tr("Load CT"));
274 putValue(SHORT_DESCRIPTION, tr("Loads information about relicensing status from the server. Users having agreed to the new contributor terms will show a green check mark."));
275 putValue(SMALL_ICON, ImageProvider.get("about"));
276 }
277
278 @Override
279 public void actionPerformed(ActionEvent e) {
280 User.loadRelicensingInformation();
281 Layer layer = Main.main.getActiveLayer();
282 if (layer instanceof OsmDataLayer) {
283 refresh(((OsmDataLayer)layer).data.getSelected());
284 }
285 setEnabled(false);
286 }
287 }
288
289 class DoubleClickAdapter extends MouseAdapter {
290 @Override
291 public void mouseClicked(MouseEvent e) {
292 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) {
293 selectionUsersPrimitivesAction.select();
294 }
295 }
296 }
297
298 /**
299 * Action for selecting the primitives contributed by the currently selected
300 * users.
301 *
302 */
303 private static class UserInfo implements Comparable<UserInfo> {
304 public User user;
305 public int count;
306 public double percent;
307 UserInfo(User user, int count, double percent) {
308 this.user=user;
309 this.count=count;
310 this.percent = percent;
311 }
312 public int compareTo(UserInfo o) {
313 if (count < o.count) return 1;
314 if (count > o.count) return -1;
315 if (user== null || user.getName() == null) return 1;
316 if (o.user == null || o.user.getName() == null) return -1;
317 return user.getName().compareTo(o.user.getName());
318 }
319
320 public String getName() {
321 if (user == null)
322 return tr("<new object>");
323 return user.getName();
324 }
325
326 public int getRelicensingStatus() {
327 if (user == null)
328 return User.STATUS_UNKNOWN;
329 return user.getRelicensingStatus();
330 }
331 }
332
333 /**
334 * The table model for the users
335 *
336 */
337 static class UserTableModel extends DefaultTableModel {
338 private ArrayList<UserInfo> data;
339 private ImageIcon greenCheckmark;
340
341 public UserTableModel() {
342 setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%", tr("CT")});
343 data = new ArrayList<UserInfo>();
344 greenCheckmark = ImageProvider.get("misc", "green_check.png");
345 }
346
347 protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
348 HashMap<User, Integer> ret = new HashMap<User, Integer>();
349 if (primitives == null || primitives.isEmpty()) return ret;
350 for (OsmPrimitive primitive: primitives) {
351 if (ret.containsKey(primitive.getUser())) {
352 ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1);
353 } else {
354 ret.put(primitive.getUser(), 1);
355 }
356 }
357 return ret;
358 }
359
360 public void populate(Collection<? extends OsmPrimitive> primitives) {
361 Map<User,Integer> statistics = computeStatistics(primitives);
362 data.clear();
363 if (primitives != null) {
364 for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
365 data.add(new UserInfo(entry.getKey(), entry.getValue(), (double)entry.getValue() / (double)primitives.size()));
366 }
367 }
368 Collections.sort(data);
369 fireTableDataChanged();
370 }
371
372 @Override
373 public int getRowCount() {
374 if (data == null) return 0;
375 return data.size();
376 }
377
378 @Override
379 public Object getValueAt(int row, int column) {
380 UserInfo info = data.get(row);
381 switch(column) {
382 case 0: /* author */ return info.getName() == null ? "" : info.getName();
383 case 1: /* count */ return info.count;
384 case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
385 case 3: /* relicensing status */
386 if (info.getRelicensingStatus() == User.STATUS_AGREED) return greenCheckmark;
387 if (info.getRelicensingStatus() == User.STATUS_AUTO_AGREED) return greenCheckmark;
388 return null;
389 }
390 return null;
391 }
392
393 @Override
394 public boolean isCellEditable(int row, int column) {
395 return false;
396 }
397
398 public void selectPrimitivesOwnedBy(int [] rows) {
399 Set<User> users= new HashSet<User>();
400 for (int index: rows) {
401 users.add(data.get(index).user);
402 }
403 Collection<OsmPrimitive> selected = Main.main.getCurrentDataSet().getSelected();
404 Collection<OsmPrimitive> byUser = new LinkedList<OsmPrimitive>();
405 for (OsmPrimitive p : selected) {
406 if (users.contains(p.getUser())) {
407 byUser.add(p);
408 }
409 }
410 Main.main.getCurrentDataSet().setSelected(byUser);
411 }
412
413 public List<User> getSelectedUsers(int rows[]) {
414 LinkedList<User> ret = new LinkedList<User>();
415 if (rows == null || rows.length == 0) return ret;
416 for (int row: rows) {
417 if (data.get(row).user == null) {
418 continue;
419 }
420 ret.add(data.get(row).user);
421 }
422 return ret;
423 }
424 }
425}
Note: See TracBrowser for help on using the repository browser.