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

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

add AbstractOsmDataLayer, MainLayerManager.getActiveData, Main.getInProgressISelection

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