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

Last change on this file since 4310 was 4310, checked in by stoecker, 13 years ago

fix #6680, fix #6677 - i18n issues

  • Property svn:eol-style set to native
File size: 17.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;
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.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.CheckParameterUtil;
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 /** 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 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
119 this.layer = layer;
120 model = new RelationTreeModel();
121 build();
122 }
123
124 /**
125 * constructor
126 *
127 * @param layer the {@see OsmDataLayer} this browser is related to. Must not be null.
128 * @param root the root relation
129 * @exception IllegalArgumentException thrown, if layer is null
130 */
131 public ChildRelationBrowser(OsmDataLayer layer, Relation root) throws IllegalArgumentException {
132 this(layer);
133 populate(root);
134 }
135
136 /**
137 * populates the browser with a relation
138 *
139 * @param r the relation
140 */
141 public void populate(Relation r) {
142 model.populate(r);
143 }
144
145 /**
146 * populates the browser with a list of relation members
147 *
148 * @param members the list of relation members
149 */
150
151 public void populate(List<RelationMember> members) {
152 model.populate(members);
153 }
154
155 /**
156 * replies the parent dialog this browser is embedded in
157 *
158 * @return the parent dialog; null, if there is no {@see Dialog} as parent dialog
159 */
160 protected Dialog getParentDialog() {
161 Component c = this;
162 while(c != null && ! (c instanceof Dialog)) {
163 c = c.getParent();
164 }
165 return (Dialog)c;
166 }
167
168 /**
169 * Action for editing the currently selected relation
170 *
171 *
172 */
173 class EditAction extends AbstractAction implements TreeSelectionListener {
174 public EditAction() {
175 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to."));
176 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
177 putValue(NAME, tr("Edit"));
178 refreshEnabled();
179 }
180
181 protected void refreshEnabled() {
182 TreePath[] selection = childTree.getSelectionPaths();
183 setEnabled(selection != null && selection.length > 0);
184 }
185
186 public void run() {
187 TreePath [] selection = childTree.getSelectionPaths();
188 if (selection == null || selection.length == 0) return;
189 // do not launch more than 10 relation editors in parallel
190 //
191 for (int i=0; i < Math.min(selection.length,10);i++) {
192 Relation r = (Relation)selection[i].getLastPathComponent();
193 if (r.isIncomplete()) {
194 continue;
195 }
196 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
197 editor.setVisible(true);
198 }
199 }
200
201 public void actionPerformed(ActionEvent e) {
202 if (!isEnabled())
203 return;
204 run();
205 }
206
207 public void valueChanged(TreeSelectionEvent e) {
208 refreshEnabled();
209 }
210 }
211
212 /**
213 * Action for downloading all child relations for a given parent relation.
214 * Recursively.
215 */
216 class DownloadAllChildRelationsAction extends AbstractAction{
217 public DownloadAllChildRelationsAction() {
218 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
219 putValue(SMALL_ICON, ImageProvider.get("download"));
220 putValue(NAME, tr("Download All Children"));
221 }
222
223 public void run() {
224 Main.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation)model.getRoot()));
225 }
226
227 public void actionPerformed(ActionEvent e) {
228 if (!isEnabled())
229 return;
230 run();
231 }
232 }
233
234 /**
235 * Action for downloading all selected relations
236 */
237 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
238 public DownloadSelectedAction() {
239 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
240 // FIXME: replace with better icon
241 //
242 putValue(SMALL_ICON, ImageProvider.get("download"));
243 putValue(NAME, tr("Download Selected Children"));
244 updateEnabledState();
245 }
246
247 protected void updateEnabledState() {
248 TreePath [] selection = childTree.getSelectionPaths();
249 setEnabled(selection != null && selection.length > 0);
250 }
251
252 public void run() {
253 TreePath [] selection = childTree.getSelectionPaths();
254 if (selection == null || selection.length == 0)
255 return;
256 HashSet<Relation> relations = new HashSet<Relation>();
257 for (TreePath aSelection : selection) {
258 relations.add((Relation) aSelection.getLastPathComponent());
259 }
260 Main.worker.submit(new DownloadRelationSetTask(getParentDialog(),relations));
261 }
262
263 public void actionPerformed(ActionEvent e) {
264 if (!isEnabled())
265 return;
266 run();
267 }
268
269 public void valueChanged(TreeSelectionEvent e) {
270 updateEnabledState();
271 }
272 }
273
274 /**
275 * The asynchronous task for downloading relation members.
276 *
277 *
278 */
279 class DownloadAllChildrenTask extends PleaseWaitRunnable {
280 private boolean canceled;
281 private int conflictsCount;
282 private Exception lastException;
283 private Relation relation;
284 private Stack<Relation> relationsToDownload;
285 private Set<Long> downloadedRelationIds;
286
287 public DownloadAllChildrenTask(Dialog parent, Relation r) {
288 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
289 * don't
290 * ignore
291 * exception
292 */);
293 this.relation = r;
294 relationsToDownload = new Stack<Relation>();
295 downloadedRelationIds = new HashSet<Long>();
296 relationsToDownload.push(this.relation);
297 }
298
299 @Override
300 protected void cancel() {
301 canceled = true;
302 OsmApi.getOsmApi().cancel();
303 }
304
305 protected void refreshView(Relation relation){
306 for (int i=0; i < childTree.getRowCount(); i++) {
307 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
308 if (reference == relation) {
309 model.refreshNode(childTree.getPathForRow(i));
310 }
311 }
312 }
313
314 @Override
315 protected void finish() {
316 if (canceled)
317 return;
318 if (lastException != null) {
319 ExceptionDialogUtil.explainException(lastException);
320 return;
321 }
322
323 if (conflictsCount > 0) {
324 JOptionPane.showMessageDialog(
325 Main.parent,
326 trn("There was {0} conflict during import.",
327 "There were {0} conflicts during import.",
328 conflictsCount, conflictsCount),
329 trn("Conflict in data", "Conflicts in data", conflictsCount),
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 cannot be loaded</html>",
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 if (!visitor.getConflicts().isEmpty()) {
384 getLayer().getConflicts().add(visitor.getConflicts());
385 conflictsCount += visitor.getConflicts().size();
386 }
387 }
388 }
389
390 @Override
391 protected void realRun() throws SAXException, IOException, OsmTransferException {
392 try {
393 while(! relationsToDownload.isEmpty() && !canceled) {
394 Relation r = relationsToDownload.pop();
395 if (r.isNew()) {
396 continue;
397 }
398 rememberChildRelationsToDownload(r);
399 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
400 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
401 true);
402 DataSet dataSet = null;
403 try {
404 dataSet = reader.parseOsm(progressMonitor
405 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
406 } catch(OsmApiException e) {
407 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
408 warnBecauseOfDeletedRelation(r);
409 continue;
410 }
411 throw e;
412 }
413 mergeDataSet(dataSet);
414 refreshView(r);
415 }
416 } catch (Exception e) {
417 if (canceled) {
418 System.out.println(tr("Warning: Ignoring exception because task was canceled. Exception: {0}", e
419 .toString()));
420 return;
421 }
422 lastException = e;
423 }
424 }
425 }
426
427 /**
428 * The asynchronous task for downloading a set of relations
429 */
430 class DownloadRelationSetTask extends PleaseWaitRunnable {
431 private boolean canceled;
432 private int conflictsCount;
433 private Exception lastException;
434 private Set<Relation> relations;
435
436 public DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
437 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
438 * don't
439 * ignore
440 * exception
441 */);
442 this.relations = relations;
443 }
444
445 @Override
446 protected void cancel() {
447 canceled = true;
448 OsmApi.getOsmApi().cancel();
449 }
450
451 protected void refreshView(Relation relation){
452 for (int i=0; i < childTree.getRowCount(); i++) {
453 Relation reference = (Relation)childTree.getPathForRow(i).getLastPathComponent();
454 if (reference == relation) {
455 model.refreshNode(childTree.getPathForRow(i));
456 }
457 }
458 }
459
460 @Override
461 protected void finish() {
462 if (canceled)
463 return;
464 if (lastException != null) {
465 ExceptionDialogUtil.explainException(lastException);
466 return;
467 }
468
469 if (conflictsCount > 0) {
470 JOptionPane.showMessageDialog(
471 Main.parent,
472 trn("There was {0} conflict during import.",
473 "There were {0} conflicts during import.",
474 conflictsCount, conflictsCount),
475 trn("Conflict in data", "Conflicts in data", conflictsCount),
476 JOptionPane.WARNING_MESSAGE
477 );
478 }
479 }
480
481 protected void mergeDataSet(DataSet dataSet) {
482 if (dataSet != null) {
483 final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
484 visitor.merge();
485 if (!visitor.getConflicts().isEmpty()) {
486 getLayer().getConflicts().add(visitor.getConflicts());
487 conflictsCount += visitor.getConflicts().size();
488 }
489 }
490 }
491
492 @Override
493 protected void realRun() throws SAXException, IOException, OsmTransferException {
494 try {
495 Iterator<Relation> it = relations.iterator();
496 while(it.hasNext() && !canceled) {
497 Relation r = it.next();
498 if (r.isNew()) {
499 continue;
500 }
501 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
502 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
503 true);
504 DataSet dataSet = reader.parseOsm(progressMonitor
505 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
506 mergeDataSet(dataSet);
507 refreshView(r);
508 }
509 } catch (Exception e) {
510 if (canceled) {
511 System.out.println(tr("Warning: Ignoring exception because task was canceled. Exception: {0}", e
512 .toString()));
513 return;
514 }
515 lastException = e;
516 }
517 }
518 }
519}
Note: See TracBrowser for help on using the repository browser.