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

Last change on this file since 2578 was 2578, checked in by jttt, 14 years ago

Encalupse OsmPrimitive.incomplete

File size: 18.4 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;
18
19import javax.swing.AbstractAction;
20import javax.swing.JButton;
21import javax.swing.JOptionPane;
22import javax.swing.JPanel;
23import javax.swing.JScrollPane;
24import javax.swing.SwingUtilities;
25import javax.swing.event.TreeSelectionEvent;
26import javax.swing.event.TreeSelectionListener;
27import javax.swing.tree.TreePath;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.data.osm.DataSet;
31import org.openstreetmap.josm.data.osm.DataSetMerger;
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.gui.DefaultNameFormatter;
36import org.openstreetmap.josm.gui.ExceptionDialogUtil;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
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.OsmApiException;
43import org.openstreetmap.josm.io.OsmServerObjectReader;
44import org.openstreetmap.josm.io.OsmTransferException;
45import org.openstreetmap.josm.tools.ImageProvider;
46import org.xml.sax.SAXException;
47
48/**
49 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
50 * structure of relations
51 *
52 *
53 */
54public class ChildRelationBrowser extends JPanel {
55 //static private final Logger logger = Logger.getLogger(ChildRelationBrowser.class.getName());
56
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 {@see 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 {@see 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 if (layer == null)
120 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
121 this.layer = layer;
122 model = new RelationTreeModel();
123 build();
124 }
125
126 /**
127 * constructor
128 *
129 * @param layer the {@see OsmDataLayer} this browser is related to. Must not be null.
130 * @param root the root relation
131 * @exception IllegalArgumentException thrown, if layer is null
132 */
133 public ChildRelationBrowser(OsmDataLayer layer, Relation root) throws IllegalArgumentException {
134 this(layer);
135 populate(root);
136 }
137
138 /**
139 * populates the browser with a relation
140 *
141 * @param r the relation
142 */
143 public void populate(Relation r) {
144 model.populate(r);
145 }
146
147 /**
148 * populates the browser with a list of relation members
149 *
150 * @param members the list of relation members
151 */
152
153 public void populate(List<RelationMember> members) {
154 model.populate(members);
155 }
156
157 /**
158 * replies the parent dialog this browser is embedded in
159 *
160 * @return the parent dialog; null, if there is no {@see Dialog} as parent dialog
161 */
162 protected Dialog getParentDialog() {
163 Component c = this;
164 while(c != null && ! (c instanceof Dialog)) {
165 c = c.getParent();
166 }
167 return (Dialog)c;
168 }
169
170 /**
171 * Action for editing the currently selected relation
172 *
173 *
174 */
175 class EditAction extends AbstractAction implements TreeSelectionListener {
176 public EditAction() {
177 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to."));
178 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
179 putValue(NAME, tr("Edit"));
180 refreshEnabled();
181 }
182
183 protected void refreshEnabled() {
184 TreePath[] selection = childTree.getSelectionPaths();
185 setEnabled(selection != null && selection.length > 0);
186 }
187
188 public void run() {
189 TreePath [] selection = childTree.getSelectionPaths();
190 if (selection == null || selection.length == 0) return;
191 // do not launch more than 10 relation editors in parallel
192 //
193 for (int i=0; i < Math.min(selection.length,10);i++) {
194 Relation r = (Relation)selection[i].getLastPathComponent();
195 if (r.isIncomplete()) {
196 continue;
197 }
198 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
199 editor.setVisible(true);
200 }
201 }
202
203 public void actionPerformed(ActionEvent e) {
204 if (!isEnabled())
205 return;
206 run();
207 }
208
209 public void valueChanged(TreeSelectionEvent e) {
210 refreshEnabled();
211 }
212 }
213
214 /**
215 * Action for downloading all child relations for a given parent relation.
216 * Recursively.
217 */
218 class DownloadAllChildRelationsAction extends AbstractAction{
219 public DownloadAllChildRelationsAction() {
220 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
221 putValue(SMALL_ICON, ImageProvider.get("download"));
222 putValue(NAME, tr("Download All Children"));
223 }
224
225 public void run() {
226 Main.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation)model.getRoot()));
227 }
228
229 public void actionPerformed(ActionEvent e) {
230 if (!isEnabled())
231 return;
232 run();
233 }
234 }
235
236 /**
237 * Action for downloading all selected relations
238 */
239 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
240 public DownloadSelectedAction() {
241 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
242 // FIXME: replace with better icon
243 //
244 putValue(SMALL_ICON, ImageProvider.get("download"));
245 putValue(NAME, tr("Download Selected Children"));
246 updateEnabledState();
247 }
248
249 protected void updateEnabledState() {
250 TreePath [] selection = childTree.getSelectionPaths();
251 setEnabled(selection != null && selection.length > 0);
252 }
253
254 public void run() {
255 TreePath [] selection = childTree.getSelectionPaths();
256 if (selection == null || selection.length == 0)
257 return;
258 HashSet<Relation> relations = new HashSet<Relation>();
259 for (TreePath aSelection : selection) {
260 relations.add((Relation) aSelection.getLastPathComponent());
261 }
262 Main.worker.submit(new DownloadRelationSetTask(getParentDialog(),relations));
263 }
264
265 public void actionPerformed(ActionEvent e) {
266 if (!isEnabled())
267 return;
268 run();
269 }
270
271 public void valueChanged(TreeSelectionEvent e) {
272 updateEnabledState();
273 }
274 }
275
276 /**
277 * The asynchronous task for downloading relation members.
278 *
279 *
280 */
281 class DownloadAllChildrenTask extends PleaseWaitRunnable {
282 private boolean cancelled;
283 private int conflictsCount;
284 private Exception lastException;
285 private Relation relation;
286 private Stack<Relation> relationsToDownload;
287 private Set<Long> downloadedRelationIds;
288
289 public DownloadAllChildrenTask(Dialog parent, Relation r) {
290 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
291 * don't
292 * ignore
293 * exception
294 */);
295 this.relation = r;
296 relationsToDownload = new Stack<Relation>();
297 downloadedRelationIds = new HashSet<Long>();
298 relationsToDownload.push(this.relation);
299 }
300
301 @Override
302 protected void cancel() {
303 cancelled = true;
304 OsmApi.getOsmApi().cancel();
305 }
306
307 protected void refreshView(Relation relation){
308 for (int i=0; i < childTree.getRowCount(); i++) {
309 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
310 if (reference == relation) {
311 model.refreshNode(childTree.getPathForRow(i));
312 }
313 }
314 }
315
316 @Override
317 protected void finish() {
318 if (cancelled)
319 return;
320 if (lastException != null) {
321 ExceptionDialogUtil.explainException(lastException);
322 return;
323 }
324
325 if (conflictsCount > 0) {
326 JOptionPane.showMessageDialog(
327 Main.parent,
328 tr("There were {0} conflicts during import.", conflictsCount),
329 tr("Conflicts in data"),
330 JOptionPane.WARNING_MESSAGE
331 );
332 }
333 }
334
335 /**
336 * warns the user if a relation couldn't be loaded because it was deleted on
337 * the server (the server replied a HTTP code 410)
338 *
339 * @param r the relation
340 */
341 protected void warnBecauseOfDeletedRelation(Relation r) {
342 String message = tr("<html>The child relation<br>"
343 + "{0}<br>"
344 + "is deleted on the server. It can't be loaded",
345 r.getDisplayName(DefaultNameFormatter.getInstance())
346 );
347
348 JOptionPane.showMessageDialog(
349 Main.parent,
350 message,
351 tr("Relation is deleted"),
352 JOptionPane.WARNING_MESSAGE
353 );
354 }
355
356 /**
357 * Remembers the child relations to download
358 *
359 * @param parent the parent relation
360 */
361 protected void rememberChildRelationsToDownload(Relation parent) {
362 downloadedRelationIds.add(parent.getId());
363 for (RelationMember member: parent.getMembers()) {
364 if (member.isRelation()) {
365 Relation child = member.getRelation();
366 if (!downloadedRelationIds.contains(child.getId())) {
367 relationsToDownload.push(child);
368 }
369 }
370 }
371 }
372
373 /**
374 * Merges the primitives in <code>ds</code> to the dataset of the
375 * edit layer
376 *
377 * @param ds the data set
378 */
379 protected void mergeDataSet(DataSet ds) {
380 if (ds != null) {
381 final DataSetMerger visitor = new DataSetMerger(getLayer().data, ds);
382 visitor.merge();
383 // FIXME: this is necessary because there are dialogs listening
384 // for DataChangeEvents which manipulate Swing components on this
385 // thread.
386 //
387 SwingUtilities.invokeLater(new Runnable() {
388 public void run() {
389 getLayer().fireDataChange();
390 }
391 });
392 if (!visitor.getConflicts().isEmpty()) {
393 getLayer().getConflicts().add(visitor.getConflicts());
394 conflictsCount += visitor.getConflicts().size();
395 }
396 }
397 }
398
399 @Override
400 protected void realRun() throws SAXException, IOException, OsmTransferException {
401 try {
402 while(! relationsToDownload.isEmpty() && !cancelled) {
403 Relation r = relationsToDownload.pop();
404 if (r.isNew()) {
405 continue;
406 }
407 rememberChildRelationsToDownload(r);
408 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
409 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
410 true);
411 DataSet dataSet = null;
412 try {
413 dataSet = reader.parseOsm(progressMonitor
414 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
415 } catch(OsmApiException e) {
416 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
417 warnBecauseOfDeletedRelation(r);
418 continue;
419 }
420 throw e;
421 }
422 mergeDataSet(dataSet);
423 refreshView(r);
424 }
425 } catch (Exception e) {
426 if (cancelled) {
427 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
428 .toString()));
429 return;
430 }
431 lastException = e;
432 }
433 }
434 }
435
436 /**
437 * The asynchronous task for downloading a set of relations
438 */
439 class DownloadRelationSetTask extends PleaseWaitRunnable {
440 private boolean cancelled;
441 private int conflictsCount;
442 private Exception lastException;
443 private Set<Relation> relations;
444
445 public DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
446 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
447 * don't
448 * ignore
449 * exception
450 */);
451 this.relations = relations;
452 }
453
454 @Override
455 protected void cancel() {
456 cancelled = true;
457 OsmApi.getOsmApi().cancel();
458 }
459
460 protected void refreshView(Relation relation){
461 for (int i=0; i < childTree.getRowCount(); i++) {
462 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
463 if (reference == relation) {
464 model.refreshNode(childTree.getPathForRow(i));
465 }
466 }
467 }
468
469 @Override
470 protected void finish() {
471 if (cancelled)
472 return;
473 if (lastException != null) {
474 ExceptionDialogUtil.explainException(lastException);
475 return;
476 }
477
478 if (conflictsCount > 0) {
479 JOptionPane.showMessageDialog(
480 Main.parent,
481 tr("There were {0} conflicts during import.", conflictsCount),
482 tr("Conflicts in data"),
483 JOptionPane.WARNING_MESSAGE
484 );
485 }
486 }
487
488 protected void mergeDataSet(DataSet dataSet) {
489 if (dataSet != null) {
490 final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
491 visitor.merge();
492 // FIXME: this is necessary because there are dialogs listening
493 // for DataChangeEvents which manipulate Swing components on this
494 // thread.
495 //
496 SwingUtilities.invokeLater(new Runnable() {
497 public void run() {
498 getLayer().fireDataChange();
499 }
500 });
501 if (!visitor.getConflicts().isEmpty()) {
502 getLayer().getConflicts().add(visitor.getConflicts());
503 conflictsCount += visitor.getConflicts().size();
504 }
505 }
506 }
507
508 @Override
509 protected void realRun() throws SAXException, IOException, OsmTransferException {
510 try {
511 Iterator<Relation> it = relations.iterator();
512 while(it.hasNext() && !cancelled) {
513 Relation r = it.next();
514 if (r.isNew()) {
515 continue;
516 }
517 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
518 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
519 true);
520 DataSet dataSet = reader.parseOsm(progressMonitor
521 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
522 mergeDataSet(dataSet);
523 refreshView(r);
524 }
525 } catch (Exception e) {
526 if (cancelled) {
527 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
528 .toString()));
529 return;
530 }
531 lastException = e;
532 }
533 }
534 }
535}
Note: See TracBrowser for help on using the repository browser.