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

Last change on this file since 19050 was 19050, checked in by taylor.smock, 4 weeks ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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