001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.gui.dialogs;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    import static org.openstreetmap.josm.tools.I18n.trn;
006    
007    import java.awt.event.ActionEvent;
008    import java.awt.event.KeyEvent;
009    import java.awt.event.MouseAdapter;
010    import java.awt.event.MouseEvent;
011    import java.io.UnsupportedEncodingException;
012    import java.net.URLEncoder;
013    import java.text.NumberFormat;
014    import java.util.ArrayList;
015    import java.util.Arrays;
016    import java.util.Collection;
017    import java.util.Collections;
018    import java.util.HashMap;
019    import java.util.HashSet;
020    import java.util.Iterator;
021    import java.util.LinkedList;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import javax.swing.AbstractAction;
027    import javax.swing.JOptionPane;
028    import javax.swing.JTable;
029    import javax.swing.ListSelectionModel;
030    import javax.swing.event.ListSelectionEvent;
031    import javax.swing.event.ListSelectionListener;
032    import javax.swing.table.DefaultTableModel;
033    
034    import org.openstreetmap.josm.Main;
035    import org.openstreetmap.josm.actions.AbstractInfoAction;
036    import org.openstreetmap.josm.data.SelectionChangedListener;
037    import org.openstreetmap.josm.data.osm.DataSet;
038    import org.openstreetmap.josm.data.osm.OsmPrimitive;
039    import org.openstreetmap.josm.data.osm.User;
040    import org.openstreetmap.josm.gui.MapView;
041    import org.openstreetmap.josm.gui.SideButton;
042    import org.openstreetmap.josm.gui.layer.Layer;
043    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
044    import org.openstreetmap.josm.tools.ImageProvider;
045    import org.openstreetmap.josm.tools.Shortcut;
046    
047    /**
048     * Displays a dialog with all users who have last edited something in the
049     * selection area, along with the number of objects.
050     *
051     */
052    public class UserListDialog extends ToggleDialog implements SelectionChangedListener, MapView.LayerChangeListener {
053    
054        /**
055         * The display list.
056         */
057        private JTable userTable;
058        private UserTableModel model;
059        private SelectUsersPrimitivesAction selectionUsersPrimitivesAction;
060        private ShowUserInfoAction showUserInfoAction;
061    
062        public UserListDialog() {
063            super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."),
064                    Shortcut.registerShortcut("subwindow:authors", tr("Toggle: {0}", tr("Authors")), KeyEvent.VK_A, Shortcut.ALT_SHIFT), 150);
065    
066            build();
067        }
068    
069        @Override
070        public void showNotify() {
071            DataSet.addSelectionListener(this);
072            MapView.addLayerChangeListener(this);
073        }
074    
075        @Override
076        public void hideNotify() {
077            MapView.removeLayerChangeListener(this);
078            DataSet.removeSelectionListener(this);
079        }
080    
081        protected void build() {
082            model = new UserTableModel();
083            userTable = new JTable(model);
084            userTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
085            userTable.addMouseListener(new DoubleClickAdapter());
086    
087            // -- select users primitives action
088            //
089            selectionUsersPrimitivesAction = new SelectUsersPrimitivesAction();
090            userTable.getSelectionModel().addListSelectionListener(selectionUsersPrimitivesAction);
091    
092            // -- info action
093            //
094            showUserInfoAction = new ShowUserInfoAction();
095            userTable.getSelectionModel().addListSelectionListener(showUserInfoAction);
096    
097            createLayout(userTable, true, Arrays.asList(new SideButton[] {
098                new SideButton(selectionUsersPrimitivesAction),
099                new SideButton(showUserInfoAction)
100            }));
101        }
102    
103        /**
104         * Called when the selection in the dataset changed.
105         * @param newSelection The new selection array.
106         */
107        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
108            refresh(newSelection);
109        }
110    
111        public void activeLayerChange(Layer oldLayer, Layer newLayer) {
112            if (newLayer instanceof OsmDataLayer) {
113                refresh(((OsmDataLayer) newLayer).data.getAllSelected());
114            } else {
115                refresh(null);
116            }
117        }
118    
119        public void layerAdded(Layer newLayer) {
120            // do nothing
121        }
122    
123        public void layerRemoved(Layer oldLayer) {
124            // do nothing
125        }
126    
127        public void refresh(Collection<? extends OsmPrimitive> fromPrimitives) {
128            model.populate(fromPrimitives);
129            if(model.getRowCount() != 0) {
130                setTitle(trn("{0} Author", "{0} Authors", model.getRowCount() , model.getRowCount()));
131            } else {
132                setTitle(tr("Authors"));
133            }
134        }
135    
136        @Override
137        public void showDialog() {
138            super.showDialog();
139            Layer layer = Main.main.getActiveLayer();
140            if (layer instanceof OsmDataLayer) {
141                refresh(((OsmDataLayer)layer).data.getAllSelected());
142            }
143    
144        }
145    
146        class SelectUsersPrimitivesAction extends AbstractAction implements ListSelectionListener{
147            public SelectUsersPrimitivesAction() {
148                putValue(NAME, tr("Select"));
149                putValue(SHORT_DESCRIPTION, tr("Select objects submitted by this user"));
150                putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
151                updateEnabledState();
152            }
153    
154            public void select() {
155                int indexes[] = userTable.getSelectedRows();
156                if (indexes == null || indexes.length == 0) return;
157                model.selectPrimitivesOwnedBy(userTable.getSelectedRows());
158            }
159    
160            public void actionPerformed(ActionEvent e) {
161                select();
162            }
163    
164            protected void updateEnabledState() {
165                setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
166            }
167    
168            public void valueChanged(ListSelectionEvent e) {
169                updateEnabledState();
170            }
171        }
172    
173        /*
174         * Action for launching the info page of a user
175         */
176        class ShowUserInfoAction extends AbstractInfoAction implements ListSelectionListener {
177    
178            public ShowUserInfoAction() {
179                super(false);
180                putValue(NAME, tr("Show info"));
181                putValue(SHORT_DESCRIPTION, tr("Launches a browser with information about the user"));
182                putValue(SMALL_ICON, ImageProvider.get("about"));
183                updateEnabledState();
184            }
185    
186            @Override
187            public void actionPerformed(ActionEvent e) {
188                int rows[] = userTable.getSelectedRows();
189                if (rows == null || rows.length == 0) return;
190                List<User> users = model.getSelectedUsers(rows);
191                if (users.isEmpty()) return;
192                if (users.size() > 10) {
193                    System.out.println(tr("Warning: only launching info browsers for the first {0} of {1} selected users", 10, users.size()));
194                }
195                int num = Math.min(10, users.size());
196                Iterator<User> it = users.iterator();
197                while(it.hasNext() && num > 0) {
198                    String url = createInfoUrl(it.next());
199                    if (url == null) {
200                        break;
201                    }
202                    launchBrowser(url);
203                    num--;
204                }
205            }
206    
207            @Override
208            protected String createInfoUrl(Object infoObject) {
209                User user = (User)infoObject;
210                try {
211                    return getBaseUserUrl() + "/" + URLEncoder.encode(user.getName(), "UTF-8").replaceAll("\\+", "%20");
212                } catch(UnsupportedEncodingException e) {
213                    e.printStackTrace();
214                    JOptionPane.showMessageDialog(
215                            Main.parent,
216                            tr("<html>Failed to create an URL because the encoding ''{0}''<br>"
217                                    + "was missing on this system.</html>", "UTF-8"),
218                                    tr("Missing encoding"),
219                                    JOptionPane.ERROR_MESSAGE
220                    );
221                    return null;
222                }
223            }
224    
225            @Override
226            protected void updateEnabledState() {
227                setEnabled(userTable != null && userTable.getSelectedRowCount() > 0);
228            }
229    
230            public void valueChanged(ListSelectionEvent e) {
231                updateEnabledState();
232            }
233        }
234    
235        class DoubleClickAdapter extends MouseAdapter {
236            @Override
237            public void mouseClicked(MouseEvent e) {
238                if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount()==2) {
239                    selectionUsersPrimitivesAction.select();
240                }
241            }
242        }
243    
244        /**
245         * Action for selecting the primitives contributed by the currently selected
246         * users.
247         *
248         */
249        private static class UserInfo implements Comparable<UserInfo> {
250            public User user;
251            public int count;
252            public double percent;
253            UserInfo(User user, int count, double percent) {
254                this.user=user;
255                this.count=count;
256                this.percent = percent;
257            }
258            public int compareTo(UserInfo o) {
259                if (count < o.count) return 1;
260                if (count > o.count) return -1;
261                if (user== null || user.getName() == null) return 1;
262                if (o.user == null || o.user.getName() == null) return -1;
263                return user.getName().compareTo(o.user.getName());
264            }
265    
266            public String getName() {
267                if (user == null)
268                    return tr("<new object>");
269                return user.getName();
270            }
271        }
272    
273        /**
274         * The table model for the users
275         *
276         */
277        static class UserTableModel extends DefaultTableModel {
278            private ArrayList<UserInfo> data;
279    
280            public UserTableModel() {
281                setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%"});
282                data = new ArrayList<UserInfo>();
283            }
284    
285            protected Map<User, Integer> computeStatistics(Collection<? extends OsmPrimitive> primitives) {
286                HashMap<User, Integer> ret = new HashMap<User, Integer>();
287                if (primitives == null || primitives.isEmpty()) return ret;
288                for (OsmPrimitive primitive: primitives) {
289                    if (ret.containsKey(primitive.getUser())) {
290                        ret.put(primitive.getUser(), ret.get(primitive.getUser()) + 1);
291                    } else {
292                        ret.put(primitive.getUser(), 1);
293                    }
294                }
295                return ret;
296            }
297    
298            public void populate(Collection<? extends OsmPrimitive> primitives) {
299                Map<User,Integer> statistics = computeStatistics(primitives);
300                data.clear();
301                if (primitives != null) {
302                    for (Map.Entry<User, Integer> entry: statistics.entrySet()) {
303                        data.add(new UserInfo(entry.getKey(), entry.getValue(), (double)entry.getValue() /  (double)primitives.size()));
304                    }
305                }
306                Collections.sort(data);
307                fireTableDataChanged();
308            }
309    
310            @Override
311            public int getRowCount() {
312                if (data == null) return 0;
313                return data.size();
314            }
315    
316            @Override
317            public Object getValueAt(int row, int column) {
318                UserInfo info = data.get(row);
319                switch(column) {
320                case 0: /* author */ return info.getName() == null ? "" : info.getName();
321                case 1: /* count */ return info.count;
322                case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent);
323                }
324                return null;
325            }
326    
327            @Override
328            public boolean isCellEditable(int row, int column) {
329                return false;
330            }
331    
332            public void selectPrimitivesOwnedBy(int [] rows) {
333                Set<User> users= new HashSet<User>();
334                for (int index: rows) {
335                    users.add(data.get(index).user);
336                }
337                Collection<OsmPrimitive> selected = Main.main.getCurrentDataSet().getAllSelected();
338                Collection<OsmPrimitive> byUser = new LinkedList<OsmPrimitive>();
339                for (OsmPrimitive p : selected) {
340                    if (users.contains(p.getUser())) {
341                        byUser.add(p);
342                    }
343                }
344                Main.main.getCurrentDataSet().setSelected(byUser);
345            }
346    
347            public List<User> getSelectedUsers(int rows[]) {
348                LinkedList<User> ret = new LinkedList<User>();
349                if (rows == null || rows.length == 0) return ret;
350                for (int row: rows) {
351                    if (data.get(row).user == null) {
352                        continue;
353                    }
354                    ret.add(data.get(row).user);
355                }
356                return ret;
357            }
358        }
359    }