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

Last change on this file since 5082 was 4982, checked in by stoecker, 12 years ago

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

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