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

Last change on this file since 17318 was 17188, checked in by Klumbumbus, 4 years ago

fix #19851 - Fix shortcut names

  • Property svn:eol-style set to native
File size: 13.2 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.data.osm.DataSelectionListener;
33import org.openstreetmap.josm.data.osm.IPrimitive;
34import org.openstreetmap.josm.data.osm.OsmData;
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.datatransfer.ClipboardUtils;
41import org.openstreetmap.josm.gui.layer.Layer;
42import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
43import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
44import org.openstreetmap.josm.gui.layer.OsmDataLayer;
45import org.openstreetmap.josm.gui.util.GuiHelper;
46import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
47import org.openstreetmap.josm.spi.preferences.Config;
48import org.openstreetmap.josm.tools.ImageProvider;
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 class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener {
167
168 /**
169 * Constructs a new {@code SelectUsersPrimitivesAction}.
170 */
171 SelectUsersPrimitivesAction() {
172 putValue(NAME, tr("Select"));
173 putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user"));
174 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
175 updateEnabledState();
176 }
177
178 public void select() {
179 int[] indexes = userTable.getSelectedRows();
180 if (indexes.length == 0)
181 return;
182 model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
183 }
184
185 @Override
186 public void actionPerformed(ActionEvent e) {
187 select();
188 }
189
190 protected void updateEnabledState() {
191 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
192 }
193
194 @Override
195 public void valueChanged(ListSelectionEvent e) {
196 updateEnabledState();
197 }
198 }
199
200 /**
201 * Action for launching the info page of a user.
202 */
203 class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
204
205 ShowUserInfoAction() {
206 super(false);
207 putValue(NAME, tr("Show info"));
208 putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
209 new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
210 updateEnabledState();
211 }
212
213 @Override
214 public void actionPerformed(ActionEvent e) {
215 List<User> users = getSelectedUsers();
216 if (users.isEmpty())
217 return;
218 if (users.size() > 10) {
219 Logging.warn(tr("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 OpenBrowser.displayUrl(url);
229 num--;
230 }
231 }
232
233 @Override
234 protected String createInfoUrl(Object infoObject) {
235 if (infoObject instanceof User) {
236 User user = (User) infoObject;
237 return Config.getUrls().getBaseUserUrl() + '/' + Utils.encodeUrl(user.getName()).replaceAll("\\+", "%20");
238 } else {
239 return null;
240 }
241 }
242
243 @Override
244 protected void updateEnabledState() {
245 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
246 }
247
248 @Override
249 public void valueChanged(ListSelectionEvent e) {
250 updateEnabledState();
251 }
252 }
253
254 class DoubleClickAdapter extends MouseAdapter {
255 @Override
256 public void mouseClicked(MouseEvent e) {
257 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
258 selectionUsersPrimitivesAction.select();
259 }
260 }
261 }
262
263 /**
264 * Action for selecting the primitives contributed by the currently selected users.
265 *
266 */
267 private static class UserInfo implements Comparable<UserInfo> {
268 public final User user;
269 public final int count;
270 public final double percent;
271
272 UserInfo(User user, int count, double percent) {
273 this.user = user;
274 this.count = count;
275 this.percent = percent;
276 }
277
278 @Override
279 public int compareTo(UserInfo o) {
280 if (count < o.count)
281 return 1;
282 if (count > o.count)
283 return -1;
284 if (user == null || user.getName() == null)
285 return 1;
286 if (o.user == null || o.user.getName() == null)
287 return -1;
288 return user.getName().compareTo(o.user.getName());
289 }
290
291 public String getName() {
292 if (user == null)
293 return tr("<new object>");
294 return user.getName();
295 }
296 }
297
298 /**
299 * The table model for the users
300 *
301 */
302 static class UserTableModel extends DefaultTableModel {
303 private final transient List<UserInfo> data;
304
305 UserTableModel() {
306 setColumnIdentifiers(new String[]{tr("Author"), tr("# Objects"), "%"});
307 data = new ArrayList<>();
308 }
309
310 protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
311 Map<User, Integer> ret = new HashMap<>();
312 if (primitives == null || primitives.isEmpty())
313 return ret;
314 for (OsmPrimitive primitive: primitives) {
315 if (ret.containsKey(primitive.getUser())) {
316 ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1);
317 } else {
318 ret.put(primitive.getUser(), 1);
319 }
320 }
321 return ret;
322 }
323
324 public void populate(Collection<? extends OsmPrimitive> primitives) {
325 GuiHelper.assertCallFromEdt();
326 Map<User, Integer> statistics = computeStatistics(primitives);
327 data.clear();
328 if (primitives != null) {
329 for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
330 data.add(new UserInfo(entry.getKey(), entry.getValue(), (double) entry.getValue() / (double) primitives.size()));
331 }
332 }
333 Collections.sort(data);
334 this.fireTableDataChanged();
335 }
336
337 @Override
338 public int getRowCount() {
339 if (data == null)
340 return 0;
341 return data.size();
342 }
343
344 @Override
345 public Object getValueAt(int row, int column) {
346 UserInfo info = data.get(row);
347 switch(column) {
348 case 0: /* author */ return info.getName() == null ? "" : info.getName();
349 case 1: /* count */ return info.count;
350 case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
351 default: return null;
352 }
353 }
354
355 @Override
356 public boolean isCellEditable(int row, int column) {
357 return false;
358 }
359
360 public void selectPrimitivesOwnedBy(int... rows) {
361 Set<User> users = Arrays.stream(rows)
362 .mapToObj(index -> data.get(index).user)
363 .collect(Collectors.toSet());
364 OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData();
365 Collection<? extends IPrimitive> selected = ds.getAllSelected();
366 Collection<IPrimitive> byUser = selected.stream()
367 .filter(p -> users.contains(p.getUser()))
368 .collect(Collectors.toList());
369 ds.setSelected(byUser);
370 }
371
372 public List<User> getSelectedUsers(int... rows) {
373 if (rows == null || rows.length == 0)
374 return Collections.emptyList();
375 return Arrays.stream(rows)
376 .filter(row -> data.get(row).user != null)
377 .mapToObj(row -> data.get(row).user)
378 .collect(Collectors.toList());
379 }
380 }
381}
Note: See TracBrowser for help on using the repository browser.