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

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

fix #7942 - Unexplained NPE in Conflict Dialog

  • Property svn:eol-style set to native
File size: 13.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;
7
8import java.awt.Color;
9import java.awt.Graphics;
10import java.awt.Point;
11import java.awt.event.ActionEvent;
12import java.awt.event.KeyEvent;
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.HashSet;
18import java.util.Iterator;
19import java.util.LinkedList;
20import java.util.Set;
21import java.util.concurrent.CopyOnWriteArrayList;
22
23import javax.swing.AbstractAction;
24import javax.swing.JList;
25import javax.swing.ListModel;
26import javax.swing.ListSelectionModel;
27import javax.swing.event.ListDataEvent;
28import javax.swing.event.ListDataListener;
29import javax.swing.event.ListSelectionEvent;
30import javax.swing.event.ListSelectionListener;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.data.SelectionChangedListener;
34import org.openstreetmap.josm.data.conflict.Conflict;
35import org.openstreetmap.josm.data.conflict.ConflictCollection;
36import org.openstreetmap.josm.data.conflict.IConflictListener;
37import org.openstreetmap.josm.data.osm.DataSet;
38import org.openstreetmap.josm.data.osm.Node;
39import org.openstreetmap.josm.data.osm.OsmPrimitive;
40import org.openstreetmap.josm.data.osm.Relation;
41import org.openstreetmap.josm.data.osm.RelationMember;
42import org.openstreetmap.josm.data.osm.Way;
43import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
44import org.openstreetmap.josm.data.osm.visitor.Visitor;
45import org.openstreetmap.josm.gui.MapView;
46import org.openstreetmap.josm.gui.NavigatableComponent;
47import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
48import org.openstreetmap.josm.gui.SideButton;
49import org.openstreetmap.josm.gui.layer.OsmDataLayer;
50import org.openstreetmap.josm.gui.util.GuiHelper;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.Shortcut;
53
54/**
55 * This dialog displays the {@link ConflictCollection} of the active {@link OsmDataLayer} in a toggle
56 * dialog on the right of the main frame.
57 *
58 */
59public final class ConflictDialog extends ToggleDialog implements MapView.EditLayerChangeListener, IConflictListener, SelectionChangedListener{
60
61 /**
62 * Replies the color used to paint conflicts.
63 *
64 * @return the color used to paint conflicts
65 * @since 1221
66 * @see #paintConflicts
67 */
68 static public Color getColor() {
69 return Main.pref.getColor(marktr("conflict"), Color.gray);
70 }
71
72 /** the collection of conflicts displayed by this conflict dialog */
73 private ConflictCollection conflicts;
74
75 /** the model for the list of conflicts */
76 private ConflictListModel model;
77 /** the list widget for the list of conflicts */
78 private JList lstConflicts;
79
80 private ResolveAction actResolve;
81 private SelectAction actSelect;
82
83 /**
84 * builds the GUI
85 */
86 protected void build() {
87 model = new ConflictListModel();
88
89 lstConflicts = new JList(model);
90 lstConflicts.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
91 lstConflicts.setCellRenderer(new OsmPrimitivRenderer());
92 lstConflicts.addMouseListener(new MouseAdapter(){
93 @Override public void mouseClicked(MouseEvent e) {
94 if (e.getClickCount() >= 2) {
95 resolve();
96 }
97 }
98 });
99 lstConflicts.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
100 public void valueChanged(ListSelectionEvent e) {
101 Main.map.mapView.repaint();
102 }
103 });
104
105 SideButton btnResolve = new SideButton(actResolve = new ResolveAction());
106 lstConflicts.getSelectionModel().addListSelectionListener(actResolve);
107
108 SideButton btnSelect = new SideButton(actSelect = new SelectAction());
109 lstConflicts.getSelectionModel().addListSelectionListener(actSelect);
110
111 createLayout(lstConflicts, true, Arrays.asList(new SideButton[] {
112 btnResolve, btnSelect
113 }));
114 }
115
116 /**
117 * constructor
118 */
119 public ConflictDialog() {
120 super(tr("Conflict"), "conflict", tr("Resolve conflicts."),
121 Shortcut.registerShortcut("subwindow:conflict", tr("Toggle: {0}", tr("Conflict")),
122 KeyEvent.VK_C, Shortcut.ALT_SHIFT), 100);
123
124 build();
125 refreshView();
126 }
127
128 @Override
129 public void showNotify() {
130 DataSet.addSelectionListener(this);
131 MapView.addEditLayerChangeListener(this, true);
132 refreshView();
133 }
134
135 @Override
136 public void hideNotify() {
137 MapView.removeEditLayerChangeListener(this);
138 DataSet.removeSelectionListener(this);
139 }
140
141 /**
142 * Launches a conflict resolution dialog for the first selected conflict
143 *
144 */
145 private final void resolve() {
146 if (conflicts == null || model.getSize() == 0) return;
147
148 int index = lstConflicts.getSelectedIndex();
149 if (index < 0) {
150 index = 0;
151 }
152
153 Conflict<? extends OsmPrimitive> c = conflicts.get(index);
154 ConflictResolutionDialog dialog = new ConflictResolutionDialog(Main.parent);
155 dialog.getConflictResolver().populate(c);
156 dialog.setVisible(true);
157
158 lstConflicts.setSelectedIndex(index);
159
160 Main.map.mapView.repaint();
161 }
162
163 /**
164 * refreshes the view of this dialog
165 */
166 public final void refreshView() {
167 OsmDataLayer editLayer = Main.main.getEditLayer();
168 conflicts = (editLayer == null ? new ConflictCollection() : editLayer.getConflicts());
169 GuiHelper.runInEDT(new Runnable() {
170 @Override
171 public void run() {
172 model.fireContentChanged();
173 updateTitle(conflicts.size());
174 }
175 });
176 }
177
178 private void updateTitle(int conflictsCount) {
179 if (conflictsCount > 0) {
180 setTitle(tr("Conflicts: {0} unresolved", conflicts.size()));
181 } else {
182 setTitle(tr("Conflict"));
183 }
184 }
185
186 /**
187 * Paints all conflicts that can be expressed on the main window.
188 *
189 * @param g The {@code Graphics} used to paint
190 * @param nc The {@code NavigatableComponent} used to get screen coordinates of nodes
191 * @since 86
192 */
193 public void paintConflicts(final Graphics g, final NavigatableComponent nc) {
194 Color preferencesColor = getColor();
195 if (preferencesColor.equals(Main.pref.getColor(marktr("background"), Color.black)))
196 return;
197 g.setColor(preferencesColor);
198 Visitor conflictPainter = new AbstractVisitor() {
199 // Manage a stack of visited relations to avoid infinite recursion with cyclic relations (fix #7938)
200 private final Set<Relation> visited = new HashSet<Relation>();
201 public void visit(Node n) {
202 Point p = nc.getPoint(n);
203 g.drawRect(p.x-1, p.y-1, 2, 2);
204 }
205 public void visit(Node n1, Node n2) {
206 Point p1 = nc.getPoint(n1);
207 Point p2 = nc.getPoint(n2);
208 g.drawLine(p1.x, p1.y, p2.x, p2.y);
209 }
210 public void visit(Way w) {
211 Node lastN = null;
212 for (Node n : w.getNodes()) {
213 if (lastN == null) {
214 lastN = n;
215 continue;
216 }
217 visit(lastN, n);
218 lastN = n;
219 }
220 }
221 public void visit(Relation e) {
222 for (RelationMember em : e.getMembers()) {
223 OsmPrimitive m = em.getMember();
224 if (m instanceof Node || m instanceof Way) {
225 m.visit(this);
226 } else if (m instanceof Relation && !visited.contains(m)) {
227 visited.add((Relation) m);
228 try {
229 m.visit(this);
230 } finally {
231 visited.remove(m);
232 }
233 }
234 }
235 }
236 };
237 for (Object o : lstConflicts.getSelectedValues()) {
238 if (conflicts == null || !conflicts.hasConflictForMy((OsmPrimitive)o)) {
239 continue;
240 }
241 conflicts.getConflictForMy((OsmPrimitive)o).getTheir().visit(conflictPainter);
242 }
243 }
244
245 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
246 if (oldLayer != null) {
247 oldLayer.getConflicts().removeConflictListener(this);
248 }
249 if (newLayer != null) {
250 newLayer.getConflicts().addConflictListener(this);
251 }
252 refreshView();
253 }
254
255
256 /**
257 * replies the conflict collection currently held by this dialog; may be null
258 *
259 * @return the conflict collection currently held by this dialog; may be null
260 */
261 public ConflictCollection getConflicts() {
262 return conflicts;
263 }
264
265 /**
266 * returns the first selected item of the conflicts list
267 *
268 * @return Conflict
269 */
270 public Conflict<? extends OsmPrimitive> getSelectedConflict() {
271 if (conflicts == null || model.getSize() == 0) return null;
272
273 int index = lstConflicts.getSelectedIndex();
274 if (index < 0) return null;
275
276 return conflicts.get(index);
277 }
278
279 public void onConflictsAdded(ConflictCollection conflicts) {
280 refreshView();
281 }
282
283 public void onConflictsRemoved(ConflictCollection conflicts) {
284 System.err.println("1 conflict has been resolved.");
285 refreshView();
286 }
287
288 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
289 lstConflicts.clearSelection();
290 for (OsmPrimitive osm : newSelection) {
291 if (conflicts != null && conflicts.hasConflictForMy(osm)) {
292 int pos = model.indexOf(osm);
293 if (pos >= 0) {
294 lstConflicts.addSelectionInterval(pos, pos);
295 }
296 }
297 }
298 }
299
300 @Override
301 public String helpTopic() {
302 return ht("/Dialog/ConflictList");
303 }
304
305 /**
306 * The {@link ListModel} for conflicts
307 *
308 */
309 class ConflictListModel implements ListModel {
310
311 private CopyOnWriteArrayList<ListDataListener> listeners;
312
313 public ConflictListModel() {
314 listeners = new CopyOnWriteArrayList<ListDataListener>();
315 }
316
317 public void addListDataListener(ListDataListener l) {
318 if (l != null) {
319 listeners.addIfAbsent(l);
320 }
321 }
322
323 public void removeListDataListener(ListDataListener l) {
324 listeners.remove(l);
325 }
326
327 protected void fireContentChanged() {
328 ListDataEvent evt = new ListDataEvent(
329 this,
330 ListDataEvent.CONTENTS_CHANGED,
331 0,
332 getSize()
333 );
334 Iterator<ListDataListener> it = listeners.iterator();
335 while(it.hasNext()) {
336 it.next().contentsChanged(evt);
337 }
338 }
339
340 public Object getElementAt(int index) {
341 if (index < 0) return null;
342 if (index >= getSize()) return null;
343 return conflicts.get(index).getMy();
344 }
345
346 public int getSize() {
347 if (conflicts == null) return 0;
348 return conflicts.size();
349 }
350
351 public int indexOf(OsmPrimitive my) {
352 if (conflicts == null) return -1;
353 for (int i=0; i < conflicts.size();i++) {
354 if (conflicts.get(i).isMatchingMy(my))
355 return i;
356 }
357 return -1;
358 }
359
360 public OsmPrimitive get(int idx) {
361 if (conflicts == null) return null;
362 return conflicts.get(idx).getMy();
363 }
364 }
365
366 class ResolveAction extends AbstractAction implements ListSelectionListener {
367 public ResolveAction() {
368 putValue(NAME, tr("Resolve"));
369 putValue(SHORT_DESCRIPTION, tr("Open a merge dialog of all selected items in the list above."));
370 putValue(SMALL_ICON, ImageProvider.get("dialogs", "conflict"));
371 putValue("help", ht("/Dialog/ConflictList#ResolveAction"));
372 }
373
374 public void actionPerformed(ActionEvent e) {
375 resolve();
376 }
377
378 public void valueChanged(ListSelectionEvent e) {
379 ListSelectionModel model = (ListSelectionModel)e.getSource();
380 boolean enabled = model.getMinSelectionIndex() >= 0
381 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex();
382 setEnabled(enabled);
383 }
384 }
385
386 class SelectAction extends AbstractAction implements ListSelectionListener {
387 public SelectAction() {
388 putValue(NAME, tr("Select"));
389 putValue(SHORT_DESCRIPTION, tr("Set the selected elements on the map to the selected items in the list above."));
390 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
391 putValue("help", ht("/Dialog/ConflictList#SelectAction"));
392 }
393
394 public void actionPerformed(ActionEvent e) {
395 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
396 for (Object o : lstConflicts.getSelectedValues()) {
397 sel.add((OsmPrimitive)o);
398 }
399 DataSet ds = Main.main.getCurrentDataSet();
400 if (ds != null) { // Can't see how it is possible but it happened in #7942
401 ds.setSelected(sel);
402 }
403 }
404
405 public void valueChanged(ListSelectionEvent e) {
406 ListSelectionModel model = (ListSelectionModel)e.getSource();
407 boolean enabled = model.getMinSelectionIndex() >= 0
408 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex();
409 setEnabled(enabled);
410 }
411 }
412
413}
Note: See TracBrowser for help on using the repository browser.