source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.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.0 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.trc;
6
7import java.awt.BorderLayout;
8import java.awt.FlowLayout;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.Insets;
12import java.awt.event.ActionEvent;
13import java.awt.event.ComponentAdapter;
14import java.awt.event.ComponentEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.text.DateFormat;
18import java.util.Collections;
19import java.util.HashSet;
20import java.util.Set;
21
22import javax.swing.AbstractAction;
23import javax.swing.BorderFactory;
24import javax.swing.JLabel;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JToolBar;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.actions.AutoScaleAction;
31import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
32import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
33import org.openstreetmap.josm.data.osm.Changeset;
34import org.openstreetmap.josm.data.osm.ChangesetCache;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.gui.HelpAwareOptionPane;
37import org.openstreetmap.josm.gui.help.HelpUtil;
38import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
39import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
40import org.openstreetmap.josm.gui.layer.OsmDataLayer;
41import org.openstreetmap.josm.gui.widgets.JosmTextArea;
42import org.openstreetmap.josm.gui.widgets.JosmTextField;
43import org.openstreetmap.josm.io.OnlineResource;
44import org.openstreetmap.josm.tools.ImageProvider;
45import org.openstreetmap.josm.tools.Utils;
46import org.openstreetmap.josm.tools.date.DateUtils;
47
48/**
49 * This panel displays the properties of the currently selected changeset in the
50 * {@link ChangesetCacheManager}.
51 *
52 */
53public class ChangesetDetailPanel extends JPanel implements PropertyChangeListener, ChangesetAware {
54
55    // CHECKSTYLE.OFF: SingleSpaceSeparator
56    private final JosmTextField tfID        = new JosmTextField(10);
57    private final JosmTextArea  taComment   = new JosmTextArea(5, 40);
58    private final JosmTextField tfOpen      = new JosmTextField(10);
59    private final JosmTextField tfUser      = new JosmTextField("");
60    private final JosmTextField tfCreatedOn = new JosmTextField(20);
61    private final JosmTextField tfClosedOn  = new JosmTextField(20);
62
63    private final DownloadChangesetContentAction actDownloadChangesetContent = new DownloadChangesetContentAction(this);
64    private final UpdateChangesetAction          actUpdateChangesets         = new UpdateChangesetAction();
65    private final RemoveFromCacheAction          actRemoveFromCache          = new RemoveFromCacheAction();
66    private final SelectInCurrentLayerAction     actSelectInCurrentLayer     = new SelectInCurrentLayerAction();
67    private final ZoomInCurrentLayerAction       actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction();
68    // CHECKSTYLE.ON: SingleSpaceSeparator
69
70    private transient Changeset currentChangeset;
71
72    protected JPanel buildActionButtonPanel() {
73        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
74
75        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
76        tb.setFloatable(false);
77
78        // -- remove from cache action
79        tb.add(actRemoveFromCache);
80        actRemoveFromCache.initProperties(currentChangeset);
81
82        // -- changeset update
83        tb.add(actUpdateChangesets);
84        actUpdateChangesets.initProperties(currentChangeset);
85
86        // -- changeset content download
87        tb.add(actDownloadChangesetContent);
88        actDownloadChangesetContent.initProperties();
89
90        tb.add(actSelectInCurrentLayer);
91        Main.getLayerManager().addActiveLayerChangeListener(actSelectInCurrentLayer);
92
93        tb.add(actZoomInCurrentLayerAction);
94        Main.getLayerManager().addActiveLayerChangeListener(actZoomInCurrentLayerAction);
95
96        addComponentListener(
97                new ComponentAdapter() {
98                    @Override
99                    public void componentShown(ComponentEvent e) {
100                        Main.getLayerManager().addAndFireActiveLayerChangeListener(actSelectInCurrentLayer);
101                        Main.getLayerManager().addAndFireActiveLayerChangeListener(actZoomInCurrentLayerAction);
102                    }
103
104                    @Override
105                    public void componentHidden(ComponentEvent e) {
106                        // make sure the listener is unregistered when the panel becomes invisible
107                        Main.getLayerManager().removeActiveLayerChangeListener(actSelectInCurrentLayer);
108                        Main.getLayerManager().removeActiveLayerChangeListener(actZoomInCurrentLayerAction);
109                    }
110                }
111        );
112
113        pnl.add(tb);
114        return pnl;
115    }
116
117    protected JPanel buildDetailViewPanel() {
118        JPanel pnl = new JPanel(new GridBagLayout());
119
120        GridBagConstraints gc = new GridBagConstraints();
121        gc.anchor = GridBagConstraints.FIRST_LINE_START;
122        gc.insets = new Insets(0, 0, 2, 3);
123
124        //-- id
125        gc.fill = GridBagConstraints.HORIZONTAL;
126        gc.weightx = 0.0;
127        pnl.add(new JLabel(tr("ID:")), gc);
128
129        gc.fill = GridBagConstraints.HORIZONTAL;
130        gc.weightx = 0.0;
131        gc.gridx = 1;
132        pnl.add(tfID, gc);
133        tfID.setEditable(false);
134
135        //-- comment
136        gc.gridx = 0;
137        gc.gridy = 1;
138        gc.fill = GridBagConstraints.HORIZONTAL;
139        gc.weightx = 0.0;
140        pnl.add(new JLabel(tr("Comment:")), gc);
141
142        gc.fill = GridBagConstraints.BOTH;
143        gc.weightx = 1.0;
144        gc.weighty = 1.0;
145        gc.gridx = 1;
146        pnl.add(taComment, gc);
147        taComment.setEditable(false);
148
149        //-- Open/Closed
150        gc.gridx = 0;
151        gc.gridy = 2;
152        gc.fill = GridBagConstraints.HORIZONTAL;
153        gc.weightx = 0.0;
154        gc.weighty = 0.0;
155        pnl.add(new JLabel(tr("Open/Closed:")), gc);
156
157        gc.fill = GridBagConstraints.HORIZONTAL;
158        gc.gridx = 1;
159        pnl.add(tfOpen, gc);
160        tfOpen.setEditable(false);
161
162        //-- Created by:
163        gc.gridx = 0;
164        gc.gridy = 3;
165        gc.fill = GridBagConstraints.HORIZONTAL;
166        gc.weightx = 0.0;
167        pnl.add(new JLabel(tr("Created by:")), gc);
168
169        gc.fill = GridBagConstraints.HORIZONTAL;
170        gc.weightx = 1.0;
171        gc.gridx = 1;
172        pnl.add(tfUser, gc);
173        tfUser.setEditable(false);
174
175        //-- Created On:
176        gc.gridx = 0;
177        gc.gridy = 4;
178        gc.fill = GridBagConstraints.HORIZONTAL;
179        gc.weightx = 0.0;
180        pnl.add(new JLabel(tr("Created on:")), gc);
181
182        gc.fill = GridBagConstraints.HORIZONTAL;
183        gc.gridx = 1;
184        pnl.add(tfCreatedOn, gc);
185        tfCreatedOn.setEditable(false);
186
187        //-- Closed On:
188        gc.gridx = 0;
189        gc.gridy = 5;
190        gc.fill = GridBagConstraints.HORIZONTAL;
191        gc.weightx = 0.0;
192        pnl.add(new JLabel(tr("Closed on:")), gc);
193
194        gc.fill = GridBagConstraints.HORIZONTAL;
195        gc.gridx = 1;
196        pnl.add(tfClosedOn, gc);
197        tfClosedOn.setEditable(false);
198
199        return pnl;
200    }
201
202    protected final void build() {
203        setLayout(new BorderLayout());
204        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
205        add(buildDetailViewPanel(), BorderLayout.CENTER);
206        add(buildActionButtonPanel(), BorderLayout.WEST);
207    }
208
209    protected void clearView() {
210        tfID.setText("");
211        taComment.setText("");
212        tfOpen.setText("");
213        tfUser.setText("");
214        tfCreatedOn.setText("");
215        tfClosedOn.setText("");
216    }
217
218    protected void updateView(Changeset cs) {
219        String msg;
220        if (cs == null) return;
221        tfID.setText(Integer.toString(cs.getId()));
222        String comment = cs.get("comment");
223        taComment.setText(comment == null ? "" : comment);
224
225        if (cs.isOpen()) {
226            msg = trc("changeset.state", "Open");
227        } else {
228            msg = trc("changeset.state", "Closed");
229        }
230        tfOpen.setText(msg);
231
232        if (cs.getUser() == null) {
233            msg = tr("anonymous");
234        } else {
235            msg = cs.getUser().getName();
236        }
237        tfUser.setText(msg);
238        DateFormat sdf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.SHORT);
239
240        tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt()));
241        tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt()));
242    }
243
244    /**
245     * Constructs a new {@code ChangesetDetailPanel}.
246     */
247    public ChangesetDetailPanel() {
248        build();
249    }
250
251    protected void setCurrentChangeset(Changeset cs) {
252        currentChangeset = cs;
253        if (cs == null) {
254            clearView();
255        } else {
256            updateView(cs);
257        }
258        actDownloadChangesetContent.initProperties();
259        actUpdateChangesets.initProperties(currentChangeset);
260        actRemoveFromCache.initProperties(currentChangeset);
261        actSelectInCurrentLayer.updateEnabledState();
262        actZoomInCurrentLayerAction.updateEnabledState();
263    }
264
265    /* ---------------------------------------------------------------------------- */
266    /* interface PropertyChangeListener                                             */
267    /* ---------------------------------------------------------------------------- */
268    @Override
269    public void propertyChange(PropertyChangeEvent evt) {
270        if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
271            return;
272        setCurrentChangeset((Changeset) evt.getNewValue());
273    }
274
275    /**
276     * The action for removing the currently selected changeset from the changeset cache
277     */
278    class RemoveFromCacheAction extends AbstractAction {
279        RemoveFromCacheAction() {
280            putValue(NAME, tr("Remove from cache"));
281            new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this);
282            putValue(SHORT_DESCRIPTION, tr("Remove the changeset in the detail view panel from the local cache"));
283        }
284
285        @Override
286        public void actionPerformed(ActionEvent evt) {
287            if (currentChangeset == null)
288                return;
289            ChangesetCache.getInstance().remove(currentChangeset);
290        }
291
292        public void initProperties(Changeset cs) {
293            setEnabled(cs != null);
294        }
295    }
296
297    /**
298     * Updates the current changeset from the OSM server
299     *
300     */
301    class UpdateChangesetAction extends AbstractAction {
302        UpdateChangesetAction() {
303            putValue(NAME, tr("Update changeset"));
304            new ImageProvider("dialogs/changeset", "updatechangesetcontent").getResource().attachImageIcon(this);
305            putValue(SHORT_DESCRIPTION, tr("Update the changeset from the OSM server"));
306        }
307
308        @Override
309        public void actionPerformed(ActionEvent evt) {
310            if (currentChangeset == null)
311                return;
312            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(
313                    ChangesetDetailPanel.this,
314                    Collections.singleton(currentChangeset.getId())
315            );
316            Main.worker.submit(new PostDownloadHandler(task, task.download()));
317        }
318
319        public void initProperties(Changeset cs) {
320            setEnabled(cs != null && !Main.isOffline(OnlineResource.OSM_API));
321        }
322    }
323
324    /**
325     * Selects the primitives in the content of this changeset in the current data layer.
326     *
327     */
328    class SelectInCurrentLayerAction extends AbstractAction implements ActiveLayerChangeListener {
329
330        SelectInCurrentLayerAction() {
331            putValue(NAME, tr("Select in layer"));
332            new ImageProvider("dialogs", "select").getResource().attachImageIcon(this);
333            putValue(SHORT_DESCRIPTION, tr("Select the primitives in the content of this changeset in the current data layer"));
334            updateEnabledState();
335        }
336
337        protected void alertNoPrimitivesToSelect() {
338            HelpAwareOptionPane.showOptionDialog(
339                    ChangesetDetailPanel.this,
340                    tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
341                            + "edit layer ''{1}''.</html>",
342                            currentChangeset.getId(),
343                            Utils.escapeReservedCharactersHTML(Main.getLayerManager().getEditLayer().getName())
344                    ),
345                    tr("Nothing to select"),
346                    JOptionPane.WARNING_MESSAGE,
347                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
348            );
349        }
350
351        @Override
352        public void actionPerformed(ActionEvent arg0) {
353            if (!isEnabled())
354                return;
355            OsmDataLayer layer = Main.getLayerManager().getEditLayer();
356            if (layer == null) {
357                return;
358            }
359            Set<OsmPrimitive> target = new HashSet<>();
360            for (OsmPrimitive p: layer.data.allPrimitives()) {
361                if (p.isUsable() && p.getChangesetId() == currentChangeset.getId()) {
362                    target.add(p);
363                }
364            }
365            if (target.isEmpty()) {
366                alertNoPrimitivesToSelect();
367                return;
368            }
369            layer.data.setSelected(target);
370        }
371
372        public void updateEnabledState() {
373            setEnabled(Main.getLayerManager().getEditLayer() != null && currentChangeset != null);
374        }
375
376        @Override
377        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
378            updateEnabledState();
379        }
380    }
381
382    /**
383     * Zooms to the primitives in the content of this changeset in the current
384     * data layer.
385     *
386     */
387    class ZoomInCurrentLayerAction extends AbstractAction implements ActiveLayerChangeListener {
388
389        ZoomInCurrentLayerAction() {
390            putValue(NAME, tr("Zoom to in layer"));
391            new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this);
392            putValue(SHORT_DESCRIPTION, tr("Zoom to the objects in the content of this changeset in the current data layer"));
393            updateEnabledState();
394        }
395
396        protected void alertNoPrimitivesToZoomTo() {
397            HelpAwareOptionPane.showOptionDialog(
398                    ChangesetDetailPanel.this,
399                    tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
400                            + "edit layer ''{1}''.</html>",
401                            currentChangeset.getId(),
402                            Main.getLayerManager().getEditLayer().getName()
403                    ),
404                    tr("Nothing to zoom to"),
405                    JOptionPane.WARNING_MESSAGE,
406                    HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
407            );
408        }
409
410        @Override
411        public void actionPerformed(ActionEvent arg0) {
412            if (!isEnabled())
413                return;
414            OsmDataLayer layer = Main.getLayerManager().getEditLayer();
415            if (layer == null) {
416                return;
417            }
418            Set<OsmPrimitive> target = new HashSet<>();
419            for (OsmPrimitive p: layer.data.allPrimitives()) {
420                if (p.isUsable() && p.getChangesetId() == currentChangeset.getId()) {
421                    target.add(p);
422                }
423            }
424            if (target.isEmpty()) {
425                alertNoPrimitivesToZoomTo();
426                return;
427            }
428            layer.data.setSelected(target);
429            AutoScaleAction.zoomToSelection();
430        }
431
432        public void updateEnabledState() {
433            setEnabled(Main.getLayerManager().getEditLayer() != null && currentChangeset != null);
434        }
435
436        @Override
437        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
438            updateEnabledState();
439        }
440    }
441
442    @Override
443    public Changeset getCurrentChangeset() {
444        return currentChangeset;
445    }
446}
Note: See TracBrowser for help on using the repository browser.