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, 15 months 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.