source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java @ 11535

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

sonar - squid:S2142 - "InterruptedException" should not be ignored

  • Property svn:eol-style set to native
File size: 21.5 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;
5
6import java.awt.BorderLayout;
7import java.awt.FlowLayout;
8import java.awt.Frame;
9import java.awt.event.ActionEvent;
10import java.awt.event.ItemEvent;
11import java.awt.event.ItemListener;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Set;
19import java.util.concurrent.ExecutionException;
20import java.util.concurrent.Future;
21
22import javax.swing.AbstractAction;
23import javax.swing.Action;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.JCheckBox;
26import javax.swing.JList;
27import javax.swing.JMenuItem;
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.ListSelectionModel;
31import javax.swing.SwingUtilities;
32import javax.swing.event.ListSelectionEvent;
33import javax.swing.event.ListSelectionListener;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.actions.AbstractInfoAction;
37import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
38import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
39import org.openstreetmap.josm.data.osm.Changeset;
40import org.openstreetmap.josm.data.osm.ChangesetCache;
41import org.openstreetmap.josm.data.osm.DataSet;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
44import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
45import org.openstreetmap.josm.gui.SideButton;
46import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
47import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
48import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
49import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
50import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
51import org.openstreetmap.josm.gui.help.HelpUtil;
52import org.openstreetmap.josm.gui.io.CloseChangesetTask;
53import org.openstreetmap.josm.gui.layer.OsmDataLayer;
54import org.openstreetmap.josm.gui.util.GuiHelper;
55import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
56import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
57import org.openstreetmap.josm.io.OnlineResource;
58import org.openstreetmap.josm.tools.ImageProvider;
59import org.openstreetmap.josm.tools.OpenBrowser;
60import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
61
62/**
63 * ChangesetDialog is a toggle dialog which displays the current list of changesets.
64 * It either displays
65 * <ul>
66 *   <li>the list of changesets the currently selected objects are assigned to</li>
67 *   <li>the list of changesets objects in the current data layer are assigend to</li>
68 * </ul>
69 *
70 * The dialog offers actions to download and to close changesets. It can also launch an external
71 * browser with information about a changeset. Furthermore, it can select all objects in
72 * the current data layer being assigned to a specific changeset.
73 * @since 2613
74 */
75public class ChangesetDialog extends ToggleDialog {
76    private ChangesetInSelectionListModel inSelectionModel;
77    private ChangesetsInActiveDataLayerListModel inActiveDataLayerModel;
78    private JList<Changeset> lstInSelection;
79    private JList<Changeset> lstInActiveDataLayer;
80    private JCheckBox cbInSelectionOnly;
81    private JPanel pnlList;
82
83    // the actions
84    private SelectObjectsAction selectObjectsAction;
85    private ReadChangesetsAction readChangesetAction;
86    private ShowChangesetInfoAction showChangesetInfoAction;
87    private CloseOpenChangesetsAction closeChangesetAction;
88
89    private ChangesetDialogPopup popupMenu;
90
91    protected void buildChangesetsLists() {
92        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
93        inSelectionModel = new ChangesetInSelectionListModel(selectionModel);
94
95        lstInSelection = new JList<>(inSelectionModel);
96        lstInSelection.setSelectionModel(selectionModel);
97        lstInSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
98        lstInSelection.setCellRenderer(new ChangesetListCellRenderer());
99
100        selectionModel = new DefaultListSelectionModel();
101        inActiveDataLayerModel = new ChangesetsInActiveDataLayerListModel(selectionModel);
102        lstInActiveDataLayer = new JList<>(inActiveDataLayerModel);
103        lstInActiveDataLayer.setSelectionModel(selectionModel);
104        lstInActiveDataLayer.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
105        lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
106
107        DblClickHandler dblClickHandler = new DblClickHandler();
108        lstInSelection.addMouseListener(dblClickHandler);
109        lstInActiveDataLayer.addMouseListener(dblClickHandler);
110    }
111
112    protected void registerAsListener() {
113        // let the model for changesets in the current selection listen to various events
114        ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
115        Main.getLayerManager().addActiveLayerChangeListener(inSelectionModel);
116        DataSet.addSelectionListener(inSelectionModel);
117
118        // let the model for changesets in the current layer listen to various
119        // events and bootstrap it's content
120        ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
121        Main.getLayerManager().addActiveLayerChangeListener(inActiveDataLayerModel);
122        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
123        if (editLayer != null) {
124            editLayer.data.addDataSetListener(inActiveDataLayerModel);
125            inActiveDataLayerModel.initFromDataSet(editLayer.data);
126            inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
127        }
128    }
129
130    protected void unregisterAsListener() {
131        // remove the list model for the current edit layer as listener
132        //
133        ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
134        Main.getLayerManager().removeActiveLayerChangeListener(inActiveDataLayerModel);
135        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
136        if (editLayer != null) {
137            editLayer.data.removeDataSetListener(inActiveDataLayerModel);
138        }
139
140        // remove the list model for the changesets in the current selection as
141        // listener
142        //
143        Main.getLayerManager().removeActiveLayerChangeListener(inSelectionModel);
144        DataSet.removeSelectionListener(inSelectionModel);
145    }
146
147    @Override
148    public void showNotify() {
149        registerAsListener();
150        DatasetEventManager.getInstance().addDatasetListener(inActiveDataLayerModel, FireMode.IN_EDT);
151    }
152
153    @Override
154    public void hideNotify() {
155        unregisterAsListener();
156        DatasetEventManager.getInstance().removeDatasetListener(inActiveDataLayerModel);
157    }
158
159    protected JPanel buildFilterPanel() {
160        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
161        pnl.setBorder(null);
162        cbInSelectionOnly = new JCheckBox(tr("For selected objects only"));
163        pnl.add(cbInSelectionOnly);
164        cbInSelectionOnly.setToolTipText(tr("<html>Select to show changesets for the currently selected objects only.<br>"
165                + "Unselect to show all changesets for objects in the current data layer.</html>"));
166        cbInSelectionOnly.setSelected(Main.pref.getBoolean("changeset-dialog.for-selected-objects-only", false));
167        return pnl;
168    }
169
170    protected JPanel buildListPanel() {
171        buildChangesetsLists();
172        JPanel pnl = new JPanel(new BorderLayout());
173        if (cbInSelectionOnly.isSelected()) {
174            pnl.add(new JScrollPane(lstInSelection));
175        } else {
176            pnl.add(new JScrollPane(lstInActiveDataLayer));
177        }
178        return pnl;
179    }
180
181    protected void build() {
182        JPanel pnl = new JPanel(new BorderLayout());
183        pnl.add(buildFilterPanel(), BorderLayout.NORTH);
184        pnlList = buildListPanel();
185        pnl.add(pnlList, BorderLayout.CENTER);
186
187        cbInSelectionOnly.addItemListener(new FilterChangeHandler());
188
189        HelpUtil.setHelpContext(pnl, HelpUtil.ht("/Dialog/ChangesetList"));
190
191        // -- select objects action
192        selectObjectsAction = new SelectObjectsAction();
193        cbInSelectionOnly.addItemListener(selectObjectsAction);
194
195        // -- read changesets action
196        readChangesetAction = new ReadChangesetsAction();
197        cbInSelectionOnly.addItemListener(readChangesetAction);
198
199        // -- close changesets action
200        closeChangesetAction = new CloseOpenChangesetsAction();
201        cbInSelectionOnly.addItemListener(closeChangesetAction);
202
203        // -- show info action
204        showChangesetInfoAction = new ShowChangesetInfoAction();
205        cbInSelectionOnly.addItemListener(showChangesetInfoAction);
206
207        popupMenu = new ChangesetDialogPopup(lstInActiveDataLayer, lstInSelection);
208
209        PopupMenuLauncher popupMenuLauncher = new PopupMenuLauncher(popupMenu);
210        lstInSelection.addMouseListener(popupMenuLauncher);
211        lstInActiveDataLayer.addMouseListener(popupMenuLauncher);
212
213        createLayout(pnl, false, Arrays.asList(new SideButton[] {
214            new SideButton(selectObjectsAction, false),
215            new SideButton(readChangesetAction, false),
216            new SideButton(closeChangesetAction, false),
217            new SideButton(showChangesetInfoAction, false),
218            new SideButton(new LaunchChangesetManagerAction(), false)
219        }));
220    }
221
222    protected JList<Changeset> getCurrentChangesetList() {
223        if (cbInSelectionOnly.isSelected())
224            return lstInSelection;
225        return lstInActiveDataLayer;
226    }
227
228    protected ChangesetListModel getCurrentChangesetListModel() {
229        if (cbInSelectionOnly.isSelected())
230            return inSelectionModel;
231        return inActiveDataLayerModel;
232    }
233
234    protected void initWithCurrentData() {
235        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
236        if (editLayer != null) {
237            inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
238            inActiveDataLayerModel.initFromDataSet(editLayer.data);
239        }
240    }
241
242    /**
243     * Constructs a new {@code ChangesetDialog}.
244     */
245    public ChangesetDialog() {
246        super(
247                tr("Changesets"),
248                "changesetdialog",
249                tr("Open the list of changesets in the current layer."),
250                null, /* no keyboard shortcut */
251                200, /* the preferred height */
252                false /* don't show if there is no preference */
253        );
254        build();
255        initWithCurrentData();
256    }
257
258    class DblClickHandler extends MouseAdapter {
259        @Override
260        public void mouseClicked(MouseEvent e) {
261            if (!SwingUtilities.isLeftMouseButton(e) || e.getClickCount() < 2)
262                return;
263            Set<Integer> sel = getCurrentChangesetListModel().getSelectedChangesetIds();
264            if (sel.isEmpty())
265                return;
266            if (Main.getLayerManager().getEditDataSet() == null)
267                return;
268            new SelectObjectsAction().selectObjectsByChangesetIds(Main.getLayerManager().getEditDataSet(), sel);
269        }
270
271    }
272
273    class FilterChangeHandler implements ItemListener {
274        @Override
275        public void itemStateChanged(ItemEvent e) {
276            Main.pref.put("changeset-dialog.for-selected-objects-only", cbInSelectionOnly.isSelected());
277            pnlList.removeAll();
278            if (cbInSelectionOnly.isSelected()) {
279                pnlList.add(new JScrollPane(lstInSelection), BorderLayout.CENTER);
280            } else {
281                pnlList.add(new JScrollPane(lstInActiveDataLayer), BorderLayout.CENTER);
282            }
283            validate();
284            repaint();
285        }
286    }
287
288    /**
289     * Selects objects for the currently selected changesets.
290     */
291    class SelectObjectsAction extends AbstractAction implements ListSelectionListener, ItemListener {
292
293        SelectObjectsAction() {
294            putValue(NAME, tr("Select"));
295            putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets"));
296            new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
297            updateEnabledState();
298        }
299
300        public void selectObjectsByChangesetIds(DataSet ds, Set<Integer> ids) {
301            if (ds == null || ids == null)
302                return;
303            Set<OsmPrimitive> sel = new HashSet<>();
304            for (OsmPrimitive p: ds.allPrimitives()) {
305                if (ids.contains(p.getChangesetId())) {
306                    sel.add(p);
307                }
308            }
309            ds.setSelected(sel);
310        }
311
312        @Override
313        public void actionPerformed(ActionEvent e) {
314            if (Main.getLayerManager().getEditLayer() == null)
315                return;
316            ChangesetListModel model = getCurrentChangesetListModel();
317            Set<Integer> sel = model.getSelectedChangesetIds();
318            if (sel.isEmpty())
319                return;
320
321            DataSet ds = Main.getLayerManager().getEditLayer().data;
322            selectObjectsByChangesetIds(ds, sel);
323        }
324
325        protected void updateEnabledState() {
326            setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
327        }
328
329        @Override
330        public void itemStateChanged(ItemEvent e) {
331            updateEnabledState();
332
333        }
334
335        @Override
336        public void valueChanged(ListSelectionEvent e) {
337            updateEnabledState();
338        }
339    }
340
341    /**
342     * Downloads selected changesets
343     *
344     */
345    class ReadChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
346        ReadChangesetsAction() {
347            putValue(NAME, tr("Download"));
348            putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server"));
349            new ImageProvider("download").getResource().attachImageIcon(this, true);
350            updateEnabledState();
351        }
352
353        @Override
354        public void actionPerformed(ActionEvent e) {
355            ChangesetListModel model = getCurrentChangesetListModel();
356            Set<Integer> sel = model.getSelectedChangesetIds();
357            if (sel.isEmpty())
358                return;
359            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
360            Main.worker.submit(new PostDownloadHandler(task, task.download()));
361        }
362
363        protected void updateEnabledState() {
364            setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0 && !Main.isOffline(OnlineResource.OSM_API));
365        }
366
367        @Override
368        public void itemStateChanged(ItemEvent e) {
369            updateEnabledState();
370        }
371
372        @Override
373        public void valueChanged(ListSelectionEvent e) {
374            updateEnabledState();
375        }
376    }
377
378    /**
379     * Closes the currently selected changesets
380     *
381     */
382    class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
383        CloseOpenChangesetsAction() {
384            putValue(NAME, tr("Close open changesets"));
385            putValue(SHORT_DESCRIPTION, tr("Closes the selected open changesets"));
386            new ImageProvider("closechangeset").getResource().attachImageIcon(this, true);
387            updateEnabledState();
388        }
389
390        @Override
391        public void actionPerformed(ActionEvent e) {
392            List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
393            if (sel.isEmpty())
394                return;
395            Main.worker.submit(new CloseChangesetTask(sel));
396        }
397
398        protected void updateEnabledState() {
399            setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets());
400        }
401
402        @Override
403        public void itemStateChanged(ItemEvent e) {
404            updateEnabledState();
405        }
406
407        @Override
408        public void valueChanged(ListSelectionEvent e) {
409            updateEnabledState();
410        }
411    }
412
413    /**
414     * Show information about the currently selected changesets
415     *
416     */
417    class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener {
418        ShowChangesetInfoAction() {
419            putValue(NAME, tr("Show info"));
420            putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset"));
421            new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
422            updateEnabledState();
423        }
424
425        @Override
426        public void actionPerformed(ActionEvent e) {
427            Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
428            if (sel.isEmpty())
429                return;
430            if (sel.size() > 10 && !AbstractInfoAction.confirmLaunchMultiple(sel.size()))
431                return;
432            String baseUrl = Main.getBaseBrowseUrl();
433            for (Changeset cs: sel) {
434                OpenBrowser.displayUrl(baseUrl + "/changeset/" + cs.getId());
435            }
436        }
437
438        protected void updateEnabledState() {
439            setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
440        }
441
442        @Override
443        public void itemStateChanged(ItemEvent e) {
444            updateEnabledState();
445        }
446
447        @Override
448        public void valueChanged(ListSelectionEvent e) {
449            updateEnabledState();
450        }
451    }
452
453    /**
454     * Show information about the currently selected changesets
455     *
456     */
457    class LaunchChangesetManagerAction extends AbstractAction {
458        LaunchChangesetManagerAction() {
459            putValue(NAME, tr("Details"));
460            putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
461            new ImageProvider("dialogs/changeset", "changesetmanager").getResource().attachImageIcon(this, true);
462        }
463
464        @Override
465        public void actionPerformed(ActionEvent e) {
466            ChangesetListModel model = getCurrentChangesetListModel();
467            Set<Integer> sel = model.getSelectedChangesetIds();
468            LaunchChangesetManager.displayChangesets(sel);
469        }
470    }
471
472    /**
473     * A utility class to fetch changesets and display the changeset dialog.
474     */
475    public static final class LaunchChangesetManager {
476
477        private LaunchChangesetManager() {
478            // Hide implicit public constructor for utility classes
479        }
480
481        private static void launchChangesetManager(Collection<Integer> toSelect) {
482            ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
483            if (cm.isVisible()) {
484                cm.setExtendedState(Frame.NORMAL);
485                cm.toFront();
486                cm.requestFocus();
487            } else {
488                cm.setVisible(true);
489                cm.toFront();
490                cm.requestFocus();
491            }
492            cm.setSelectedChangesetsById(toSelect);
493        }
494
495        /**
496         * Fetches changesets and display the changeset dialog.
497         * @param sel the changeset ids to fetch and display.
498         */
499        public static void displayChangesets(final Set<Integer> sel) {
500            final Set<Integer> toDownload = new HashSet<>();
501            if (!Main.isOffline(OnlineResource.OSM_API)) {
502                ChangesetCache cc = ChangesetCache.getInstance();
503                for (int id: sel) {
504                    if (!cc.contains(id)) {
505                        toDownload.add(id);
506                    }
507                }
508            }
509
510            final ChangesetHeaderDownloadTask task;
511            final Future<?> future;
512            if (toDownload.isEmpty()) {
513                task = null;
514                future = null;
515            } else {
516                task = new ChangesetHeaderDownloadTask(toDownload);
517                future = Main.worker.submit(new PostDownloadHandler(task, task.download()));
518            }
519
520            Runnable r = () -> {
521                // first, wait for the download task to finish, if a download task was launched
522                if (future != null) {
523                    try {
524                        future.get();
525                    } catch (InterruptedException e1) {
526                        Main.warn(e1, "InterruptedException in ChangesetDialog while downloading changeset header");
527                        Thread.currentThread().interrupt();
528                    } catch (ExecutionException e2) {
529                        Main.error(e2);
530                        BugReportExceptionHandler.handleException(e2.getCause());
531                        return;
532                    }
533                }
534                if (task != null) {
535                    if (task.isCanceled())
536                        // don't launch the changeset manager if the download task was canceled
537                        return;
538                    if (task.isFailed()) {
539                        toDownload.clear();
540                    }
541                }
542                // launch the task
543                GuiHelper.runInEDT(() -> launchChangesetManager(sel));
544            };
545            Main.worker.submit(r);
546        }
547    }
548
549    class ChangesetDialogPopup extends ListPopupMenu {
550        ChangesetDialogPopup(JList<?> ... lists) {
551            super(lists);
552            add(selectObjectsAction);
553            addSeparator();
554            add(readChangesetAction);
555            add(closeChangesetAction);
556            addSeparator();
557            add(showChangesetInfoAction);
558        }
559    }
560
561    public void addPopupMenuSeparator() {
562        popupMenu.addSeparator();
563    }
564
565    public JMenuItem addPopupMenuAction(Action a) {
566        return popupMenu.add(a);
567    }
568}
Note: See TracBrowser for help on using the repository browser.