// License: GPL. See LICENSE file for details. package org.openstreetmap.josm.gui.dialogs; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.AbstractAction; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.conflict.Conflict; import org.openstreetmap.josm.data.conflict.ConflictCollection; import org.openstreetmap.josm.data.conflict.IConflictListener; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; import org.openstreetmap.josm.data.osm.visitor.Visitor; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.NavigatableComponent; import org.openstreetmap.josm.gui.OsmPrimitivRenderer; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; /** * This dialog displays the {@see ConflictCollection} of the active {@see OsmDataLayer} in a toggle * dialog on the right of the main frame. * */ public final class ConflictDialog extends ToggleDialog implements MapView.EditLayerChangeListener, IConflictListener, SelectionChangedListener{ static public Color getColor() { return Main.pref.getColor(marktr("conflict"), Color.gray); } /** the collection of conflicts displayed by this conflict dialog*/ private ConflictCollection conflicts; /** the model for the list of conflicts */ private ConflictListModel model; /** the list widget for the list of conflicts */ private JList lstConflicts; private ResolveAction actResolve; private SelectAction actSelect; /** * builds the GUI */ protected void build() { model = new ConflictListModel(); lstConflicts = new JList(model); lstConflicts.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); lstConflicts.setCellRenderer(new OsmPrimitivRenderer()); lstConflicts.addMouseListener(new MouseAdapter(){ @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() >= 2) { resolve(); } } }); lstConflicts.getSelectionModel().addListSelectionListener(new ListSelectionListener(){ public void valueChanged(ListSelectionEvent e) { Main.map.mapView.repaint(); } }); add(new JScrollPane(lstConflicts), BorderLayout.CENTER); SideButton btnResolve = new SideButton(actResolve = new ResolveAction()); lstConflicts.getSelectionModel().addListSelectionListener(actResolve); SideButton btnSelect = new SideButton(actSelect = new SelectAction()); lstConflicts.getSelectionModel().addListSelectionListener(actSelect); JPanel buttonPanel = getButtonPanel(2); buttonPanel.add(btnResolve); buttonPanel.add(btnSelect); add(buttonPanel, BorderLayout.SOUTH); } /** * constructor */ public ConflictDialog() { super(tr("Conflict"), "conflict", tr("Resolve conflicts."), Shortcut.registerShortcut("subwindow:conflict", tr("Toggle: {0}", tr("Conflict")), KeyEvent.VK_C, Shortcut.GROUP_LAYER), 100); build(); refreshView(); } @Override public void showNotify() { DataSet.selListeners.add(this); MapView.addEditLayerChangeListener(this, true); refreshView(); } @Override public void hideNotify() { MapView.removeEditLayerChangeListener(this); DataSet.selListeners.remove(this); } /** * Launches a conflict resolution dialog for the first selected conflict * */ private final void resolve() { if (conflicts == null || model.getSize() == 0) return; int index = lstConflicts.getSelectedIndex(); if (index < 0) { index = 0; } Conflict<? extends OsmPrimitive> c = conflicts.get(index); ConflictResolutionDialog dialog = new ConflictResolutionDialog(Main.parent); dialog.getConflictResolver().populate(c); dialog.setVisible(true); lstConflicts.setSelectedIndex(index); Main.map.mapView.repaint(); } /** * refreshes the view of this dialog */ public final void refreshView() { OsmDataLayer editLayer = Main.main.getEditLayer(); conflicts = editLayer == null?new ConflictCollection():editLayer.getConflicts(); model.fireContentChanged(); updateTitle(conflicts.size()); } private void updateTitle(int conflictsCount) { if (conflictsCount > 0) { setTitle(tr("Conflicts: {0} unresolved", conflicts.size())); } else { setTitle(tr("Conflict")); } } /** * Paint all conflicts that can be expressed on the main window. */ public void paintConflicts(final Graphics g, final NavigatableComponent nc) { Color preferencesColor = getColor(); if (preferencesColor.equals(Main.pref.getColor(marktr("background"), Color.black))) return; g.setColor(preferencesColor); Visitor conflictPainter = new AbstractVisitor(){ public void visit(Node n) { Point p = nc.getPoint(n); g.drawRect(p.x-1, p.y-1, 2, 2); } public void visit(Node n1, Node n2) { Point p1 = nc.getPoint(n1); Point p2 = nc.getPoint(n2); g.drawLine(p1.x, p1.y, p2.x, p2.y); } public void visit(Way w) { Node lastN = null; for (Node n : w.getNodes()) { if (lastN == null) { lastN = n; continue; } visit(lastN, n); lastN = n; } } public void visit(Relation e) { for (RelationMember em : e.getMembers()) { em.getMember().visit(this); } } }; for (Object o : lstConflicts.getSelectedValues()) { if (conflicts == null || !conflicts.hasConflictForMy((OsmPrimitive)o)) { continue; } conflicts.getConflictForMy((OsmPrimitive)o).getTheir().visit(conflictPainter); } } public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { if (oldLayer != null) { oldLayer.getConflicts().removeConflictListener(this); } if (newLayer != null) { newLayer.getConflicts().addConflictListener(this); } refreshView(); } /** * replies the conflict collection currently held by this dialog; may be null * * @return the conflict collection currently held by this dialog; may be null */ public ConflictCollection getConflicts() { return conflicts; } public void onConflictsAdded(ConflictCollection conflicts) { refreshView(); } public void onConflictsRemoved(ConflictCollection conflicts) { System.err.println("1 conflict has been resolved."); refreshView(); } public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { lstConflicts.clearSelection(); for (OsmPrimitive osm : newSelection) { if (conflicts != null && conflicts.hasConflictForMy(osm)) { int pos = model.indexOf(osm); if (pos >= 0) { lstConflicts.addSelectionInterval(pos, pos); } } } } @Override public String helpTopic() { return "Dialogs/ConflictListDialog"; } /** * The {@see ListModel} for conflicts * */ class ConflictListModel implements ListModel { private CopyOnWriteArrayList<ListDataListener> listeners; public ConflictListModel() { listeners = new CopyOnWriteArrayList<ListDataListener>(); } public void addListDataListener(ListDataListener l) { if (l != null) { listeners.addIfAbsent(l); } } public void removeListDataListener(ListDataListener l) { listeners.remove(l); } protected void fireContentChanged() { ListDataEvent evt = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, getSize() ); Iterator<ListDataListener> it = listeners.iterator(); while(it.hasNext()) { it.next().contentsChanged(evt); } } public Object getElementAt(int index) { if (index < 0) return null; if (index >= getSize()) return null; return conflicts.get(index).getMy(); } public int getSize() { if (conflicts == null) return 0; return conflicts.size(); } public int indexOf(OsmPrimitive my) { if (conflicts == null) return -1; for (int i=0; i < conflicts.size();i++) { if (conflicts.get(i).isMatchingMy(my)) return i; } return -1; } public OsmPrimitive get(int idx) { if (conflicts == null) return null; return conflicts.get(idx).getMy(); } } class ResolveAction extends AbstractAction implements ListSelectionListener { public ResolveAction() { putValue(NAME, tr("Resolve")); putValue(SHORT_DESCRIPTION, tr("Open a merge dialog of all selected items in the list above.")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "conflict")); putValue("help", "Dialogs/ConflictListDialog#ResolveAction"); } public void actionPerformed(ActionEvent e) { resolve(); } public void valueChanged(ListSelectionEvent e) { ListSelectionModel model = (ListSelectionModel)e.getSource(); boolean enabled = model.getMinSelectionIndex() >= 0 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex(); setEnabled(enabled); } } class SelectAction extends AbstractAction implements ListSelectionListener { public SelectAction() { putValue(NAME, tr("Select")); putValue(SHORT_DESCRIPTION, tr("Set the selected elements on the map to the selected items in the list above.")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); putValue("help", "Dialogs/ConflictListDialog#SelectAction"); } public void actionPerformed(ActionEvent e) { Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>(); for (Object o : lstConflicts.getSelectedValues()) { sel.add((OsmPrimitive)o); } Main.main.getCurrentDataSet().setSelected(sel); } public void valueChanged(ListSelectionEvent e) { ListSelectionModel model = (ListSelectionModel)e.getSource(); boolean enabled = model.getMinSelectionIndex() >= 0 && model.getMaxSelectionIndex() >= model.getMinSelectionIndex(); setEnabled(enabled); } } }