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

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

new: tree-like browser for child relations
new: action/task for recursively downloading all child relations. Use case: download a complete network with cycle routes in one step
applied #2953: patch by cjw - Sorting a relation adds new members or crashes

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