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

Last change on this file since 16913 was 16913, checked in by simon04, 4 years ago

fix #19698 - Refactoring: make private fields final

  • Property svn:eol-style set to native
File size: 16.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.Point;
12import java.awt.event.ActionEvent;
13import java.awt.event.MouseEvent;
14import java.io.IOException;
15import java.util.Arrays;
16import java.util.List;
17import java.util.Set;
18import java.util.stream.Collectors;
19
20import javax.swing.AbstractAction;
21import javax.swing.JButton;
22import javax.swing.JOptionPane;
23import javax.swing.JPanel;
24import javax.swing.JPopupMenu;
25import javax.swing.JScrollPane;
26import javax.swing.JTree;
27import javax.swing.SwingUtilities;
28import javax.swing.event.TreeSelectionEvent;
29import javax.swing.event.TreeSelectionListener;
30import javax.swing.tree.TreePath;
31
32import org.openstreetmap.josm.data.osm.DataSet;
33import org.openstreetmap.josm.data.osm.DataSetMerger;
34import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.data.osm.Relation;
37import org.openstreetmap.josm.data.osm.RelationMember;
38import org.openstreetmap.josm.gui.ExceptionDialogUtil;
39import org.openstreetmap.josm.gui.MainApplication;
40import org.openstreetmap.josm.gui.PleaseWaitRunnable;
41import org.openstreetmap.josm.gui.PopupMenuHandler;
42import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.gui.progress.ProgressMonitor;
45import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
46import org.openstreetmap.josm.gui.util.GuiHelper;
47import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
48import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
49import org.openstreetmap.josm.io.OsmApi;
50import org.openstreetmap.josm.io.OsmTransferException;
51import org.openstreetmap.josm.tools.CheckParameterUtil;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.Logging;
54import org.openstreetmap.josm.tools.Utils;
55import org.xml.sax.SAXException;
56
57/**
58 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
59 * structure of relations.
60 *
61 * @since 1828
62 */
63public class ChildRelationBrowser extends JPanel {
64 /** the tree with relation children */
65 private RelationTree childTree;
66 /** the tree model */
67 private final transient RelationTreeModel model;
68
69 /** the osm data layer this browser is related to */
70 private final transient OsmDataLayer layer;
71
72 /** the editAction used in the bottom panel and for doubleClick */
73 private EditAction editAction;
74
75 /**
76 * Replies the {@link OsmDataLayer} this editor is related to
77 *
78 * @return the osm data layer
79 */
80 protected OsmDataLayer getLayer() {
81 return layer;
82 }
83
84 /**
85 * builds the UI
86 */
87 protected void build() {
88 setLayout(new BorderLayout());
89 childTree = new RelationTree(model);
90 JScrollPane pane = new JScrollPane(childTree);
91 add(pane, BorderLayout.CENTER);
92
93 final JPopupMenu popupMenu = new JPopupMenu();
94 final PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu);
95 RelationPopupMenus.setupHandler(popupMenuHandler, DuplicateRelationAction.class);
96
97 add(buildButtonPanel(), BorderLayout.SOUTH);
98 childTree.setToggleClickCount(0);
99 childTree.addMouseListener(new PopupMenuLauncher(popupMenu) {
100 @Override
101 public void mouseClicked(MouseEvent e) {
102 if (e.getClickCount() == 2
103 && !e.isAltDown() && !e.isAltGraphDown() && !e.isControlDown() && !e.isMetaDown() && !e.isShiftDown()
104 && childTree.getRowForLocation(e.getX(), e.getY()) == childTree.getMinSelectionRow()) {
105 Relation r = (Relation) childTree.getLastSelectedPathComponent();
106 if (r != null && r.isIncomplete()) {
107 childTree.expandPath(childTree.getSelectionPath());
108 } else {
109 editAction.actionPerformed(new ActionEvent(e.getSource(), ActionEvent.ACTION_PERFORMED, null));
110 }
111 }
112 }
113
114 @Override
115 protected TreePath checkTreeSelection(JTree tree, Point p) {
116 final TreePath treeSelection = super.checkTreeSelection(tree, p);
117 final TreePath[] selectionPaths = tree.getSelectionPaths();
118 if (selectionPaths == null) {
119 return treeSelection;
120 }
121 final List<OsmPrimitive> relations = Arrays.stream(selectionPaths)
122 .map(TreePath::getLastPathComponent)
123 .map(OsmPrimitive.class::cast)
124 .collect(Collectors.toList());
125 popupMenuHandler.setPrimitives(relations);
126 return treeSelection;
127 }
128 });
129 }
130
131 /**
132 * builds the panel with the command buttons
133 *
134 * @return the button panel
135 */
136 protected JPanel buildButtonPanel() {
137 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
138
139 // ---
140 DownloadAllChildRelationsAction downloadAction = new DownloadAllChildRelationsAction();
141 pnl.add(new JButton(downloadAction));
142
143 // ---
144 DownloadSelectedAction downloadSelectedAction = new DownloadSelectedAction();
145 childTree.addTreeSelectionListener(downloadSelectedAction);
146 pnl.add(new JButton(downloadSelectedAction));
147
148 // ---
149 editAction = new EditAction();
150 childTree.addTreeSelectionListener(editAction);
151 pnl.add(new JButton(editAction));
152
153 return pnl;
154 }
155
156 /**
157 * constructor
158 *
159 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
160 * @throws IllegalArgumentException if layer is null
161 */
162 public ChildRelationBrowser(OsmDataLayer layer) {
163 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
164 this.layer = layer;
165 model = new RelationTreeModel();
166 build();
167 }
168
169 /**
170 * constructor
171 *
172 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
173 * @param root the root relation
174 * @throws IllegalArgumentException if layer is null
175 */
176 public ChildRelationBrowser(OsmDataLayer layer, Relation root) {
177 this(layer);
178 populate(root);
179 }
180
181 /**
182 * populates the browser with a relation
183 *
184 * @param r the relation
185 */
186 public void populate(Relation r) {
187 model.populate(r);
188 }
189
190 /**
191 * populates the browser with a list of relation members
192 *
193 * @param members the list of relation members
194 */
195
196 public void populate(List<RelationMember> members) {
197 model.populate(members);
198 }
199
200 /**
201 * replies the parent dialog this browser is embedded in
202 *
203 * @return the parent dialog; null, if there is no {@link Dialog} as parent dialog
204 */
205 protected Dialog getParentDialog() {
206 Component c = this;
207 while (c != null && !(c instanceof Dialog)) {
208 c = c.getParent();
209 }
210 return (Dialog) c;
211 }
212
213 /**
214 * Action for editing the currently selected relation
215 *
216 *
217 */
218 class EditAction extends AbstractAction implements TreeSelectionListener {
219 EditAction() {
220 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
221 new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this, true);
222 putValue(NAME, tr("Edit"));
223 refreshEnabled();
224 }
225
226 protected void refreshEnabled() {
227 TreePath[] selection = childTree.getSelectionPaths();
228 setEnabled(selection != null && selection.length > 0);
229 }
230
231 public void run() {
232 TreePath[] selection = childTree.getSelectionPaths();
233 if (selection == null || selection.length == 0) return;
234 // do not launch more than 10 relation editors in parallel
235 //
236 for (int i = 0; i < Math.min(selection.length, 10); i++) {
237 Relation r = (Relation) selection[i].getLastPathComponent();
238 if (r.isIncomplete()) {
239 continue;
240 }
241 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
242 editor.setVisible(true);
243 }
244 }
245
246 @Override
247 public void actionPerformed(ActionEvent e) {
248 if (!isEnabled())
249 return;
250 run();
251 }
252
253 @Override
254 public void valueChanged(TreeSelectionEvent e) {
255 refreshEnabled();
256 }
257 }
258
259 /**
260 * Action for downloading all child relations for a given parent relation.
261 * Recursively.
262 */
263 class DownloadAllChildRelationsAction extends AbstractAction {
264 DownloadAllChildRelationsAction() {
265 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
266 new ImageProvider("download").getResource().attachImageIcon(this, true);
267 putValue(NAME, tr("Download All Children"));
268 }
269
270 public void run() {
271 MainApplication.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation) model.getRoot()));
272 }
273
274 @Override
275 public void actionPerformed(ActionEvent e) {
276 if (!isEnabled())
277 return;
278 run();
279 }
280 }
281
282 /**
283 * Action for downloading all selected relations
284 */
285 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
286 DownloadSelectedAction() {
287 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
288 // FIXME: replace with better icon
289 new ImageProvider("download").getResource().attachImageIcon(this, true);
290 putValue(NAME, tr("Download Selected Children"));
291 updateEnabledState();
292 }
293
294 protected void updateEnabledState() {
295 TreePath[] selection = childTree.getSelectionPaths();
296 setEnabled(selection != null && selection.length > 0);
297 }
298
299 public void run() {
300 TreePath[] selection = childTree.getSelectionPaths();
301 if (selection == null || selection.length == 0)
302 return;
303 Set<Relation> relations = Arrays.stream(selection)
304 .map(s -> (Relation) s.getLastPathComponent())
305 .collect(Collectors.toSet());
306 MainApplication.worker.submit(new DownloadRelationSetTask(getParentDialog(), relations));
307 }
308
309 @Override
310 public void actionPerformed(ActionEvent e) {
311 if (!isEnabled())
312 return;
313 run();
314 }
315
316 @Override
317 public void valueChanged(TreeSelectionEvent e) {
318 updateEnabledState();
319 }
320 }
321
322 abstract class DownloadTask extends PleaseWaitRunnable {
323 protected boolean canceled;
324 protected int conflictsCount;
325 protected Exception lastException;
326
327 DownloadTask(String title, Dialog parent) {
328 super(title, new PleaseWaitProgressMonitor(parent), false);
329 }
330
331 @Override
332 protected void cancel() {
333 canceled = true;
334 OsmApi.getOsmApi().cancel();
335 }
336
337 protected MultiFetchServerObjectReader createReader() {
338 return MultiFetchServerObjectReader.create().setRecurseDownAppended(false).setRecurseDownRelations(true);
339 }
340
341 /**
342 * Merges the primitives in <code>ds</code> to the dataset of the edit layer
343 *
344 * @param ds the data set
345 */
346 protected void mergeDataSet(DataSet ds) {
347 if (ds != null) {
348 final DataSetMerger visitor = new DataSetMerger(getLayer().getDataSet(), ds);
349 visitor.merge();
350 if (!visitor.getConflicts().isEmpty()) {
351 getLayer().getConflicts().add(visitor.getConflicts());
352 conflictsCount += visitor.getConflicts().size();
353 }
354 }
355 }
356
357 protected void refreshView(Relation relation) {
358 GuiHelper.runInEDT(() -> {
359 for (int i = 0; i < childTree.getRowCount(); i++) {
360 Relation reference = (Relation) childTree.getPathForRow(i).getLastPathComponent();
361 if (reference == relation) {
362 model.refreshNode(childTree.getPathForRow(i));
363 }
364 }
365 });
366 }
367
368 @Override
369 protected void finish() {
370 if (canceled)
371 return;
372 if (lastException != null) {
373 ExceptionDialogUtil.explainException(lastException);
374 return;
375 }
376
377 if (conflictsCount > 0) {
378 JOptionPane.showMessageDialog(
379 MainApplication.getMainFrame(),
380 trn("There was {0} conflict during import.",
381 "There were {0} conflicts during import.",
382 conflictsCount, conflictsCount),
383 trn("Conflict in data", "Conflicts in data", conflictsCount),
384 JOptionPane.WARNING_MESSAGE
385 );
386 }
387 }
388 }
389
390 /**
391 * The asynchronous task for downloading relation members.
392 */
393 class DownloadAllChildrenTask extends DownloadTask {
394 private final Relation relation;
395
396 DownloadAllChildrenTask(Dialog parent, Relation r) {
397 super(tr("Download relation members"), parent);
398 relation = r;
399 }
400
401 /**
402 * warns the user if a relation couldn't be loaded because it was deleted on
403 * the server (the server replied a HTTP code 410)
404 *
405 * @param r the relation
406 */
407 protected void warnBecauseOfDeletedRelation(Relation r) {
408 String message = tr("<html>The child relation<br>"
409 + "{0}<br>"
410 + "is deleted on the server. It cannot be loaded</html>",
411 Utils.escapeReservedCharactersHTML(r.getDisplayName(DefaultNameFormatter.getInstance()))
412 );
413
414 JOptionPane.showMessageDialog(
415 MainApplication.getMainFrame(),
416 message,
417 tr("Relation is deleted"),
418 JOptionPane.WARNING_MESSAGE
419 );
420 }
421
422 @Override
423 protected void realRun() throws SAXException, IOException, OsmTransferException {
424 try {
425 MultiFetchServerObjectReader reader = createReader();
426 reader.append(relation.getMemberPrimitives());
427 DataSet dataSet = reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
428 mergeDataSet(dataSet);
429 Utils.filteredCollection(reader.getMissingPrimitives(), Relation.class).forEach(this::warnBecauseOfDeletedRelation);
430 for (Relation rel : dataSet.getRelations()) {
431 refreshView((Relation) getLayer().getDataSet().getPrimitiveById(rel));
432 }
433 SwingUtilities.invokeLater(MainApplication.getMap()::repaint);
434 } catch (OsmTransferException e) {
435 if (canceled) {
436 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
437 return;
438 }
439 lastException = e;
440 }
441 }
442 }
443
444 /**
445 * The asynchronous task for downloading a set of relations
446 */
447 class DownloadRelationSetTask extends DownloadTask {
448 private final Set<Relation> relations;
449
450 DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
451 super(tr("Download relation members"), parent);
452 this.relations = relations;
453 }
454
455 @Override
456 protected void realRun() throws SAXException, IOException, OsmTransferException {
457 try {
458 MultiFetchServerObjectReader reader = createReader();
459 reader.append(relations);
460 DataSet dataSet = reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
461 mergeDataSet(dataSet);
462
463 for (Relation rel : dataSet.getRelations()) {
464 refreshView((Relation) getLayer().getDataSet().getPrimitiveById(rel));
465 }
466
467 } catch (OsmTransferException e) {
468 if (canceled) {
469 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
470 return;
471 }
472 lastException = e;
473 }
474 }
475 }
476}
Note: See TracBrowser for help on using the repository browser.