// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.czechaddress.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.LayoutStyle;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.plugins.czechaddress.MapUtils;
import org.openstreetmap.josm.plugins.czechaddress.NotNullList;
import org.openstreetmap.josm.plugins.czechaddress.PrimUtils;
import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.AddressElement;
import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.House;
import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.Street;
import org.openstreetmap.josm.plugins.czechaddress.gui.utils.UniversalListRenderer;
import org.openstreetmap.josm.plugins.czechaddress.intelligence.Reasoner;
import org.openstreetmap.josm.plugins.czechaddress.intelligence.ReasonerListener;
import org.openstreetmap.josm.tools.ImageProvider;
/**
* Dialog for displaying and handling conflicts.
*
* @author Radomír Černoch, radomir.cernoch@gmail.com
*/
public final class ConflictResolver extends ExtendedDialog {
private static ConflictResolver singleton = null;
public static ConflictResolver getInstance() {
if (singleton == null)
singleton = new ConflictResolver();
return singleton;
}
public static Logger logger = Logger.getLogger(ConflictResolver.class.getName());
/**
* Creates new dialog, but does not display it, nor hook to messages.
*/
private ConflictResolver() {
super(Main.parent, "Řešení konfliktů", new String[] {}, true);
initComponents();
mainField.setModel(conflictModel);
Reasoner.getInstance().addListener(new ReasonerHook());
// Create those lovely 'zoom' icons for professional look.
Icon zoomIcon = ImageProvider.get("zoom.png");
mainZoomButton.setIcon(zoomIcon); mainZoomButton.setText("");
candZoomButton.setIcon(zoomIcon); candZoomButton.setText("");
Icon cursorIcon = ImageProvider.get("cursor.png");
mainPickButton.setIcon(cursorIcon); mainPickButton.setText("");
candPickButton.setIcon(cursorIcon); candPickButton.setText("");
// And finalize initializing the form.
setContent(mainPanel);
setupDialog();
// TODO: Why does it always crash if the modality is set in constructor?
setModal(false);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
mainPanel = new javax.swing.JPanel();
candZoomButton = new javax.swing.JButton();
mainLabel = new javax.swing.JLabel();
reassignButton = new javax.swing.JButton();
candLabel = new javax.swing.JLabel();
mainZoomButton = new javax.swing.JButton();
mainField = new javax.swing.JComboBox<>();
candField = new javax.swing.JComboBox<>();
candPickButton = new javax.swing.JButton();
mainPickButton = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
getContentPane().setLayout(new java.awt.GridLayout(1, 0));
candZoomButton.setText(" ");
candZoomButton.setEnabled(false);
candZoomButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
candZoomButtonActionPerformed(evt);
}
});
mainLabel.setText("Nejednoznačný prvek:");
reassignButton.setText("Určit jako nejlepší přiřažení");
reassignButton.setEnabled(false);
reassignButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
reassignButtonActionPerformed(evt);
}
});
candLabel.setText("Kandidáti na přiřazení:");
mainZoomButton.setText(" ");
mainZoomButton.setEnabled(false);
mainZoomButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
mainZoomButtonActionPerformed(evt);
}
});
mainField.setModel(new javax.swing.DefaultComboBoxModel<Object>(new String[] {"Item 1", "Item 2", "Item 3", "Item 4"}));
mainField.setRenderer(new UniversalListRenderer());
candField.setModel(new javax.swing.DefaultComboBoxModel<Object>(new String[] {" "}));
candField.setRenderer(new UniversalListRenderer());
candPickButton.setText(" ");
candPickButton.setEnabled(false);
candPickButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
candPickButtonActionPerformed(evt);
}
});
mainPickButton.setText(" ");
mainPickButton.setEnabled(false);
mainPickButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
mainPickButtonActionPerformed(evt);
}
});
GroupLayout mainPanelLayout = new GroupLayout(mainPanel);
mainPanel.setLayout(mainPanelLayout);
mainPanelLayout.setHorizontalGroup(
mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup()
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(candLabel)
.addComponent(mainLabel))
.addGap(6, 6, 6)
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(candField, GroupLayout.Alignment.TRAILING, 0, 430, Short.MAX_VALUE)
.addComponent(mainField, 0, 430, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.TRAILING)
.addComponent(candZoomButton)
.addComponent(mainZoomButton))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(mainPickButton)
.addComponent(candPickButton)))
.addGroup(GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup()
.addContainerGap()
.addComponent(reassignButton))
);
mainPanelLayout.setVerticalGroup(
mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(mainPanelLayout.createSequentialGroup()
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(mainField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(mainLabel))
.addGroup(mainPanelLayout.createSequentialGroup()
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(mainZoomButton)
.addComponent(mainPickButton))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(candPickButton)
.addComponent(candZoomButton)
.addComponent(candField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(candLabel))))
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(reassignButton))
);
getContentPane().add(mainPanel);
pack();
} // </editor-fold>//GEN-END:initComponents
private void mainZoomButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_mainZoomButtonActionPerformed
zoomTo(mainField.getSelectedItem());
} //GEN-LAST:event_mainZoomButtonActionPerformed
private void candZoomButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_candZoomButtonActionPerformed
zoomTo(candField.getSelectedItem());
} //GEN-LAST:event_candZoomButtonActionPerformed
private void zoomTo(Object item) {
if (item instanceof OsmPrimitive)
MapUtils.zoomTo((OsmPrimitive) item);
else if (item instanceof AddressElement) {
OsmPrimitive prim = Reasoner.getInstance().translate((AddressElement) item);
if (prim != null)
MapUtils.zoomTo(prim);
}
}
public void focusElement(AddressElement elem) {
int index = Collections.binarySearch(conflictModel.elements, elem);
if (index >= 0) {
mainField.setSelectedIndex(index);
mainField.repaint();
showDialog();
}
}
private void reassignButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_reassignButtonActionPerformed
Reasoner r = Reasoner.getInstance();
synchronized (r) {
r.openTransaction();
if (conflictModel.getSelectedItem() instanceof OsmPrimitive) {
OsmPrimitive prim = (OsmPrimitive) conflictModel.getSelectedItem();
if (r.translate(prim) != null)
r.unOverwrite(prim, r.translate(prim));
ComboBoxModel<Object> model = candField.getModel();
for (int i = 0; i < model.getSize(); i++) {
AddressElement elem = (AddressElement) model.getElementAt(i);
r.unOverwrite(prim, elem);
if (r.translate(elem) != null)
r.unOverwrite(r.translate(elem), elem);
}
r.doOverwrite(prim, (AddressElement) model.getSelectedItem());
}
if (conflictModel.getSelectedItem() instanceof AddressElement) {
AddressElement elem = (AddressElement) conflictModel.getSelectedItem();
if (r.translate(elem) != null)
r.unOverwrite(r.translate(elem), elem);
ComboBoxModel<Object> model = candField.getModel();
for (int i = 0; i < model.getSize(); i++) {
OsmPrimitive prim = (OsmPrimitive) model.getElementAt(i);
r.unOverwrite(prim, elem);
if (r.translate(prim) != null)
r.unOverwrite(prim, r.translate(prim));
}
r.doOverwrite((OsmPrimitive) model.getSelectedItem(), elem);
}
r.closeTransaction();
}
} //GEN-LAST:event_reassignButtonActionPerformed
private void mainPickButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_mainPickButtonActionPerformed
if (mainField.getSelectedItem() instanceof House)
FactoryDialog.getInstance().setSelectedHouse((House) mainField.getSelectedItem());
} //GEN-LAST:event_mainPickButtonActionPerformed
private void candPickButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_candPickButtonActionPerformed
if (candField.getSelectedItem() instanceof House)
FactoryDialog.getInstance().setSelectedHouse((House) candField.getSelectedItem());
} //GEN-LAST:event_candPickButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JComboBox<Object> candField;
private javax.swing.JLabel candLabel;
private javax.swing.JButton candPickButton;
private javax.swing.JButton candZoomButton;
private javax.swing.JComboBox<Object> mainField;
private javax.swing.JLabel mainLabel;
private javax.swing.JPanel mainPanel;
private javax.swing.JButton mainPickButton;
private javax.swing.JButton mainZoomButton;
private javax.swing.JButton reassignButton;
// End of variables declaration//GEN-END:variables
//==============================================================================
/**
* Listenes to the {@link Reasoner} and updates data models.
*/
private class ReasonerHook implements ReasonerListener {
@Override
public void elementChanged(AddressElement elem) {
if (!(elem instanceof House)) return;
logger.log(Level.FINER, "hook: element changed", elem.getName());
if (Reasoner.getInstance().inConflict(elem))
conflictModel.put(elem);
else
conflictModel.remove(elem);
}
@Override
public void primitiveChanged(OsmPrimitive prim) {
if (!House.isMatchable(prim)) return;
logger.log(Level.FINER, "hook: primitive changed", AddressElement.getName(prim));
if (Reasoner.getInstance().inConflict(prim))
conflictModel.put(prim);
else
conflictModel.remove(prim);
}
@Override
public void resonerReseted() {
}
}
//==============================================================================
private ConflictsModel conflictModel = new ConflictsModel();
private class ConflictsModel implements ComboBoxModel<Object> {
ArrayList<AddressElement> elements = new ArrayList<>();
ArrayList<OsmPrimitive> primitives = new ArrayList<>();
Set<ListDataListener> listeners = new HashSet<>();
Object selected = null;
public void put(AddressElement elem) {
int index = Collections.binarySearch(elements, elem);
if (index < 0) {
logger.log(Level.FINE, "conflicts: adding element",
"[" + String.valueOf(-index-1) + "]=„"
+ elem.getName() + "“");
elements.add(-index-1, elem);
ListDataEvent evt = new ListDataEvent(this,
ListDataEvent.INTERVAL_ADDED,
-index-1, -index-1);
for (ListDataListener listener : listeners) {
listener.intervalAdded(evt);
}
if (mainField.getSelectedItem() == null)
mainField.setSelectedIndex(-index-1);
}
}
public void put(OsmPrimitive prim) {
int index = Collections.binarySearch(primitives, prim, PrimUtils.comparator);
if (index < 0) {
logger.log(Level.FINE, "conflicts: adding primitive",
"["+String.valueOf(-index-1)+"]=„"
+AddressElement.getName(prim) + "“");
primitives.add(-index-1, prim);
ListDataEvent evt = new ListDataEvent(this,
ListDataEvent.INTERVAL_ADDED,
-index-1 + elements.size(), -index-1 + elements.size());
for (ListDataListener listener : listeners) {
listener.intervalAdded(evt);
}
if (mainField.getSelectedItem() == null)
mainField.setSelectedIndex(-index-1);
}
}
public void remove(AddressElement elem) {
int index = Collections.binarySearch(elements, elem);
//index = primitives.indexOf(elem);
if (index >= 0) {
logger.log(Level.FINE, "conflicts: removing element",
"[" + String.valueOf(index) + "]=„"
+ elem.getName() + "“");
elements.remove(index);
if (selected == elem)
setSelectedItem(null);
ListDataEvent evt = new ListDataEvent(this,
ListDataEvent.INTERVAL_REMOVED,
index, index);
for (ListDataListener listener : listeners) {
listener.intervalRemoved(evt);
}
}
}
public void remove(OsmPrimitive prim) {
int index = Collections.binarySearch(primitives, prim);
index = primitives.indexOf(prim);
if (index >= 0) {
logger.log(Level.FINE, "conflicts: removing primitive",
"[" + String.valueOf(index) + "]=„"
+ AddressElement.getName(prim) + "“");
primitives.remove(index);
if (selected == prim)
setSelectedItem(null);
ListDataEvent evt = new ListDataEvent(this,
ListDataEvent.INTERVAL_REMOVED,
index + elements.size(), index + elements.size());
for (ListDataListener listener : listeners) {
listener.intervalRemoved(evt);
}
}
}
/*
public void clear() {
logger.log(Level.FINE, "conflicts: clearing");
ListDataEvent evt = new ListDataEvent(this,
ListDataEvent.CONTENTS_CHANGED,
0, elements.size() + primitives.size() - 1);
elements.clear();
primitives.clear();
for (ListDataListener listener : listeners)
listener.contentsChanged(evt);
}
*/
@Override
public void setSelectedItem(Object anItem) {
if (anItem == null && getSize() > 0) {
mainField.setSelectedIndex(0);
return;
}
selected = anItem;
updateZoomButtons(selected, mainZoomButton);
updatePickButtons(selected, mainPickButton);
updateCandidatesModel(anItem);
}
@Override
public Object getSelectedItem() {
return selected;
}
@Override
public int getSize() {
return primitives.size() + elements.size();
}
@Override
public Object getElementAt(int index) {
if (index < elements.size())
return elements.get(index);
index -= elements.size();
if (index < primitives.size())
return primitives.get(index);
return null;
}
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
}
private void updateCandidatesModel(Object selected) {
if (selected instanceof AddressElement) {
AddressElement selElem = (AddressElement) selected;
List<OsmPrimitive> conflPrims = new NotNullList<>();
conflPrims.addAll(Reasoner.getInstance().getCandidates(selElem));
Collections.sort(conflPrims, PrimUtils.comparator);
candField.setModel(new CandidatesModel<Object>(conflPrims));
} else if (selected instanceof OsmPrimitive) {
OsmPrimitive selElem = (OsmPrimitive) selected;
List<AddressElement> conflElems = new NotNullList<>();
conflElems.addAll(Reasoner.getInstance().getCandidates(selElem));
Collections.sort(conflElems);
candField.setModel(new CandidatesModel<Object>(conflElems));
} else {
candField.setModel(new DefaultComboBoxModel<>());
candZoomButton.setEnabled(false);
reassignButton.setEnabled(false);
}
candZoomButton.setEnabled(false);
candPickButton.setEnabled(false);
reassignButton.setEnabled(false);
if (candField.getModel().getSize() > 0)
candField.setSelectedIndex(0);
else
candField.setSelectedIndex(-1);
}
private void updateZoomButtons(Object selected, JButton button) {
if (selected instanceof OsmPrimitive) {
button.setEnabled(true);
} else if (selected instanceof AddressElement) {
button.setEnabled(Reasoner.getInstance().translate(
(AddressElement) selected) != null);
} else {
button.setEnabled(false);
}
}
private void updatePickButtons(Object selected, JButton button) {
button.setEnabled(selected instanceof House || selected instanceof Street);
}
private class CandidatesModel<E> implements ComboBoxModel<E> {
Set<ListDataListener> listeners = new HashSet<>();
List<? extends E> primitives;
Object selected = null;
CandidatesModel(List<? extends E> data) {
primitives = data;
}
@Override
public void setSelectedItem(Object anItem) {
selected = anItem;
updateZoomButtons(selected, candZoomButton);
updatePickButtons(selected, candPickButton);
if (conflictModel.getSelectedItem() instanceof AddressElement) {
reassignButton.setEnabled(selected !=
Reasoner.getInstance().getStrictlyBest(
(AddressElement) conflictModel.getSelectedItem()));
reassignButton.setEnabled(true);
} else if (conflictModel.getSelectedItem() instanceof OsmPrimitive) {
reassignButton.setEnabled(selected !=
Reasoner.getInstance().getStrictlyBest(
(OsmPrimitive) conflictModel.getSelectedItem()));
reassignButton.setEnabled(true);
} else
reassignButton.setEnabled(false);
}
@Override
public Object getSelectedItem() {
return selected;
}
@Override
public int getSize() {
return primitives.size();
}
@Override
public E getElementAt(int index) {
if (index < primitives.size())
return primitives.get(index);
return null;
}
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
}
}