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

Last change on this file since 4475 was 4353, checked in by stoecker, 13 years ago

see #6731 - introduce better layout function for ToggleDialogs, changed author and relation dialog

  • Property svn:eol-style set to native
File size: 15.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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.BorderLayout;
8import java.awt.Component;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseAdapter;
12import java.awt.event.MouseEvent;
13import java.io.UnsupportedEncodingException;
14import java.net.URLEncoder;
15import java.text.NumberFormat;
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.Iterator;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27
28import javax.swing.AbstractAction;
29import javax.swing.ImageIcon;
30import javax.swing.JLabel;
31import javax.swing.JOptionPane;
32import javax.swing.JTable;
33import javax.swing.ListSelectionModel;
34import javax.swing.event.ListSelectionEvent;
35import javax.swing.event.ListSelectionListener;
36import javax.swing.table.DefaultTableCellRenderer;
37import javax.swing.table.DefaultTableModel;
38import javax.swing.table.TableColumnModel;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.actions.AbstractInfoAction;
42import org.openstreetmap.josm.data.SelectionChangedListener;
43import org.openstreetmap.josm.data.osm.DataSet;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.User;
46import org.openstreetmap.josm.gui.MapView;
47import org.openstreetmap.josm.gui.SideButton;
48import org.openstreetmap.josm.gui.layer.Layer;
49import org.openstreetmap.josm.gui.layer.OsmDataLayer;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.Shortcut;
52
53/**
54 * Displays a dialog with all users who have last edited something in the
55 * selection area, along with the number of objects.
56 *
57 */
58public class UserListDialog extends ToggleDialog implements SelectionChangedListener, MapView.LayerChangeListener {
59
60 /**
61 * The display list.
62 */
63 private JTable userTable;
64 private UserTableModel model;
65 private SelectUsersPrimitivesAction selectionUsersPrimitivesAction;
66 private ShowUserInfoAction showUserInfoAction;
67 private LoadRelicensingInformationAction loadRelicensingInformationAction;
68
69 public UserListDialog() {
70 super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."),
71 Shortcut.registerShortcut("subwindow:authors", tr("Toggle: {0}", tr("Authors")), KeyEvent.VK_A, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
72
73 build();
74 }
75
76 @Override
77 public void showNotify() {
78 DataSet.addSelectionListener(this);
79 MapView.addLayerChangeListener(this);
80 }
81
82 @Override
83 public void hideNotify() {
84 MapView.removeLayerChangeListener(this);
85 DataSet.removeSelectionListener(this);
86 }
87
88 protected void build() {
89 model = new UserTableModel();
90 userTable = new JTable(model);
91 userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
92 userTable.addMouseListener(new DoubleClickAdapter());
93 TableColumnModel columnModel = userTable.getColumnModel();
94 columnModel.getColumn(3).setPreferredWidth(20);
95 columnModel.getColumn(3).setCellRenderer(new DefaultTableCellRenderer() {
96 @Override
97 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
98 final JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
99 label.setIcon((ImageIcon)value);
100 label.setText("");
101 return label;
102 };
103 });
104
105 // -- select users primitives action
106 //
107 selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction();
108 userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction);
109
110 // -- info action
111 //
112 showUserInfoAction = new ShowUserInfoAction();
113 userTable.getSelectionModel().addListSelectionListener(showUserInfoAction);
114
115 // -- load relicensing info action
116 loadRelicensingInformationAction = new LoadRelicensingInformationAction();
117
118 createLayout(userTable, true, Arrays.asList(new SideButton[] {
119 new SideButton(selectionUsersPrimitivesAction),
120 new SideButton(showUserInfoAction),
121 new SideButton(loadRelicensingInformationAction)
122 }));
123 }
124
125 /**
126 * Called when the selection in the dataset changed.
127 * @param newSelection The new selection array.
128 */
129 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
130 refresh(newSelection);
131 }
132
133 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
134 if (newLayer instanceof OsmDataLayer) {
135 refresh(((OsmDataLayer) newLayer).data.getSelected());
136 } else {
137 refresh(null);
138 }
139 }
140
141 public void layerAdded(Layer newLayer) {
142 // do nothing
143 }
144
145 public void layerRemoved(Layer oldLayer) {
146 // do nothing
147 }
148
149 public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) {
150 model.populate(fromPrimitives);
151 if(model.getRowCount() != 0) {
152 setTitle(trn("{0} Author", "{0} Authors", model.getRowCount() , model.getRowCount()));
153 } else {
154 setTitle(tr("Authors"));
155 }
156 }
157
158 @Override
159 public void showDialog() {
160 super.showDialog();
161 User.initRelicensingInformation();
162 Layer layer = Main.main.getActiveLayer();
163 if (layer instanceof OsmDataLayer) {
164 refresh(((OsmDataLayer)layer).data.getSelected());
165 }
166
167 }
168
169 class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener{
170 public SelectUsersPrimitivesAction() {
171 putValue(NAME, tr("Select"));
172 putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user"));
173 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
174 updateEnabledState();
175 }
176
177 public void select() {
178 int indexes[] = userTable.getSelectedRows();
179 if (indexes == null || indexes.length == 0) return;
180 model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
181 }
182
183 public void actionPerformed(ActionEvent e) {
184 select();
185 }
186
187 protected void updateEnabledState() {
188 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
189 }
190
191 public void valueChanged(ListSelectionEvent e) {
192 updateEnabledState();
193 }
194 }
195
196 /*
197 * Action for launching the info page of a user
198 */
199 class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
200
201 public ShowUserInfoAction() {
202 super(false);
203 putValue(NAME, tr("Show info"));
204 putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
205 putValue(SMALL_ICON, ImageProvider.get("about"));
206 updateEnabledState();
207 }
208
209 @Override
210 public void actionPerformed(ActionEvent e) {
211 int rows[] = userTable.getSelectedRows();
212 if (rows == null || rows.length == 0) return;
213 List<User> users = model.getSelectedUsers(rows);
214 if (users.isEmpty()) return;
215 if (users.size() > 10) {
216 System.out.println(tr("Warning: only launching info browsers for the first {0} of {1} selected users", 10, users.size()));
217 }
218 int num = Math.min(10, users.size());
219 Iterator<User> it = users.iterator();
220 while(it.hasNext() && num > 0) {
221 String url = createInfoUrl(it.next());
222 if (url == null) {
223 break;
224 }
225 launchBrowser(url);
226 num--;
227 }
228 }
229
230 @Override
231 protected String createInfoUrl(Object infoObject) {
232 User user = (User)infoObject;
233 try {
234 return getBaseUserUrl() + "/" + URLEncoder.encode(user.getName(), "UTF-8").replaceAll("\\+", "%20");
235 } catch(UnsupportedEncodingException e) {
236 e.printStackTrace();
237 JOptionPane.showMessageDialog(
238 Main.parent,
239 tr("<html>Failed to create an URL because the encoding ''{0}''<br>"
240 + "was missing on this system.</html>", "UTF-8"),
241 tr("Missing encoding"),
242 JOptionPane.ERROR_MESSAGE
243 );
244 return null;
245 }
246 }
247
248 @Override
249 protected void updateEnabledState() {
250 setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
251 }
252
253 public void valueChanged(ListSelectionEvent e) {
254 updateEnabledState();
255 }
256 }
257
258 /*
259 */
260 class LoadRelicensingInformationAction extends AbstractAction {
261
262 public LoadRelicensingInformationAction() {
263 super();
264 putValue(NAME, tr("Load CT"));
265 putValue(SHORT_DESCRIPTION, tr("Loads information about relicensing status from the server. Users having agreed to the new contributor terms will show a green check mark."));
266 putValue(SMALL_ICON, ImageProvider.get("about"));
267 }
268
269 @Override
270 public void actionPerformed(ActionEvent e) {
271 User.loadRelicensingInformation(true);
272 Layer layer = Main.main.getActiveLayer();
273 if (layer instanceof OsmDataLayer) {
274 refresh(((OsmDataLayer)layer).data.getSelected());
275 }
276 setEnabled(false);
277 }
278 }
279
280 class DoubleClickAdapter extends MouseAdapter {
281 @Override
282 public void mouseClicked(MouseEvent e) {
283 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) {
284 selectionUsersPrimitivesAction.select();
285 }
286 }
287 }
288
289 /**
290 * Action for selecting the primitives contributed by the currently selected
291 * users.
292 *
293 */
294 private static class UserInfo implements Comparable<UserInfo> {
295 public User user;
296 public int count;
297 public double percent;
298 UserInfo(User user, int count, double percent) {
299 this.user=user;
300 this.count=count;
301 this.percent = percent;
302 }
303 public int compareTo(UserInfo o) {
304 if (count < o.count) return 1;
305 if (count > o.count) return -1;
306 if (user== null || user.getName() == null) return 1;
307 if (o.user == null || o.user.getName() == null) return -1;
308 return user.getName().compareTo(o.user.getName());
309 }
310
311 public String getName() {
312 if (user == null)
313 return tr("<new object>");
314 return user.getName();
315 }
316
317 public int getRelicensingStatus() {
318 if (user == null)
319 return User.STATUS_UNKNOWN;
320 return user.getRelicensingStatus();
321 }
322 }
323
324 /**
325 * The table model for the users
326 *
327 */
328 static class UserTableModel extends DefaultTableModel {
329 private ArrayList<UserInfo> data;
330 private ImageIcon greenCheckmark;
331 private ImageIcon greyCheckmark;
332 private ImageIcon redX;
333
334 public UserTableModel() {
335 setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%", tr("CT")});
336 data = new ArrayList<UserInfo>();
337 greenCheckmark = ImageProvider.get("misc", "green_check.png");
338 greyCheckmark = ImageProvider.get("misc", "grey_check.png");
339 redX = ImageProvider.get("misc", "red_x.png");
340 }
341
342 protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
343 HashMap<User, Integer> ret = new HashMap<User, Integer>();
344 if (primitives == null || primitives.isEmpty()) return ret;
345 for (OsmPrimitive primitive: primitives) {
346 if (ret.containsKey(primitive.getUser())) {
347 ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1);
348 } else {
349 ret.put(primitive.getUser(), 1);
350 }
351 }
352 return ret;
353 }
354
355 public void populate(Collection<? extends OsmPrimitive> primitives) {
356 Map<User,Integer> statistics = computeStatistics(primitives);
357 data.clear();
358 if (primitives != null) {
359 for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
360 data.add(new UserInfo(entry.getKey(), entry.getValue(), (double)entry.getValue() / (double)primitives.size()));
361 }
362 }
363 Collections.sort(data);
364 fireTableDataChanged();
365 }
366
367 @Override
368 public int getRowCount() {
369 if (data == null) return 0;
370 return data.size();
371 }
372
373 @Override
374 public Object getValueAt(int row, int column) {
375 UserInfo info = data.get(row);
376 switch(column) {
377 case 0: /* author */ return info.getName() == null ? "" : info.getName();
378 case 1: /* count */ return info.count;
379 case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
380 case 3: /* relicensing status */
381 switch(info.getRelicensingStatus()) {
382 case User.STATUS_AGREED: return greenCheckmark;
383 case User.STATUS_AUTO_AGREED: return greyCheckmark;
384 case User.STATUS_NOT_AGREED: return redX;
385 default: return null;
386 }
387 }
388 return null;
389 }
390
391 @Override
392 public boolean isCellEditable(int row, int column) {
393 return false;
394 }
395
396 public void selectPrimitivesOwnedBy(int [] rows) {
397 Set<User> users= new HashSet<User>();
398 for (int index: rows) {
399 users.add(data.get(index).user);
400 }
401 Collection<OsmPrimitive> selected = Main.main.getCurrentDataSet().getSelected();
402 Collection<OsmPrimitive> byUser = new LinkedList<OsmPrimitive>();
403 for (OsmPrimitive p : selected) {
404 if (users.contains(p.getUser())) {
405 byUser.add(p);
406 }
407 }
408 Main.main.getCurrentDataSet().setSelected(byUser);
409 }
410
411 public List<User> getSelectedUsers(int rows[]) {
412 LinkedList<User> ret = new LinkedList<User>();
413 if (rows == null || rows.length == 0) return ret;
414 for (int row: rows) {
415 if (data.get(row).user == null) {
416 continue;
417 }
418 ret.add(data.get(row).user);
419 }
420 return ret;
421 }
422 }
423}
Note: See TracBrowser for help on using the repository browser.