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, 7 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.