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

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

see #16288 - replace similar i18n strings (removes 35 strings from Launchpad)

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