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

Last change on this file since 5925 was 5925, checked in by bastiK, 11 years ago

replace tabs by spaces

  • Property svn:eol-style set to native
File size: 18.1 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.OsmPrimitiveType;
34import org.openstreetmap.josm.data.osm.Relation;
35import org.openstreetmap.josm.data.osm.RelationMember;
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.CheckParameterUtil;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.xml.sax.SAXException;
49
50/**
51 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
52 * structure of relations
53 *
54 *
55 */
56public class ChildRelationBrowser extends JPanel {
57 /** the tree with relation children */
58 private RelationTree childTree;
59 /** the tree model */
60 private RelationTreeModel model;
61
62 /** the osm data layer this browser is related to */
63 private OsmDataLayer layer;
64
65 /**
66 * Replies the {@link OsmDataLayer} this editor is related to
67 *
68 * @return the osm data layer
69 */
70 protected OsmDataLayer getLayer() {
71 return layer;
72 }
73
74 /**
75 * builds the UI
76 */
77 protected void build() {
78 setLayout(new BorderLayout());
79 childTree = new RelationTree(model);
80 JScrollPane pane = new JScrollPane(childTree);
81 add(pane, BorderLayout.CENTER);
82
83 add(buildButtonPanel(), BorderLayout.SOUTH);
84 }
85
86 /**
87 * builds the panel with the command buttons
88 *
89 * @return the button panel
90 */
91 protected JPanel buildButtonPanel() {
92 JPanel pnl = new JPanel();
93 pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
94
95 // ---
96 DownloadAllChildRelationsAction downloadAction= new DownloadAllChildRelationsAction();
97 pnl.add(new JButton(downloadAction));
98
99 // ---
100 DownloadSelectedAction downloadSelectedAction= new DownloadSelectedAction();
101 childTree.addTreeSelectionListener(downloadSelectedAction);
102 pnl.add(new JButton(downloadSelectedAction));
103
104 // ---
105 EditAction editAction = new EditAction();
106 childTree.addTreeSelectionListener(editAction);
107 pnl.add(new JButton(editAction));
108
109 return pnl;
110 }
111
112 /**
113 * constructor
114 *
115 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
116 * @exception IllegalArgumentException thrown, if layer is null
117 */
118 public ChildRelationBrowser(OsmDataLayer layer) throws IllegalArgumentException {
119 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
120 this.layer = layer;
121 model = new RelationTreeModel();
122 build();
123 }
124
125 /**
126 * constructor
127 *
128 * @param layer the {@link 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 {@link 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.isIncomplete()) {
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 (TreePath aSelection : selection) {
259 relations.add((Relation) aSelection.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 canceled;
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 canceled = true;
303 OsmApi.getOsmApi().cancel();
304 }
305
306 protected void refreshView(Relation relation){
307 for (int i=0; i < childTree.getRowCount(); i++) {
308 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
309 if (reference == relation) {
310 model.refreshNode(childTree.getPathForRow(i));
311 }
312 }
313 }
314
315 @Override
316 protected void finish() {
317 if (canceled)
318 return;
319 if (lastException != null) {
320 ExceptionDialogUtil.explainException(lastException);
321 return;
322 }
323
324 if (conflictsCount > 0) {
325 JOptionPane.showMessageDialog(
326 Main.parent,
327 trn("There was {0} conflict during import.",
328 "There were {0} conflicts during import.",
329 conflictsCount, conflictsCount),
330 trn("Conflict in data", "Conflicts in data", conflictsCount),
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 cannot be loaded</html>",
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.getId());
364 for (RelationMember member: parent.getMembers()) {
365 if (member.isRelation()) {
366 Relation child = member.getRelation();
367 if (!downloadedRelationIds.contains(child.getId())) {
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 DataSetMerger visitor = new DataSetMerger(getLayer().data, ds);
383 visitor.merge();
384 if (!visitor.getConflicts().isEmpty()) {
385 getLayer().getConflicts().add(visitor.getConflicts());
386 conflictsCount += visitor.getConflicts().size();
387 }
388 }
389 }
390
391 @Override
392 protected void realRun() throws SAXException, IOException, OsmTransferException {
393 try {
394 while(! relationsToDownload.isEmpty() && !canceled) {
395 Relation r = relationsToDownload.pop();
396 if (r.isNew()) {
397 continue;
398 }
399 rememberChildRelationsToDownload(r);
400 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
401 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
402 true);
403 DataSet dataSet = null;
404 try {
405 dataSet = reader.parseOsm(progressMonitor
406 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
407 } catch(OsmApiException e) {
408 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
409 warnBecauseOfDeletedRelation(r);
410 continue;
411 }
412 throw e;
413 }
414 mergeDataSet(dataSet);
415 refreshView(r);
416 }
417 SwingUtilities.invokeLater(new Runnable() {
418 public void run() {
419 Main.map.repaint();
420 }
421 });
422 } catch (Exception e) {
423 if (canceled) {
424 System.out.println(tr("Warning: Ignoring exception because task was canceled. Exception: {0}", e
425 .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 PleaseWaitRunnable {
437 private boolean canceled;
438 private int conflictsCount;
439 private Exception lastException;
440 private Set<Relation> relations;
441
442 public DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
443 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
444 * don't
445 * ignore
446 * exception
447 */);
448 this.relations = relations;
449 }
450
451 @Override
452 protected void cancel() {
453 canceled = true;
454 OsmApi.getOsmApi().cancel();
455 }
456
457 protected void refreshView(Relation relation){
458 for (int i=0; i < childTree.getRowCount(); i++) {
459 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
460 if (reference == relation) {
461 model.refreshNode(childTree.getPathForRow(i));
462 }
463 }
464 }
465
466 @Override
467 protected void finish() {
468 if (canceled)
469 return;
470 if (lastException != null) {
471 ExceptionDialogUtil.explainException(lastException);
472 return;
473 }
474
475 if (conflictsCount > 0) {
476 JOptionPane.showMessageDialog(
477 Main.parent,
478 trn("There was {0} conflict during import.",
479 "There were {0} conflicts during import.",
480 conflictsCount, conflictsCount),
481 trn("Conflict in data", "Conflicts in data", conflictsCount),
482 JOptionPane.WARNING_MESSAGE
483 );
484 }
485 }
486
487 protected void mergeDataSet(DataSet dataSet) {
488 if (dataSet != null) {
489 final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
490 visitor.merge();
491 if (!visitor.getConflicts().isEmpty()) {
492 getLayer().getConflicts().add(visitor.getConflicts());
493 conflictsCount += visitor.getConflicts().size();
494 }
495 }
496 }
497
498 @Override
499 protected void realRun() throws SAXException, IOException, OsmTransferException {
500 try {
501 Iterator<Relation> it = relations.iterator();
502 while(it.hasNext() && !canceled) {
503 Relation r = it.next();
504 if (r.isNew()) {
505 continue;
506 }
507 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
508 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
509 true);
510 DataSet dataSet = reader.parseOsm(progressMonitor
511 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
512 mergeDataSet(dataSet);
513 refreshView(r);
514 }
515 } catch (Exception e) {
516 if (canceled) {
517 System.out.println(tr("Warning: Ignoring exception because task was canceled. Exception: {0}", e
518 .toString()));
519 return;
520 }
521 lastException = e;
522 }
523 }
524 }
525}
Note: See TracBrowser for help on using the repository browser.