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

Last change on this file since 1852 was 1852, checked in by Gubaer, 15 years ago

fixed #3094: Relation editor tries to download objects with id:0 from the server
Parent relations now also displayed for new relations

File size: 19.9 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;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dialog;
9import java.awt.FlowLayout;
10import java.awt.event.ActionEvent;
11import java.io.IOException;
12import java.net.HttpURLConnection;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.List;
16import java.util.Set;
17import java.util.Stack;
18import java.util.logging.Logger;
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.OsmPrimitiveType;
33import org.openstreetmap.josm.data.osm.Relation;
34import org.openstreetmap.josm.data.osm.RelationMember;
35import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
36import org.openstreetmap.josm.gui.OptionPaneUtil;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
38import org.openstreetmap.josm.gui.PrimitiveNameFormatter;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
41import org.openstreetmap.josm.gui.progress.ProgressMonitor;
42import org.openstreetmap.josm.io.OsmApi;
43import org.openstreetmap.josm.io.OsmApiException;
44import org.openstreetmap.josm.io.OsmServerObjectReader;
45import org.openstreetmap.josm.io.OsmTransferException;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.xml.sax.SAXException;
48
49/**
50 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
51 * structure of relations
52 *
53 *
54 */
55public class ChildRelationBrowser extends JPanel {
56 static private final Logger logger = Logger.getLogger(ChildRelationBrowser.class.getName());
57
58 /** the tree with relation children */
59 private RelationTree childTree;
60 /** the tree model */
61 private RelationTreeModel model;
62
63 /** the osm data layer this browser is related to */
64 private OsmDataLayer layer;
65
66 /**
67 * Replies the {@see OsmDataLayer} this editor is related to
68 *
69 * @return the osm data layer
70 */
71 protected OsmDataLayer getLayer() {
72 return layer;
73 }
74
75 /**
76 * builds the UI
77 */
78 protected void build() {
79 setLayout(new BorderLayout());
80 childTree = new RelationTree(model);
81 JScrollPane pane = new JScrollPane(childTree);
82 add(pane, BorderLayout.CENTER);
83
84 add(buildButtonPanel(), BorderLayout.SOUTH);
85 }
86
87 /**
88 * builds the panel with the command buttons
89 *
90 * @return the button panel
91 */
92 protected JPanel buildButtonPanel() {
93 JPanel pnl = new JPanel();
94 pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
95
96 // ---
97 DownloadAllChildRelationsAction downloadAction= new DownloadAllChildRelationsAction();
98 pnl.add(new JButton(downloadAction));
99
100 // ---
101 DownloadSelectedAction downloadSelectedAction= new DownloadSelectedAction();
102 childTree.addTreeSelectionListener(downloadSelectedAction);
103 pnl.add(new JButton(downloadSelectedAction));
104
105 // ---
106 EditAction editAction = new EditAction();
107 childTree.addTreeSelectionListener(editAction);
108 pnl.add(new JButton(editAction));
109
110 return pnl;
111 }
112
113 /**
114 * constructor
115 *
116 * @param layer the {@see OsmDataLayer} this browser is related to. Must not be null.
117 * @exception IllegalArgumentException thrown, if layer is null
118 */
119 public ChildRelationBrowser(OsmDataLayer layer) throws IllegalArgumentException {
120 if (layer == null)
121 throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "layer"));
122 this.layer = layer;
123 model = new RelationTreeModel();
124 build();
125 }
126
127 /**
128 * constructor
129 *
130 * @param layer the {@see OsmDataLayer} this browser is related to. Must not be null.
131 * @param root the root relation
132 * @exception IllegalArgumentException thrown, if layer is null
133 */
134 public ChildRelationBrowser(OsmDataLayer layer, Relation root) throws IllegalArgumentException {
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 {@see 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 public EditAction() {
178 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
179 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
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.incomplete) {
197 continue;
198 }
199 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
200 editor.setVisible(true);
201 }
202 }
203
204 public void actionPerformed(ActionEvent e) {
205 if (!isEnabled())
206 return;
207 run();
208 }
209
210 public void valueChanged(TreeSelectionEvent e) {
211 refreshEnabled();
212 }
213 }
214
215 /**
216 * Action for downloading all child relations for a given parent relation.
217 * Recursively.
218 */
219 class DownloadAllChildRelationsAction extends AbstractAction{
220 public DownloadAllChildRelationsAction() {
221 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
222 putValue(SMALL_ICON, ImageProvider.get("download"));
223 putValue(NAME, tr("Download All Children"));
224 }
225
226 public void run() {
227 Main.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation)model.getRoot()));
228 }
229
230 public void actionPerformed(ActionEvent e) {
231 if (!isEnabled())
232 return;
233 run();
234 }
235 }
236
237 /**
238 * Action for downloading all selected relations
239 */
240 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
241 public DownloadSelectedAction() {
242 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
243 // FIXME: replace with better icon
244 //
245 putValue(SMALL_ICON, ImageProvider.get("download"));
246 putValue(NAME, tr("Download Selected Children"));
247 updateEnabledState();
248 }
249
250 protected void updateEnabledState() {
251 TreePath [] selection = childTree.getSelectionPaths();
252 setEnabled(selection != null && selection.length > 0);
253 }
254
255 public void run() {
256 TreePath [] selection = childTree.getSelectionPaths();
257 if (selection == null || selection.length == 0)
258 return;
259 HashSet<Relation> relations = new HashSet<Relation>();
260 for (int i=0; i < selection.length;i++) {
261 relations.add((Relation)selection[i].getLastPathComponent());
262 }
263 Main.worker.submit(new DownloadRelationSetTask(getParentDialog(),relations));
264 }
265
266 public void actionPerformed(ActionEvent e) {
267 if (!isEnabled())
268 return;
269 run();
270 }
271
272 public void valueChanged(TreeSelectionEvent e) {
273 updateEnabledState();
274 }
275 }
276
277 /**
278 * The asynchronous task for downloading relation members.
279 *
280 *
281 */
282 class DownloadAllChildrenTask extends PleaseWaitRunnable {
283 private boolean cancelled;
284 private int conflictsCount;
285 private Exception lastException;
286 private Relation relation;
287 private Stack<Relation> relationsToDownload;
288 private Set<Long> downloadedRelationIds;
289
290 public DownloadAllChildrenTask(Dialog parent, Relation r) {
291 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
292 * don't
293 * ignore
294 * exception
295 */);
296 this.relation = r;
297 relationsToDownload = new Stack<Relation>();
298 downloadedRelationIds = new HashSet<Long>();
299 relationsToDownload.push(this.relation);
300 }
301
302 @Override
303 protected void cancel() {
304 cancelled = true;
305 OsmApi.getOsmApi().cancel();
306 }
307
308 protected void showLastException() {
309 String msg = lastException.getMessage();
310 if (msg == null) {
311 msg = lastException.toString();
312 }
313 OptionPaneUtil.showMessageDialog(
314 Main.parent,
315 msg,
316 tr("Error"),
317 JOptionPane.ERROR_MESSAGE
318 );
319 }
320
321 protected void refreshView(Relation relation){
322 for (int i=0; i < childTree.getRowCount(); i++) {
323 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
324 if (reference == relation) {
325 model.refreshNode(childTree.getPathForRow(i));
326 }
327 }
328 }
329
330 @Override
331 protected void finish() {
332 if (cancelled)
333 return;
334 if (lastException != null) {
335 showLastException();
336 return;
337 }
338
339 if (conflictsCount > 0) {
340 OptionPaneUtil.showMessageDialog(
341 Main.parent,
342 tr("There were {0} conflicts during import.", conflictsCount),
343 tr("Conflicts in data"),
344 JOptionPane.WARNING_MESSAGE
345 );
346 }
347 }
348
349 /**
350 * warns the user if a relation couldn't be loaded because it was deleted on
351 * the server (the server replied a HTTP code 410)
352 *
353 * @param r the relation
354 */
355 protected void warnBecauseOfDeletedRelation(Relation r) {
356 PrimitiveNameFormatter nameFormatter = new PrimitiveNameFormatter();
357
358 String message = tr("<html>The child relation<br>"
359 + "{0}<br>"
360 + "is deleted on the server. It can't be loaded",
361 nameFormatter.getName(r)
362 );
363
364 OptionPaneUtil.showMessageDialog(
365 Main.parent,
366 message,
367 tr("Relation is deleted"),
368 JOptionPane.WARNING_MESSAGE
369 );
370 }
371
372 /**
373 * Remembers the child relations to download
374 *
375 * @param parent the parent relation
376 */
377 protected void rememberChildRelationsToDownload(Relation parent) {
378 downloadedRelationIds.add(parent.id);
379 for (RelationMember member: parent.members) {
380 if (member.member instanceof Relation) {
381 Relation child = (Relation)member.member;
382 if (!downloadedRelationIds.contains(child)) {
383 relationsToDownload.push(child);
384 }
385 }
386 }
387 }
388
389 /**
390 * Merges the primitives in <code>ds</code> to the dataset of the
391 * edit layer
392 *
393 * @param ds the data set
394 */
395 protected void mergeDataSet(DataSet ds) {
396 if (ds != null) {
397 final MergeVisitor visitor = new MergeVisitor(getLayer().data, ds);
398 visitor.merge();
399 // FIXME: this is necessary because there are dialogs listening
400 // for DataChangeEvents which manipulate Swing components on this
401 // thread.
402 //
403 SwingUtilities.invokeLater(new Runnable() {
404 public void run() {
405 getLayer().fireDataChange();
406 }
407 });
408 if (!visitor.getConflicts().isEmpty()) {
409 getLayer().getConflicts().add(visitor.getConflicts());
410 conflictsCount += visitor.getConflicts().size();
411 }
412 }
413 }
414
415 @Override
416 protected void realRun() throws SAXException, IOException, OsmTransferException {
417 try {
418 PrimitiveNameFormatter nameFormatter = new PrimitiveNameFormatter();
419 while(! relationsToDownload.isEmpty() && !cancelled) {
420 Relation r = relationsToDownload.pop();
421 if (r.id == 0) {
422 continue;
423 }
424 rememberChildRelationsToDownload(r);
425 progressMonitor.setCustomText(tr("Downloading relation {0}", nameFormatter.getName(r)));
426 OsmServerObjectReader reader = new OsmServerObjectReader(r.id, 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 } catch (Exception e) {
443 if (cancelled) {
444 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
445 .toString()));
446 return;
447 }
448 lastException = e;
449 }
450 }
451 }
452
453
454 /**
455 * The asynchronous task for downloading a set of relations
456 */
457 class DownloadRelationSetTask extends PleaseWaitRunnable {
458 private boolean cancelled;
459 private int conflictsCount;
460 private Exception lastException;
461 private Set<Relation> relations;
462
463 public DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
464 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
465 * don't
466 * ignore
467 * exception
468 */);
469 this.relations = relations;
470 }
471
472 @Override
473 protected void cancel() {
474 cancelled = true;
475 OsmApi.getOsmApi().cancel();
476 }
477
478 protected void showLastException() {
479 String msg = lastException.getMessage();
480 if (msg == null) {
481 msg = lastException.toString();
482 }
483 OptionPaneUtil.showMessageDialog(
484 Main.parent,
485 msg,
486 tr("Error"),
487 JOptionPane.ERROR_MESSAGE
488 );
489 }
490
491 protected void refreshView(Relation relation){
492 for (int i=0; i < childTree.getRowCount(); i++) {
493 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
494 if (reference == relation) {
495 model.refreshNode(childTree.getPathForRow(i));
496 }
497 }
498 }
499
500 @Override
501 protected void finish() {
502 if (cancelled)
503 return;
504 if (lastException != null) {
505 showLastException();
506 return;
507 }
508
509 if (conflictsCount > 0) {
510 OptionPaneUtil.showMessageDialog(
511 Main.parent,
512 tr("There were {0} conflicts during import.", conflictsCount),
513 tr("Conflicts in data"),
514 JOptionPane.WARNING_MESSAGE
515 );
516 }
517 }
518
519 protected void mergeDataSet(DataSet dataSet) {
520 if (dataSet != null) {
521 final MergeVisitor visitor = new MergeVisitor(getLayer().data, dataSet);
522 visitor.merge();
523 // FIXME: this is necessary because there are dialogs listening
524 // for DataChangeEvents which manipulate Swing components on this
525 // thread.
526 //
527 SwingUtilities.invokeLater(new Runnable() {
528 public void run() {
529 getLayer().fireDataChange();
530 }
531 });
532 if (!visitor.getConflicts().isEmpty()) {
533 getLayer().getConflicts().add(visitor.getConflicts());
534 conflictsCount += visitor.getConflicts().size();
535 }
536 }
537 }
538
539 @Override
540 protected void realRun() throws SAXException, IOException, OsmTransferException {
541 try {
542 PrimitiveNameFormatter nameFormatter = new PrimitiveNameFormatter();
543 Iterator<Relation> it = relations.iterator();
544 while(it.hasNext() && !cancelled) {
545 Relation r = it.next();
546 if (r.id == 0) {
547 continue;
548 }
549 progressMonitor.setCustomText(tr("Downloading relation {0}", nameFormatter.getName(r)));
550 OsmServerObjectReader reader = new OsmServerObjectReader(r.id, OsmPrimitiveType.RELATION,
551 true);
552 DataSet dataSet = reader.parseOsm(progressMonitor
553 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
554 mergeDataSet(dataSet);
555 refreshView(r);
556 }
557 } catch (Exception e) {
558 if (cancelled) {
559 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
560 .toString()));
561 return;
562 }
563 lastException = e;
564 }
565 }
566 }
567}
Note: See TracBrowser for help on using the repository browser.