source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java@ 13130

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

fix #15572 - use ImageProvider attach API for all JOSM actions to ensure proper icon size everywhere

  • Property svn:eol-style set to native
File size: 16.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
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.Component;
9import java.awt.Dialog;
10import java.awt.FlowLayout;
11import java.awt.event.ActionEvent;
12import java.io.IOException;
13import java.net.HttpURLConnection;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Set;
18import java.util.Stack;
19
20import javax.swing.AbstractAction;
21import javax.swing.JButton;
22import javax.swing.JOptionPane;
23import javax.swing.JPanel;
24import javax.swing.JScrollPane;
25import javax.swing.SwingUtilities;
26import javax.swing.event.TreeSelectionEvent;
27import javax.swing.event.TreeSelectionListener;
28import javax.swing.tree.TreePath;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.DataSetMerger;
33import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
34import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
35import org.openstreetmap.josm.data.osm.Relation;
36import org.openstreetmap.josm.data.osm.RelationMember;
37import org.openstreetmap.josm.gui.ExceptionDialogUtil;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.PleaseWaitRunnable;
40import org.openstreetmap.josm.gui.layer.OsmDataLayer;
41import org.openstreetmap.josm.gui.progress.ProgressMonitor;
42import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
43import org.openstreetmap.josm.io.OsmApi;
44import org.openstreetmap.josm.io.OsmApiException;
45import org.openstreetmap.josm.io.OsmServerObjectReader;
46import org.openstreetmap.josm.io.OsmTransferException;
47import org.openstreetmap.josm.tools.CheckParameterUtil;
48import org.openstreetmap.josm.tools.ImageProvider;
49import org.openstreetmap.josm.tools.Logging;
50import org.openstreetmap.josm.tools.Utils;
51import org.xml.sax.SAXException;
52
53/**
54 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
55 * structure of relations.
56 *
57 * @since 1828
58 */
59public class ChildRelationBrowser extends JPanel {
60 /** the tree with relation children */
61 private RelationTree childTree;
62 /** the tree model */
63 private transient RelationTreeModel model;
64
65 /** the osm data layer this browser is related to */
66 private transient OsmDataLayer layer;
67
68 /**
69 * Replies the {@link OsmDataLayer} this editor is related to
70 *
71 * @return the osm data layer
72 */
73 protected OsmDataLayer getLayer() {
74 return layer;
75 }
76
77 /**
78 * builds the UI
79 */
80 protected void build() {
81 setLayout(new BorderLayout());
82 childTree = new RelationTree(model);
83 JScrollPane pane = new JScrollPane(childTree);
84 add(pane, BorderLayout.CENTER);
85
86 add(buildButtonPanel(), BorderLayout.SOUTH);
87 }
88
89 /**
90 * builds the panel with the command buttons
91 *
92 * @return the button panel
93 */
94 protected JPanel buildButtonPanel() {
95 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
96
97 // ---
98 DownloadAllChildRelationsAction downloadAction = new DownloadAllChildRelationsAction();
99 pnl.add(new JButton(downloadAction));
100
101 // ---
102 DownloadSelectedAction downloadSelectedAction = new DownloadSelectedAction();
103 childTree.addTreeSelectionListener(downloadSelectedAction);
104 pnl.add(new JButton(downloadSelectedAction));
105
106 // ---
107 EditAction editAction = new EditAction();
108 childTree.addTreeSelectionListener(editAction);
109 pnl.add(new JButton(editAction));
110
111 return pnl;
112 }
113
114 /**
115 * constructor
116 *
117 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
118 * @throws IllegalArgumentException if layer is null
119 */
120 public ChildRelationBrowser(OsmDataLayer layer) {
121 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
122 this.layer = layer;
123 model = new RelationTreeModel();
124 build();
125 }
126
127 /**
128 * constructor
129 *
130 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
131 * @param root the root relation
132 * @throws IllegalArgumentException if layer is null
133 */
134 public ChildRelationBrowser(OsmDataLayer layer, Relation root) {
135 this(layer);
136 populate(root);
137 }
138
139 /**
140 * populates the browser with a relation
141 *
142 * @param r the relation
143 */
144 public void populate(Relation r) {
145 model.populate(r);
146 }
147
148 /**
149 * populates the browser with a list of relation members
150 *
151 * @param members the list of relation members
152 */
153
154 public void populate(List<RelationMember> members) {
155 model.populate(members);
156 }
157
158 /**
159 * replies the parent dialog this browser is embedded in
160 *
161 * @return the parent dialog; null, if there is no {@link Dialog} as parent dialog
162 */
163 protected Dialog getParentDialog() {
164 Component c = this;
165 while (c != null && !(c instanceof Dialog)) {
166 c = c.getParent();
167 }
168 return (Dialog) c;
169 }
170
171 /**
172 * Action for editing the currently selected relation
173 *
174 *
175 */
176 class EditAction extends AbstractAction implements TreeSelectionListener {
177 EditAction() {
178 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to."));
179 new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this, true);
180 putValue(NAME, tr("Edit"));
181 refreshEnabled();
182 }
183
184 protected void refreshEnabled() {
185 TreePath[] selection = childTree.getSelectionPaths();
186 setEnabled(selection != null && selection.length > 0);
187 }
188
189 public void run() {
190 TreePath[] selection = childTree.getSelectionPaths();
191 if (selection == null || selection.length == 0) return;
192 // do not launch more than 10 relation editors in parallel
193 //
194 for (int i = 0; i < Math.min(selection.length, 10); i++) {
195 Relation r = (Relation) selection[i].getLastPathComponent();
196 if (r.isIncomplete()) {
197 continue;
198 }
199 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
200 editor.setVisible(true);
201 }
202 }
203
204 @Override
205 public void actionPerformed(ActionEvent e) {
206 if (!isEnabled())
207 return;
208 run();
209 }
210
211 @Override
212 public void valueChanged(TreeSelectionEvent e) {
213 refreshEnabled();
214 }
215 }
216
217 /**
218 * Action for downloading all child relations for a given parent relation.
219 * Recursively.
220 */
221 class DownloadAllChildRelationsAction extends AbstractAction {
222 DownloadAllChildRelationsAction() {
223 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
224 new ImageProvider("download").getResource().attachImageIcon(this, true);
225 putValue(NAME, tr("Download All Children"));
226 }
227
228 public void run() {
229 MainApplication.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation) model.getRoot()));
230 }
231
232 @Override
233 public void actionPerformed(ActionEvent e) {
234 if (!isEnabled())
235 return;
236 run();
237 }
238 }
239
240 /**
241 * Action for downloading all selected relations
242 */
243 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
244 DownloadSelectedAction() {
245 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
246 // FIXME: replace with better icon
247 new ImageProvider("download").getResource().attachImageIcon(this, true);
248 putValue(NAME, tr("Download Selected Children"));
249 updateEnabledState();
250 }
251
252 protected void updateEnabledState() {
253 TreePath[] selection = childTree.getSelectionPaths();
254 setEnabled(selection != null && selection.length > 0);
255 }
256
257 public void run() {
258 TreePath[] selection = childTree.getSelectionPaths();
259 if (selection == null || selection.length == 0)
260 return;
261 Set<Relation> relations = new HashSet<>();
262 for (TreePath aSelection : selection) {
263 relations.add((Relation) aSelection.getLastPathComponent());
264 }
265 MainApplication.worker.submit(new DownloadRelationSetTask(getParentDialog(), relations));
266 }
267
268 @Override
269 public void actionPerformed(ActionEvent e) {
270 if (!isEnabled())
271 return;
272 run();
273 }
274
275 @Override
276 public void valueChanged(TreeSelectionEvent e) {
277 updateEnabledState();
278 }
279 }
280
281 abstract class DownloadTask extends PleaseWaitRunnable {
282 protected boolean canceled;
283 protected int conflictsCount;
284 protected Exception lastException;
285
286 DownloadTask(String title, Dialog parent) {
287 super(title, new PleaseWaitProgressMonitor(parent), false);
288 }
289
290 @Override
291 protected void cancel() {
292 canceled = true;
293 OsmApi.getOsmApi().cancel();
294 }
295
296 protected void refreshView(Relation relation) {
297 for (int i = 0; i < childTree.getRowCount(); i++) {
298 Relation reference = (Relation) childTree.getPathForRow(i).getLastPathComponent();
299 if (reference == relation) {
300 model.refreshNode(childTree.getPathForRow(i));
301 }
302 }
303 }
304
305 @Override
306 protected void finish() {
307 if (canceled)
308 return;
309 if (lastException != null) {
310 ExceptionDialogUtil.explainException(lastException);
311 return;
312 }
313
314 if (conflictsCount > 0) {
315 JOptionPane.showMessageDialog(
316 Main.parent,
317 trn("There was {0} conflict during import.",
318 "There were {0} conflicts during import.",
319 conflictsCount, conflictsCount),
320 trn("Conflict in data", "Conflicts in data", conflictsCount),
321 JOptionPane.WARNING_MESSAGE
322 );
323 }
324 }
325 }
326
327 /**
328 * The asynchronous task for downloading relation members.
329 */
330 class DownloadAllChildrenTask extends DownloadTask {
331 private final Stack<Relation> relationsToDownload;
332 private final Set<Long> downloadedRelationIds;
333
334 DownloadAllChildrenTask(Dialog parent, Relation r) {
335 super(tr("Download relation members"), parent);
336 relationsToDownload = new Stack<>();
337 downloadedRelationIds = new HashSet<>();
338 relationsToDownload.push(r);
339 }
340
341 /**
342 * warns the user if a relation couldn't be loaded because it was deleted on
343 * the server (the server replied a HTTP code 410)
344 *
345 * @param r the relation
346 */
347 protected void warnBecauseOfDeletedRelation(Relation r) {
348 String message = tr("<html>The child relation<br>"
349 + "{0}<br>"
350 + "is deleted on the server. It cannot be loaded</html>",
351 Utils.escapeReservedCharactersHTML(r.getDisplayName(DefaultNameFormatter.getInstance()))
352 );
353
354 JOptionPane.showMessageDialog(
355 Main.parent,
356 message,
357 tr("Relation is deleted"),
358 JOptionPane.WARNING_MESSAGE
359 );
360 }
361
362 /**
363 * Remembers the child relations to download
364 *
365 * @param parent the parent relation
366 */
367 protected void rememberChildRelationsToDownload(Relation parent) {
368 downloadedRelationIds.add(parent.getId());
369 for (RelationMember member: parent.getMembers()) {
370 if (member.isRelation()) {
371 Relation child = member.getRelation();
372 if (!downloadedRelationIds.contains(child.getId())) {
373 relationsToDownload.push(child);
374 }
375 }
376 }
377 }
378
379 /**
380 * Merges the primitives in <code>ds</code> to the dataset of the
381 * edit layer
382 *
383 * @param ds the data set
384 */
385 protected void mergeDataSet(DataSet ds) {
386 if (ds != null) {
387 final DataSetMerger visitor = new DataSetMerger(getLayer().data, ds);
388 visitor.merge();
389 if (!visitor.getConflicts().isEmpty()) {
390 getLayer().getConflicts().add(visitor.getConflicts());
391 conflictsCount += visitor.getConflicts().size();
392 }
393 }
394 }
395
396 @Override
397 protected void realRun() throws SAXException, IOException, OsmTransferException {
398 try {
399 while (!relationsToDownload.isEmpty() && !canceled) {
400 Relation r = relationsToDownload.pop();
401 if (r.isNew()) {
402 continue;
403 }
404 rememberChildRelationsToDownload(r);
405 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
406 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
407 true);
408 DataSet dataSet = null;
409 try {
410 dataSet = reader.parseOsm(progressMonitor
411 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
412 } catch (OsmApiException e) {
413 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
414 warnBecauseOfDeletedRelation(r);
415 continue;
416 }
417 throw e;
418 }
419 mergeDataSet(dataSet);
420 refreshView(r);
421 }
422 SwingUtilities.invokeLater(MainApplication.getMap()::repaint);
423 } catch (OsmTransferException e) {
424 if (canceled) {
425 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
426 return;
427 }
428 lastException = e;
429 }
430 }
431 }
432
433 /**
434 * The asynchronous task for downloading a set of relations
435 */
436 class DownloadRelationSetTask extends DownloadTask {
437 private final Set<Relation> relations;
438
439 DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
440 super(tr("Download relation members"), parent);
441 this.relations = relations;
442 }
443
444 protected void mergeDataSet(DataSet dataSet) {
445 if (dataSet != null) {
446 final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
447 visitor.merge();
448 if (!visitor.getConflicts().isEmpty()) {
449 getLayer().getConflicts().add(visitor.getConflicts());
450 conflictsCount += visitor.getConflicts().size();
451 }
452 }
453 }
454
455 @Override
456 protected void realRun() throws SAXException, IOException, OsmTransferException {
457 try {
458 Iterator<Relation> it = relations.iterator();
459 while (it.hasNext() && !canceled) {
460 Relation r = it.next();
461 if (r.isNew()) {
462 continue;
463 }
464 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
465 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
466 true);
467 DataSet dataSet = reader.parseOsm(progressMonitor
468 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
469 mergeDataSet(dataSet);
470 refreshView(r);
471 }
472 } catch (OsmTransferException e) {
473 if (canceled) {
474 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
475 return;
476 }
477 lastException = e;
478 }
479 }
480 }
481}
Note: See TracBrowser for help on using the repository browser.