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

Last change on this file since 13434 was 13434, checked in by Don-vip, 6 years ago

see #8039, see #10456 - support read-only data layers

  • Property svn:eol-style set to native
File size: 12.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.awt.event.MouseAdapter;
10import java.awt.event.MouseEvent;
11import java.text.NumberFormat;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashMap;
17import java.util.HashSet;
18import java.util.Iterator;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23
24import javax.swing.AbstractAction;
25import javax.swing.JTable;
26import javax.swing.ListSelectionModel;
27import javax.swing.event.ListSelectionEvent;
28import javax.swing.event.ListSelectionListener;
29import javax.swing.table.DefaultTableModel;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.actions.AbstractInfoAction;
33import org.openstreetmap.josm.data.osm.DataSelectionListener;
34import org.openstreetmap.josm.data.osm.DataSet;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.data.osm.User;
37import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.SideButton;
40import org.openstreetmap.josm.gui.layer.Layer;
41import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
42import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.gui.util.GuiHelper;
45import org.openstreetmap.josm.tools.ImageProvider;
46import org.openstreetmap.josm.tools.Logging;
47import org.openstreetmap.josm.tools.OpenBrowser;
48import org.openstreetmap.josm.tools.Shortcut;
49import org.openstreetmap.josm.tools.Utils;
50
51/**
52 * Displays a dialog with all users who have last edited something in the
53 * selection area, along with the number of objects.
54 *
55 */
56public class UserListDialog extends ToggleDialog implements DataSelectionListener, ActiveLayerChangeListener {
57
58 /**
59 * The display list.
60 */
61 private JTable userTable;
62 private UserTableModel model;
63 private SelectUsersPrimitivesAction selectionUsersPrimitivesAction;
64
65 /**
66 * Constructs a new {@code UserListDialog}.
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 build();
72 }
73
74 @Override
75 public void showNotify() {
76 SelectionEventManager.getInstance().addSelectionListenerForEdt(this);
77 MainApplication.getLayerManager().addActiveLayerChangeListener(this);
78 }
79
80 @Override
81 public void hideNotify() {
82 MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
83 SelectionEventManager.getInstance().removeSelectionListener(this);
84 }
85
86 protected void build() {
87 model = new UserTableModel();
88 userTable = new JTable(model);
89 userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
90 userTable.addMouseListener(new DoubleClickAdapter());
91
92 // -- select users primitives action
93 //
94 selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction();
95 userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction);
96
97 // -- info action
98 //
99 ShowUserInfoAction showUserInfoAction = new ShowUserInfoAction();
100 userTable.getSelectionModel().addListSelectionListener(showUserInfoAction);
101
102 createLayout(userTable, true, Arrays.asList(
103 new SideButton(selectionUsersPrimitivesAction),
104 new SideButton(showUserInfoAction)
105 ));
106 }
107
108 @Override
109 public void selectionChanged(SelectionChangeEvent event) {
110 refresh(event.getSelection());
111 }
112
113 @Override
114 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
115 Layer activeLayer = e.getSource().getActiveLayer();
116 refreshForActiveLayer(activeLayer);
117 }
118
119 private void refreshForActiveLayer(Layer activeLayer) {
120 if (activeLayer instanceof OsmDataLayer) {
121 refresh(((OsmDataLayer) activeLayer).data.getAllSelected());
122 } else {
123 refresh(null);
124 }
125 }
126
127 /**
128 * Refreshes user list from given collection of OSM primitives.
129 * @param fromPrimitives OSM primitives to fetch users from
130 */
131 public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) {
132 GuiHelper.runInEDT(() -> {
133 model.populate(fromPrimitives);
134 if (model.getRowCount() != 0) {
135 setTitle(trn("{0} Author", "{0} Authors", model.getRowCount(), model.getRowCount()));
136 } else {
137 setTitle(tr("Authors"));
138 }
139 });
140 }
141
142 @Override
143 public void showDialog() {
144 super.showDialog();
145 refreshForActiveLayer(MainApplication.getLayerManager().getActiveLayer());
146 }
147
148 class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener {
149
150 /**
151 * Constructs a new {@code SelectUsersPrimitivesAction}.
152 */
153 SelectUsersPrimitivesAction() {
154 putValue(NAME, tr("Select"));
155 putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user"));
156 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
157 updateEnabledState();
158 }
159
160 public void select() {
161 int[] indexes = userTable.getSelectedRows();
162 if (indexes.length == 0)
163 return;
164 model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
165 }
166
167 @Override
168 public void actionPerformed(ActionEvent e) {
169 select();
170 }
171
172 protected void updateEnabledState() {
173 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
174 }
175
176 @Override
177 public void valueChanged(ListSelectionEvent e) {
178 updateEnabledState();
179 }
180 }
181
182 /**
183 * Action for launching the info page of a user.
184 */
185 class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
186
187 ShowUserInfoAction() {
188 super(false);
189 putValue(NAME, tr("Show info"));
190 putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
191 new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
192 updateEnabledState();
193 }
194
195 @Override
196 public void actionPerformed(ActionEvent e) {
197 int[] rows = userTable.getSelectedRows();
198 if (rows.length == 0)
199 return;
200 List<User> users = model.getSelectedUsers(rows);
201 if (users.isEmpty())
202 return;
203 if (users.size() > 10) {
204 Logging.warn(tr("Only launching info browsers for the first {0} of {1} selected users", 10, users.size()));
205 }
206 int num = Math.min(10, users.size());
207 Iterator<User> it = users.iterator();
208 while (it.hasNext() && num > 0) {
209 String url = createInfoUrl(it.next());
210 if (url == null) {
211 break;
212 }
213 OpenBrowser.displayUrl(url);
214 num--;
215 }
216 }
217
218 @Override
219 protected String createInfoUrl(Object infoObject) {
220 if (infoObject instanceof User) {
221 User user = (User) infoObject;
222 return Main.getBaseUserUrl() + '/' + Utils.encodeUrl(user.getName()).replaceAll("\\+", "%20");
223 } else {
224 return null;
225 }
226 }
227
228 @Override
229 protected void updateEnabledState() {
230 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
231 }
232
233 @Override
234 public void valueChanged(ListSelectionEvent e) {
235 updateEnabledState();
236 }
237 }
238
239 class DoubleClickAdapter extends MouseAdapter {
240 @Override
241 public void mouseClicked(MouseEvent e) {
242 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
243 selectionUsersPrimitivesAction.select();
244 }
245 }
246 }
247
248 /**
249 * Action for selecting the primitives contributed by the currently selected users.
250 *
251 */
252 private static class UserInfo implements Comparable<UserInfo> {
253 public final User user;
254 public final int count;
255 public final double percent;
256
257 UserInfo(User user, int count, double percent) {
258 this.user = user;
259 this.count = count;
260 this.percent = percent;
261 }
262
263 @Override
264 public int compareTo(UserInfo o) {
265 if (count < o.count)
266 return 1;
267 if (count > o.count)
268 return -1;
269 if (user == null || user.getName() == null)
270 return 1;
271 if (o.user == null || o.user.getName() == null)
272 return -1;
273 return user.getName().compareTo(o.user.getName());
274 }
275
276 public String getName() {
277 if (user == null)
278 return tr("<new object>");
279 return user.getName();
280 }
281 }
282
283 /**
284 * The table model for the users
285 *
286 */
287 static class UserTableModel extends DefaultTableModel {
288 private final transient List<UserInfo> data;
289
290 UserTableModel() {
291 setColumnIdentifiers(new String[]{tr("Author"), tr("# Objects"), "%"});
292 data = new ArrayList<>();
293 }
294
295 protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
296 Map<User, Integer> ret = new HashMap<>();
297 if (primitives == null || primitives.isEmpty())
298 return ret;
299 for (OsmPrimitive primitive: primitives) {
300 if (ret.containsKey(primitive.getUser())) {
301 ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1);
302 } else {
303 ret.put(primitive.getUser(), 1);
304 }
305 }
306 return ret;
307 }
308
309 public void populate(Collection<? extends OsmPrimitive> primitives) {
310 GuiHelper.assertCallFromEdt();
311 Map<User, Integer> statistics = computeStatistics(primitives);
312 data.clear();
313 if (primitives != null) {
314 for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
315 data.add(new UserInfo(entry.getKey(), entry.getValue(), (double) entry.getValue() / (double) primitives.size()));
316 }
317 }
318 Collections.sort(data);
319 this.fireTableDataChanged();
320 }
321
322 @Override
323 public int getRowCount() {
324 if (data == null)
325 return 0;
326 return data.size();
327 }
328
329 @Override
330 public Object getValueAt(int row, int column) {
331 UserInfo info = data.get(row);
332 switch(column) {
333 case 0: /* author */ return info.getName() == null ? "" : info.getName();
334 case 1: /* count */ return info.count;
335 case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
336 default: return null;
337 }
338 }
339
340 @Override
341 public boolean isCellEditable(int row, int column) {
342 return false;
343 }
344
345 public void selectPrimitivesOwnedBy(int... rows) {
346 Set<User> users = new HashSet<>();
347 for (int index: rows) {
348 users.add(data.get(index).user);
349 }
350 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
351 Collection<OsmPrimitive> selected = ds.getAllSelected();
352 Collection<OsmPrimitive> byUser = new LinkedList<>();
353 for (OsmPrimitive p : selected) {
354 if (users.contains(p.getUser())) {
355 byUser.add(p);
356 }
357 }
358 ds.setSelected(byUser);
359 }
360
361 public List<User> getSelectedUsers(int... rows) {
362 List<User> ret = new LinkedList<>();
363 if (rows == null || rows.length == 0)
364 return ret;
365 for (int row: rows) {
366 if (data.get(row).user == null) {
367 continue;
368 }
369 ret.add(data.get(row).user);
370 }
371 return ret;
372 }
373 }
374}
Note: See TracBrowser for help on using the repository browser.