source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java@ 8510

Last change on this file since 8510 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Color;
10import java.awt.Graphics;
11import java.awt.Point;
12import java.awt.event.ActionEvent;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.HashSet;
18import java.util.LinkedList;
19import java.util.Set;
20import java.util.concurrent.CopyOnWriteArrayList;
21
22import javax.swing.AbstractAction;
23import javax.swing.JList;
24import javax.swing.JOptionPane;
25import javax.swing.JPopupMenu;
26import javax.swing.ListModel;
27import javax.swing.ListSelectionModel;
28import javax.swing.event.ListDataEvent;
29import javax.swing.event.ListDataListener;
30import javax.swing.event.ListSelectionEvent;
31import javax.swing.event.ListSelectionListener;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.actions.AbstractSelectAction;
35import org.openstreetmap.josm.data.SelectionChangedListener;
36import org.openstreetmap.josm.data.conflict.Conflict;
37import org.openstreetmap.josm.data.conflict.ConflictCollection;
38import org.openstreetmap.josm.data.conflict.IConflictListener;
39import org.openstreetmap.josm.data.osm.DataSet;
40import org.openstreetmap.josm.data.osm.Node;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.Relation;
43import org.openstreetmap.josm.data.osm.RelationMember;
44import org.openstreetmap.josm.data.osm.Way;
45import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
46import org.openstreetmap.josm.data.osm.visitor.Visitor;
47import org.openstreetmap.josm.gui.HelpAwareOptionPane;
48import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
49import org.openstreetmap.josm.gui.MapView;
50import org.openstreetmap.josm.gui.NavigatableComponent;
51import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
52import org.openstreetmap.josm.gui.PopupMenuHandler;
53import org.openstreetmap.josm.gui.SideButton;
54import org.openstreetmap.josm.gui.layer.OsmDataLayer;
55import org.openstreetmap.josm.gui.util.GuiHelper;
56import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
57import org.openstreetmap.josm.tools.ImageProvider;
58import org.openstreetmap.josm.tools.Shortcut;
59
60/**
61 * This dialog displays the {@link ConflictCollection} of the active {@link OsmDataLayer} in a toggle
62 * dialog on the right of the main frame.
63 *
64 */
65public final class ConflictDialog extends ToggleDialog implements MapView.EditLayerChangeListener, IConflictListener, SelectionChangedListener{
66
67 /**
68 * Replies the color used to paint conflicts.
69 *
70 * @return the color used to paint conflicts
71 * @see #paintConflicts
72 * @since 1221
73 */
74 public static Color getColor() {
75 return Main.pref.getColor(marktr("conflict"), Color.gray);
76 }
77
78 /** the collection of conflicts displayed by this conflict dialog */
79 private transient ConflictCollection conflicts;
80
81 /** the model for the list of conflicts */
82 private transient ConflictListModel model;
83 /** the list widget for the list of conflicts */
84 private JList<OsmPrimitive> lstConflicts;
85
86 private final JPopupMenu popupMenu = new JPopupMenu();
87 private final transient PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu);
88
89 private ResolveAction actResolve;
90 private SelectAction actSelect;
91
92 /**
93 * builds the GUI
94 */
95 protected void build() {
96 model = new ConflictListModel();
97
98 lstConflicts = new JList<>(model);
99 lstConflicts.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
100 lstConflicts.setCellRenderer(new OsmPrimitivRenderer());
101 lstConflicts.addMouseListener(new MouseEventHandler());
102 addListSelectionListener(new ListSelectionListener() {
103 @Override
104 public void valueChanged(ListSelectionEvent e) {
105 Main.map.mapView.repaint();
106 }
107 });
108
109 SideButton btnResolve = new SideButton(actResolve = new ResolveAction());
110 addListSelectionListener(actResolve);
111
112 SideButton btnSelect = new SideButton(actSelect = new SelectAction());
113 addListSelectionListener(actSelect);
114
115 createLayout(lstConflicts, true, Arrays.asList(new SideButton[] {
116 btnResolve, btnSelect
117 }));
118
119 popupMenuHandler.addAction(Main.main.menu.autoScaleActions.get("conflict"));
120 }
121
122 /**
123 * constructor
124 */
125 public ConflictDialog() {
126 super(tr("Conflict"), "conflict", tr("Resolve conflicts."),
127 Shortcut.registerShortcut("subwindow:conflict", tr("Toggle: {0}", tr("Conflict")),
128 KeyEvent.VK_C, Shortcut.ALT_SHIFT), 100);
129
130 build();
131 refreshView();
132 }
133
134 @Override
135 public void showNotify() {
136 DataSet.addSelectionListener(this);
137 MapView.addEditLayerChangeListener(this, true);
138 refreshView();
139 }
140
141 @Override
142 public void hideNotify() {
143 MapView.removeEditLayerChangeListener(this);
144 DataSet.removeSelectionListener(this);
145 }
146
147 /**
148 * Add a list selection listener to the conflicts list.
149 * @param listener the ListSelectionListener
150 * @since 5958
151 */
152 public void addListSelectionListener(ListSelectionListener listener) {
153 lstConflicts.getSelectionModel().addListSelectionListener(listener);
154 }
155
156 /**
157 * Remove the given list selection listener from the conflicts list.
158 * @param listener the ListSelectionListener
159 * @since 5958
160 */
161 public void removeListSelectionListener(ListSelectionListener listener) {
162 lstConflicts.getSelectionModel().removeListSelectionListener(listener);
163 }
164
165 /**
166 * Replies the popup menu handler.
167 * @return The popup menu handler
168 * @since 5958
169 */
170 public PopupMenuHandler getPopupMenuHandler() {
171 return popupMenuHandler;
172 }
173
174 /**
175 * Launches a conflict resolution dialog for the first selected conflict
176 *
177 */
178 private final void resolve() {
179 if (conflicts == null || model.getSize() == 0) return;
180
181 int index = lstConflicts.getSelectedIndex();
182 if (index < 0) {
183 index = 0;
184 }
185
186 Conflict<? extends OsmPrimitive> c = conflicts.get(index);
187 ConflictResolutionDialog dialog = new ConflictResolutionDialog(Main.parent);
188 dialog.getConflictResolver().populate(c);
189 dialog.setVisible(true);
190
191 lstConflicts.setSelectedIndex(index);
192
193 Main.map.mapView.repaint();
194 }
195
196 /**
197 * refreshes the view of this dialog
198 */
199 public final void refreshView() {
200 OsmDataLayer editLayer = Main.main.getEditLayer();
201 conflicts = (editLayer == null ? new ConflictCollection() : editLayer.getConflicts());
202 GuiHelper.runInEDT(new Runnable() {
203 @Override
204 public void run() {
205 model.fireContentChanged();
206 updateTitle();
207 }
208 });
209 }
210
211 private void updateTitle() {
212 int conflictsCount = conflicts.size();
213 if (conflictsCount > 0) {
214 setTitle(trn("Conflict: {0} unresolved", "Conflicts: {0} unresolved", conflictsCount, conflictsCount) +
215 " ("+tr("Rel.:{0} / Ways:{1} / Nodes:{2}",
216 conflicts.getRelationConflicts().size(),
217 conflicts.getWayConflicts().size(),
218 conflicts.getNodeConflicts().size())+")");
219 } else {
220 setTitle(tr("Conflict"));
221 }
222 }
223
224 /**
225 * Paints all conflicts that can be expressed on the main window.
226 *
227 * @param g The {@code Graphics} used to paint
228 * @param nc The {@code NavigatableComponent} used to get screen coordinates of nodes
229 * @since 86
230 */
231 public void paintConflicts(final Graphics g, final NavigatableComponent nc) {
232 Color preferencesColor = getColor();
233 if (preferencesColor.equals(Main.pref.getColor(marktr("background"), Color.black)))
234 return;
235 g.setColor(preferencesColor);
236 Visitor conflictPainter = new AbstractVisitor() {
237 // Manage a stack of visited relations to avoid infinite recursion with cyclic relations (fix #7938)
238 private final Set<Relation> visited = new HashSet<>();
239 @Override
240 public void visit(Node n) {
241 Point p = nc.getPoint(n);
242 g.drawRect(p.x-1, p.y-1, 2, 2);
243 }
244
245 public void visit(Node n1, Node n2) {
246 Point p1 = nc.getPoint(n1);
247 Point p2 = nc.getPoint(n2);
248 g.drawLine(p1.x, p1.y, p2.x, p2.y);
249 }
250
251 @Override
252 public void visit(Way w) {
253 Node lastN = null;
254 for (Node n : w.getNodes()) {
255 if (lastN == null) {
256 lastN = n;
257 continue;
258 }
259 visit(lastN, n);
260 lastN = n;
261 }
262 }
263
264 @Override
265 public void visit(Relation e) {
266 if (!visited.contains(e)) {
267 visited.add(e);
268 try {
269 for (RelationMember em : e.getMembers()) {
270 em.getMember().accept(this);
271 }
272 } finally {
273 visited.remove(e);
274 }
275 }
276 }
277 };
278 for (OsmPrimitive o : lstConflicts.getSelectedValuesList()) {
279 if (conflicts == null || !conflicts.hasConflictForMy(o)) {
280 continue;
281 }
282 conflicts.getConflictForMy(o).getTheir().accept(conflictPainter);
283 }
284 }
285
286 @Override
287 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
288 if (oldLayer != null) {
289 oldLayer.getConflicts().removeConflictListener(this);
290 }
291 if (newLayer != null) {
292 newLayer.getConflicts().addConflictListener(this);
293 }
294 refreshView();
295 }
296
297
298 /**
299 * replies the conflict collection currently held by this dialog; may be null
300 *
301 * @return the conflict collection currently held by this dialog; may be null
302 */
303 public ConflictCollection getConflicts() {
304 return conflicts;
305 }
306
307 /**
308 * returns the first selected item of the conflicts list
309 *
310 * @return Conflict
311 */
312 public Conflict<? extends OsmPrimitive> getSelectedConflict() {
313 if (conflicts == null || model.getSize() == 0) return null;
314
315 int index = lstConflicts.getSelectedIndex();
316 if (index < 0) return null;
317
318 return conflicts.get(index);
319 }
320
321 @Override
322 public void onConflictsAdded(ConflictCollection conflicts) {
323 refreshView();
324 }
325
326 @Override
327 public void onConflictsRemoved(ConflictCollection conflicts) {
328 Main.info("1 conflict has been resolved.");
329 refreshView();
330 }
331
332 @Override
333 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
334 lstConflicts.clearSelection();
335 for (OsmPrimitive osm : newSelection) {
336 if (conflicts != null && conflicts.hasConflictForMy(osm)) {
337 int pos = model.indexOf(osm);
338 if (pos >= 0) {
339 lstConflicts.addSelectionInterval(pos, pos);
340 }
341 }
342 }
343 }
344
345 @Override
346 public String helpTopic() {
347 return ht("/Dialog/ConflictList");
348 }
349
350 class MouseEventHandler extends PopupMenuLauncher {
351 /**
352 * Constructs a new {@code MouseEventHandler}.
353 */
354 public MouseEventHandler() {
355 super(popupMenu);
356 }
357
358 @Override public void mouseClicked(MouseEvent e) {
359 if (isDoubleClick(e)) {
360 resolve();
361 }
362 }
363 }
364
365 /**
366 * The {@link ListModel} for conflicts
367 *
368 */
369 class ConflictListModel implements ListModel<OsmPrimitive> {
370
371 private final CopyOnWriteArrayList<ListDataListener> listeners;
372
373 /**
374 * Constructs a new {@code ConflictListModel}.
375 */
376 public ConflictListModel() {
377 listeners = new CopyOnWriteArrayList<>();
378 }
379
380 @Override
381 public void addListDataListener(ListDataListener l) {
382 if (l != null) {
383 listeners.addIfAbsent(l);
384 }
385 }
386
387 @Override
388 public void removeListDataListener(ListDataListener l) {
389 listeners.remove(l);
390 }
391
392 protected void fireContentChanged() {
393 ListDataEvent evt = new ListDataEvent(
394 this,
395 ListDataEvent.CONTENTS_CHANGED,
396 0,
397 getSize()
398 );
399 for (ListDataListener listener : listeners) {
400 listener.contentsChanged(evt);
401 }
402 }
403
404 @Override
405 public OsmPrimitive getElementAt(int index) {
406 if (index < 0) return null;
407 if (index >= getSize()) return null;
408 return conflicts.get(index).getMy();
409 }
410
411 @Override
412 public int getSize() {
413 if (conflicts == null) return 0;
414 return conflicts.size();
415 }
416
417 public int indexOf(OsmPrimitive my) {
418 if (conflicts == null) return -1;
419 for (int i = 0; i < conflicts.size(); i++) {
420 if (conflicts.get(i).isMatchingMy(my))
421 return i;
422 }
423 return -1;
424 }
425
426 public OsmPrimitive get(int idx) {
427 if (conflicts == null) return null;
428 return conflicts.get(idx).getMy();
429 }
430 }
431
432 class ResolveAction extends AbstractAction implements ListSelectionListener {
433 public ResolveAction() {
434 putValue(NAME, tr("Resolve"));
435 putValue(SHORT_DESCRIPTION, tr("Open a merge dialog of all selected items in the list above."));
436 putValue(SMALL_ICON, ImageProvider.get("dialogs", "conflict"));
437 putValue("help", ht("/Dialog/ConflictList#ResolveAction"));
438 }
439
440 @Override
441 public void actionPerformed(ActionEvent e) {
442 resolve();
443 }
444
445 @Override
446 public void valueChanged(ListSelectionEvent e) {
447 ListSelectionModel model = (ListSelectionModel) e.getSource();
448 boolean enabled = model.getMinSelectionIndex() >= 0
449 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex();
450 setEnabled(enabled);
451 }
452 }
453
454 final class SelectAction extends AbstractSelectAction implements ListSelectionListener {
455 private SelectAction() {
456 putValue("help", ht("/Dialog/ConflictList#SelectAction"));
457 }
458
459 @Override
460 public void actionPerformed(ActionEvent e) {
461 Collection<OsmPrimitive> sel = new LinkedList<>();
462 for (OsmPrimitive o : lstConflicts.getSelectedValuesList()) {
463 sel.add(o);
464 }
465 DataSet ds = Main.main.getCurrentDataSet();
466 if (ds != null) { // Can't see how it is possible but it happened in #7942
467 ds.setSelected(sel);
468 }
469 }
470
471 @Override
472 public void valueChanged(ListSelectionEvent e) {
473 ListSelectionModel model = (ListSelectionModel) e.getSource();
474 boolean enabled = model.getMinSelectionIndex() >= 0
475 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex();
476 setEnabled(enabled);
477 }
478 }
479
480 /**
481 * Warns the user about the number of detected conflicts
482 *
483 * @param numNewConflicts the number of detected conflicts
484 * @since 5775
485 */
486 public void warnNumNewConflicts(int numNewConflicts) {
487 if (numNewConflicts == 0) return;
488
489 String msg1 = trn(
490 "There was {0} conflict detected.",
491 "There were {0} conflicts detected.",
492 numNewConflicts,
493 numNewConflicts
494 );
495
496 final StringBuilder sb = new StringBuilder();
497 sb.append("<html>").append(msg1).append("</html>");
498 if (numNewConflicts > 0) {
499 final ButtonSpec[] options = new ButtonSpec[] {
500 new ButtonSpec(
501 tr("OK"),
502 ImageProvider.get("ok"),
503 tr("Click to close this dialog and continue editing"),
504 null /* no specific help */
505 )
506 };
507 GuiHelper.runInEDT(new Runnable() {
508 @Override
509 public void run() {
510 HelpAwareOptionPane.showOptionDialog(
511 Main.parent,
512 sb.toString(),
513 tr("Conflicts detected"),
514 JOptionPane.WARNING_MESSAGE,
515 null, /* no icon */
516 options,
517 options[0],
518 ht("/Concepts/Conflict#WarningAboutDetectedConflicts")
519 );
520 unfurlDialog();
521 Main.map.repaint();
522 }
523 });
524 }
525 }
526}
Note: See TracBrowser for help on using the repository browser.