source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java @ 11848

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

fix #14613 - Special HTML characters not escaped in GUI error messages

  • Property svn:eol-style set to native
File size: 16.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.changeset;
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.FlowLayout;
9import java.awt.event.ActionEvent;
10import java.awt.event.ComponentAdapter;
11import java.awt.event.ComponentEvent;
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Set;
19import java.util.stream.Collectors;
20
21import javax.swing.AbstractAction;
22import javax.swing.BorderFactory;
23import javax.swing.DefaultListSelectionModel;
24import javax.swing.JButton;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JPopupMenu;
28import javax.swing.JScrollPane;
29import javax.swing.JSeparator;
30import javax.swing.JTable;
31import javax.swing.JToolBar;
32import javax.swing.event.ListSelectionEvent;
33import javax.swing.event.ListSelectionListener;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.actions.AutoScaleAction;
37import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
38import org.openstreetmap.josm.data.osm.Changeset;
39import org.openstreetmap.josm.data.osm.OsmPrimitive;
40import org.openstreetmap.josm.data.osm.PrimitiveId;
41import org.openstreetmap.josm.data.osm.history.History;
42import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
43import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
44import org.openstreetmap.josm.gui.HelpAwareOptionPane;
45import org.openstreetmap.josm.gui.help.HelpUtil;
46import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
47import org.openstreetmap.josm.gui.history.HistoryLoadTask;
48import org.openstreetmap.josm.gui.io.DownloadPrimitivesWithReferrersTask;
49import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
50import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
51import org.openstreetmap.josm.gui.layer.OsmDataLayer;
52import org.openstreetmap.josm.gui.util.GuiHelper;
53import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
54import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
55import org.openstreetmap.josm.tools.ImageProvider;
56import org.openstreetmap.josm.tools.JosmRuntimeException;
57import org.openstreetmap.josm.tools.Utils;
58import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
59
60/**
61 * The panel which displays the content of a changeset in a scrollable table.
62 *
63 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
64 * and updates its view accordingly.
65 *
66 */
67public class ChangesetContentPanel extends JPanel implements PropertyChangeListener, ChangesetAware {
68
69    private ChangesetContentTableModel model;
70    private transient Changeset currentChangeset;
71
72    private DownloadChangesetContentAction actDownloadContentAction;
73    private ShowHistoryAction actShowHistory;
74    private SelectInCurrentLayerAction actSelectInCurrentLayerAction;
75    private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
76
77    private final HeaderPanel pnlHeader = new HeaderPanel();
78    public DownloadObjectAction actDownloadObjectAction;
79
80    protected void buildModels() {
81        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
82        model = new ChangesetContentTableModel(selectionModel);
83        actDownloadContentAction = new DownloadChangesetContentAction(this);
84        actDownloadContentAction.initProperties();
85
86        actDownloadObjectAction = new DownloadObjectAction();
87        model.getSelectionModel().addListSelectionListener(actDownloadObjectAction);
88
89        actShowHistory = new ShowHistoryAction();
90        model.getSelectionModel().addListSelectionListener(actShowHistory);
91
92        actSelectInCurrentLayerAction = new SelectInCurrentLayerAction();
93        model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction);
94        Main.getLayerManager().addActiveLayerChangeListener(actSelectInCurrentLayerAction);
95
96        actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction();
97        model.getSelectionModel().addListSelectionListener(actZoomInCurrentLayerAction);
98        Main.getLayerManager().addActiveLayerChangeListener(actZoomInCurrentLayerAction);
99
100        addComponentListener(
101                new ComponentAdapter() {
102                    @Override
103                    public void componentShown(ComponentEvent e) {
104                        Main.getLayerManager().addAndFireActiveLayerChangeListener(actSelectInCurrentLayerAction);
105                        Main.getLayerManager().addAndFireActiveLayerChangeListener(actZoomInCurrentLayerAction);
106                    }
107
108                    @Override
109                    public void componentHidden(ComponentEvent e) {
110                        // make sure the listener is unregistered when the panel becomes invisible
111                        Main.getLayerManager().removeActiveLayerChangeListener(actSelectInCurrentLayerAction);
112                        Main.getLayerManager().removeActiveLayerChangeListener(actZoomInCurrentLayerAction);
113                    }
114                }
115        );
116    }
117
118    protected JPanel buildContentPanel() {
119        JPanel pnl = new JPanel(new BorderLayout());
120        JTable tblContent = new JTable(
121                model,
122                new ChangesetContentTableColumnModel(),
123                model.getSelectionModel()
124        );
125        tblContent.addMouseListener(new PopupMenuLauncher(new ChangesetContentTablePopupMenu()));
126        pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER);
127        return pnl;
128    }
129
130    protected JPanel buildActionButtonPanel() {
131        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
132        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
133        tb.setFloatable(false);
134
135        tb.add(actDownloadContentAction);
136        tb.addSeparator();
137        tb.add(actDownloadObjectAction);
138        tb.add(actShowHistory);
139        tb.addSeparator();
140        tb.add(actSelectInCurrentLayerAction);
141        tb.add(actZoomInCurrentLayerAction);
142
143        pnl.add(tb);
144        return pnl;
145    }
146
147    protected final void build() {
148        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
149        setLayout(new BorderLayout());
150        buildModels();
151
152        add(pnlHeader, BorderLayout.NORTH);
153        add(buildActionButtonPanel(), BorderLayout.WEST);
154        add(buildContentPanel(), BorderLayout.CENTER);
155    }
156
157    /**
158     * Constructs a new {@code ChangesetContentPanel}.
159     */
160    public ChangesetContentPanel() {
161        build();
162    }
163
164    /**
165     * Replies the changeset content model
166     * @return The model
167     */
168    public ChangesetContentTableModel getModel() {
169        return model;
170    }
171
172    protected void setCurrentChangeset(Changeset cs) {
173        currentChangeset = cs;
174        if (cs == null) {
175            model.populate(null);
176        } else {
177            model.populate(cs.getContent());
178        }
179        actDownloadContentAction.initProperties();
180        pnlHeader.setChangeset(cs);
181    }
182
183    /* ---------------------------------------------------------------------------- */
184    /* interface PropertyChangeListener                                             */
185    /* ---------------------------------------------------------------------------- */
186    @Override
187    public void propertyChange(PropertyChangeEvent evt) {
188        if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
189            return;
190        Changeset cs = (Changeset) evt.getNewValue();
191        setCurrentChangeset(cs);
192    }
193
194    private void alertNoPrimitivesTo(Collection<HistoryOsmPrimitive> primitives, String title, String helpTopic) {
195        HelpAwareOptionPane.showOptionDialog(
196                this,
197                trn("<html>The selected object is not available in the current<br>"
198                        + "edit layer ''{0}''.</html>",
199                        "<html>None of the selected objects is available in the current<br>"
200                        + "edit layer ''{0}''.</html>",
201                        primitives.size(),
202                        Utils.escapeReservedCharactersHTML(Main.getLayerManager().getEditLayer().getName())
203                ),
204                title, JOptionPane.WARNING_MESSAGE, helpTopic
205        );
206    }
207
208    class ChangesetContentTablePopupMenu extends JPopupMenu {
209        ChangesetContentTablePopupMenu() {
210            add(actDownloadContentAction);
211            add(new JSeparator());
212            add(actDownloadObjectAction);
213            add(actShowHistory);
214            add(new JSeparator());
215            add(actSelectInCurrentLayerAction);
216            add(actZoomInCurrentLayerAction);
217        }
218    }
219
220    class ShowHistoryAction extends AbstractAction implements ListSelectionListener {
221
222        private final class ShowHistoryTask implements Runnable {
223            private final Collection<HistoryOsmPrimitive> primitives;
224
225            private ShowHistoryTask(Collection<HistoryOsmPrimitive> primitives) {
226                this.primitives = primitives;
227            }
228
229            @Override
230            public void run() {
231                try {
232                    for (HistoryOsmPrimitive p : primitives) {
233                        final History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId());
234                        if (h == null) {
235                            continue;
236                        }
237                        GuiHelper.runInEDT(() -> HistoryBrowserDialogManager.getInstance().show(h));
238                    }
239                } catch (final JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
240                    GuiHelper.runInEDT(() -> BugReportExceptionHandler.handleException(e));
241                }
242            }
243        }
244
245        ShowHistoryAction() {
246            putValue(NAME, tr("Show history"));
247            new ImageProvider("dialogs", "history").getResource().attachImageIcon(this);
248            putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected objects"));
249            updateEnabledState();
250        }
251
252        protected List<HistoryOsmPrimitive> filterPrimitivesWithUnloadedHistory(Collection<HistoryOsmPrimitive> primitives) {
253            List<HistoryOsmPrimitive> ret = new ArrayList<>(primitives.size());
254            for (HistoryOsmPrimitive p: primitives) {
255                if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) {
256                    ret.add(p);
257                }
258            }
259            return ret;
260        }
261
262        public void showHistory(final Collection<HistoryOsmPrimitive> primitives) {
263
264            List<HistoryOsmPrimitive> toLoad = filterPrimitivesWithUnloadedHistory(primitives);
265            if (!toLoad.isEmpty()) {
266                HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this);
267                for (HistoryOsmPrimitive p: toLoad) {
268                    task.add(p);
269                }
270                Main.worker.submit(task);
271            }
272
273            Main.worker.submit(new ShowHistoryTask(primitives));
274        }
275
276        protected final void updateEnabledState() {
277            setEnabled(model.hasSelectedPrimitives());
278        }
279
280        @Override
281        public void actionPerformed(ActionEvent arg0) {
282            Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
283            if (selected.isEmpty()) return;
284            showHistory(selected);
285        }
286
287        @Override
288        public void valueChanged(ListSelectionEvent e) {
289            updateEnabledState();
290        }
291    }
292
293    class DownloadObjectAction extends AbstractAction implements ListSelectionListener {
294
295        DownloadObjectAction() {
296            putValue(NAME, tr("Download objects"));
297            putValue(SMALL_ICON, ImageProvider.get("downloadprimitive"));
298            putValue(SHORT_DESCRIPTION, tr("Download the current version of the selected objects"));
299            updateEnabledState();
300        }
301
302        @Override
303        public void actionPerformed(ActionEvent arg0) {
304            final List<PrimitiveId> primitiveIds = model.getSelectedPrimitives().stream().map(HistoryOsmPrimitive::getPrimitiveId)
305                    .collect(Collectors.toList());
306            Main.worker.submit(new DownloadPrimitivesWithReferrersTask(false, primitiveIds, true, true, null, null));
307        }
308
309        protected final void updateEnabledState() {
310            setEnabled(model.hasSelectedPrimitives());
311        }
312
313        @Override
314        public void valueChanged(ListSelectionEvent e) {
315            updateEnabledState();
316        }
317    }
318
319    abstract class SelectionBasedAction extends AbstractAction implements ListSelectionListener, ActiveLayerChangeListener {
320
321        protected Set<OsmPrimitive> getTarget() {
322            if (!isEnabled()) {
323                return null;
324            }
325            OsmDataLayer layer = Main.getLayerManager().getEditLayer();
326            if (layer == null) {
327                return null;
328            }
329            Set<OsmPrimitive> target = new HashSet<>();
330            for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
331                OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
332                if (op != null) {
333                    target.add(op);
334                }
335            }
336            return target;
337        }
338
339        public final void updateEnabledState() {
340            setEnabled(Main.getLayerManager().getEditLayer() != null && model.hasSelectedPrimitives());
341        }
342
343        @Override
344        public void valueChanged(ListSelectionEvent e) {
345            updateEnabledState();
346        }
347
348        @Override
349        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
350            updateEnabledState();
351        }
352
353    }
354
355    class SelectInCurrentLayerAction extends SelectionBasedAction {
356
357        SelectInCurrentLayerAction() {
358            putValue(NAME, tr("Select in layer"));
359            new ImageProvider("dialogs", "select").getResource().attachImageIcon(this);
360            putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer"));
361            updateEnabledState();
362        }
363
364        @Override
365        public void actionPerformed(ActionEvent arg0) {
366            final Set<OsmPrimitive> target = getTarget();
367            if (target == null) {
368                return;
369            } else if (target.isEmpty()) {
370                alertNoPrimitivesTo(model.getSelectedPrimitives(), tr("Nothing to select"),
371                        HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer"));
372                return;
373            }
374            Main.getLayerManager().getEditLayer().data.setSelected(target);
375        }
376    }
377
378    class ZoomInCurrentLayerAction extends SelectionBasedAction {
379
380        ZoomInCurrentLayerAction() {
381            putValue(NAME, tr("Zoom to in layer"));
382            new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this);
383            putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding objects in the current data layer"));
384            updateEnabledState();
385        }
386
387        @Override
388        public void actionPerformed(ActionEvent arg0) {
389            final Set<OsmPrimitive> target = getTarget();
390            if (target == null) {
391                return;
392            } else if (target.isEmpty()) {
393                alertNoPrimitivesTo(model.getSelectedPrimitives(), tr("Nothing to zoom to"),
394                        HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo"));
395                return;
396            }
397            Main.getLayerManager().getEditLayer().data.setSelected(target);
398            AutoScaleAction.zoomToSelection();
399        }
400    }
401
402    private static class HeaderPanel extends JPanel {
403
404        private transient Changeset current;
405
406        HeaderPanel() {
407            build();
408        }
409
410        protected final void build() {
411            setLayout(new FlowLayout(FlowLayout.LEFT));
412            add(new JMultilineLabel(tr("The content of this changeset is not downloaded yet.")));
413            add(new JButton(new DownloadAction()));
414
415        }
416
417        public void setChangeset(Changeset cs) {
418            setVisible(cs != null && cs.getContent() == null);
419            this.current = cs;
420        }
421
422        private class DownloadAction extends AbstractAction {
423            DownloadAction() {
424                putValue(NAME, tr("Download now"));
425                putValue(SHORT_DESCRIPTION, tr("Download the changeset content"));
426                new ImageProvider("dialogs/changeset", "downloadchangesetcontent").getResource().attachImageIcon(this);
427            }
428
429            @Override
430            public void actionPerformed(ActionEvent evt) {
431                if (current == null) return;
432                ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId());
433                ChangesetCacheManager.getInstance().runDownloadTask(task);
434            }
435        }
436    }
437
438    @Override
439    public Changeset getCurrentChangeset() {
440        return currentChangeset;
441    }
442}
Note: See TracBrowser for help on using the repository browser.