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

Revision 4982, 16.0 KB checked in by stoecker, 3 months ago (diff)

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

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