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

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

removed OptionPaneUtil
cleanup of deprecated Layer API
cleanup of deprecated APIs in OsmPrimitive and Way
cleanup of imports

File size: 18.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.DefaultNameFormatter;
37import org.openstreetmap.josm.gui.ExceptionDialogUtil;
38import org.openstreetmap.josm.gui.PleaseWaitRunnable;
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 refreshView(Relation relation){
309 for (int i=0; i < childTree.getRowCount(); i++) {
310 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
311 if (reference == relation) {
312 model.refreshNode(childTree.getPathForRow(i));
313 }
314 }
315 }
316
317 @Override
318 protected void finish() {
319 if (cancelled)
320 return;
321 if (lastException != null) {
322 ExceptionDialogUtil.explainException(lastException);
323 return;
324 }
325
326 if (conflictsCount > 0) {
327 JOptionPane.showMessageDialog(
328 Main.parent,
329 tr("There were {0} conflicts during import.", conflictsCount),
330 tr("Conflicts in data"),
331 JOptionPane.WARNING_MESSAGE
332 );
333 }
334 }
335
336 /**
337 * warns the user if a relation couldn't be loaded because it was deleted on
338 * the server (the server replied a HTTP code 410)
339 *
340 * @param r the relation
341 */
342 protected void warnBecauseOfDeletedRelation(Relation r) {
343 String message = tr("<html>The child relation<br>"
344 + "{0}<br>"
345 + "is deleted on the server. It can't be loaded",
346 r.getDisplayName(DefaultNameFormatter.getInstance())
347 );
348
349 JOptionPane.showMessageDialog(
350 Main.parent,
351 message,
352 tr("Relation is deleted"),
353 JOptionPane.WARNING_MESSAGE
354 );
355 }
356
357 /**
358 * Remembers the child relations to download
359 *
360 * @param parent the parent relation
361 */
362 protected void rememberChildRelationsToDownload(Relation parent) {
363 downloadedRelationIds.add(parent.id);
364 for (RelationMember member: parent.getMembers()) {
365 if (member.isRelation()) {
366 Relation child = member.getRelation();
367 if (!downloadedRelationIds.contains(child)) {
368 relationsToDownload.push(child);
369 }
370 }
371 }
372 }
373
374 /**
375 * Merges the primitives in <code>ds</code> to the dataset of the
376 * edit layer
377 *
378 * @param ds the data set
379 */
380 protected void mergeDataSet(DataSet ds) {
381 if (ds != null) {
382 final MergeVisitor visitor = new MergeVisitor(getLayer().data, ds);
383 visitor.merge();
384 // FIXME: this is necessary because there are dialogs listening
385 // for DataChangeEvents which manipulate Swing components on this
386 // thread.
387 //
388 SwingUtilities.invokeLater(new Runnable() {
389 public void run() {
390 getLayer().fireDataChange();
391 }
392 });
393 if (!visitor.getConflicts().isEmpty()) {
394 getLayer().getConflicts().add(visitor.getConflicts());
395 conflictsCount += visitor.getConflicts().size();
396 }
397 }
398 }
399
400 @Override
401 protected void realRun() throws SAXException, IOException, OsmTransferException {
402 try {
403 while(! relationsToDownload.isEmpty() && !cancelled) {
404 Relation r = relationsToDownload.pop();
405 if (r.id == 0) {
406 continue;
407 }
408 rememberChildRelationsToDownload(r);
409 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
410 OsmServerObjectReader reader = new OsmServerObjectReader(r.id, OsmPrimitiveType.RELATION,
411 true);
412 DataSet dataSet = null;
413 try {
414 dataSet = reader.parseOsm(progressMonitor
415 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
416 } catch(OsmApiException e) {
417 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
418 warnBecauseOfDeletedRelation(r);
419 continue;
420 }
421 throw e;
422 }
423 mergeDataSet(dataSet);
424 refreshView(r);
425 }
426 } catch (Exception e) {
427 if (cancelled) {
428 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
429 .toString()));
430 return;
431 }
432 lastException = e;
433 }
434 }
435 }
436
437
438 /**
439 * The asynchronous task for downloading a set of relations
440 */
441 class DownloadRelationSetTask extends PleaseWaitRunnable {
442 private boolean cancelled;
443 private int conflictsCount;
444 private Exception lastException;
445 private Set<Relation> relations;
446
447 public DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
448 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
449 * don't
450 * ignore
451 * exception
452 */);
453 this.relations = relations;
454 }
455
456 @Override
457 protected void cancel() {
458 cancelled = true;
459 OsmApi.getOsmApi().cancel();
460 }
461
462 protected void refreshView(Relation relation){
463 for (int i=0; i < childTree.getRowCount(); i++) {
464 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
465 if (reference == relation) {
466 model.refreshNode(childTree.getPathForRow(i));
467 }
468 }
469 }
470
471 @Override
472 protected void finish() {
473 if (cancelled)
474 return;
475 if (lastException != null) {
476 ExceptionDialogUtil.explainException(lastException);
477 return;
478 }
479
480 if (conflictsCount > 0) {
481 JOptionPane.showMessageDialog(
482 Main.parent,
483 tr("There were {0} conflicts during import.", conflictsCount),
484 tr("Conflicts in data"),
485 JOptionPane.WARNING_MESSAGE
486 );
487 }
488 }
489
490 protected void mergeDataSet(DataSet dataSet) {
491 if (dataSet != null) {
492 final MergeVisitor visitor = new MergeVisitor(getLayer().data, dataSet);
493 visitor.merge();
494 // FIXME: this is necessary because there are dialogs listening
495 // for DataChangeEvents which manipulate Swing components on this
496 // thread.
497 //
498 SwingUtilities.invokeLater(new Runnable() {
499 public void run() {
500 getLayer().fireDataChange();
501 }
502 });
503 if (!visitor.getConflicts().isEmpty()) {
504 getLayer().getConflicts().add(visitor.getConflicts());
505 conflictsCount += visitor.getConflicts().size();
506 }
507 }
508 }
509
510 @Override
511 protected void realRun() throws SAXException, IOException, OsmTransferException {
512 try {
513 Iterator<Relation> it = relations.iterator();
514 while(it.hasNext() && !cancelled) {
515 Relation r = it.next();
516 if (r.id == 0) {
517 continue;
518 }
519 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
520 OsmServerObjectReader reader = new OsmServerObjectReader(r.id, OsmPrimitiveType.RELATION,
521 true);
522 DataSet dataSet = reader.parseOsm(progressMonitor
523 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
524 mergeDataSet(dataSet);
525 refreshView(r);
526 }
527 } catch (Exception e) {
528 if (cancelled) {
529 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
530 .toString()));
531 return;
532 }
533 lastException = e;
534 }
535 }
536 }
537}
Note: See TracBrowser for help on using the repository browser.