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

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

see #8902 - Small performance enhancements / coding style (patch by shinigami):

  • while -> foreach
  • for -> for each

plus:

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