// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.czechaddress.gui; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.GroupLayout; import javax.swing.Timer; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.plugins.czechaddress.CzechAddressPlugin; import org.openstreetmap.josm.plugins.czechaddress.NotNullList; import org.openstreetmap.josm.plugins.czechaddress.Preferences; import org.openstreetmap.josm.plugins.czechaddress.PrimUtils; import org.openstreetmap.josm.plugins.czechaddress.StatusListener; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.AddressElement; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.House; import org.openstreetmap.josm.plugins.czechaddress.gui.utils.HalfCookedComboBoxModel; import org.openstreetmap.josm.plugins.czechaddress.gui.utils.UniversalListRenderer; import org.openstreetmap.josm.plugins.czechaddress.intelligence.Reasoner; import org.openstreetmap.josm.plugins.czechaddress.proposal.AddKeyValueProposal; import org.openstreetmap.josm.plugins.czechaddress.proposal.Proposal; import org.openstreetmap.josm.plugins.czechaddress.proposal.ProposalContainer; /** * Dialog for adding/editing an address of a single primitive. * * <p><b>TODO:</b> This dialog does not dispose and disconnect from * message handling system after being closed. Reproduce: Create a node * using this dialog, delete it and update reasoner.</p> * * @author radomir.cernoch@gmail.com */ public class PointManipulatorDialog extends ExtendedDialog implements StatusListener { private Timer updateMatchesTimer = null; private Action updateMatchesAction; private ProposalContainer proposalContainer; /** * Creates and shows the dialog. * @param primitive the primitive, which should be edited by this dialog */ public PointManipulatorDialog(OsmPrimitive primitive) { super(Main.parent, "Adresní bod", new String[] {"OK", "Zrušit"}, true); initComponents(); // Create action for delaying the database query... updateMatchesAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { updateMatches(); } }; // Register for plugin-wide messages. CzechAddressPlugin.addStatusListener(this); updateLocation(); // A the beginning there are no proposals. proposalContainer = new ProposalContainer(primitive); proposalList.setModel(proposalContainer); proposalList.setCellRenderer(new UniversalListRenderer()); // Init the "match" combobox. matchesComboBox.setModel(new MatchesComboBoxModel()); matchesComboBox.setRenderer(new UniversalListRenderer()); if (primitive.get(PrimUtils.KEY_ADDR_CP) != null) { alternateNumberEdit.setText(primitive.get(PrimUtils.KEY_ADDR_CP)); updateMatches(); } // And finalize initializing the form. setContent(mainPanel, false); setButtonIcons(new String[] {"ok.png", "cancel.png"}); setDefaultButton(1); setCancelButton(2); setupDialog(); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setAlwaysOnTop(false); // TODO: Why does it always crash if the modality is set in constructor? setModal(false); } @Override protected void buttonAction(int i, ActionEvent evt) { super.buttonAction(i, evt); if (getValue() == 1) { if (updateMatchesTimer != null && updateMatchesTimer.isRunning()) { updateMatchesTimer.stop(); updateMatches(); } proposalContainer.applyAll(); Main.getLayerManager().getEditDataSet().setSelected((Node) null); // TODO: This is an ugly hack. Main.getLayerManager().getEditDataSet().setSelected(proposalContainer.getTarget()); AddressElement elem = (AddressElement) matchesComboBox.getSelectedItem(); if (elem != null) { Reasoner r = Reasoner.getInstance(); synchronized (r) { r.openTransaction(); r.doOverwrite(proposalContainer.getTarget(), elem); r.closeTransaction(); } } } CzechAddressPlugin.removeStatusListener(this); } /** * Updates the dialog after a location has changed. */ public void updateLocation() { locationEdit.setText(CzechAddressPlugin.getLocation().toString()); } /** * Updates the combobox with houses that match the current input. */ public void updateMatches() { if (proposalContainer.getTarget().isDeleted()) setVisible(false); OsmPrimitive prim = this.proposalContainer.getTarget(); Reasoner r = Reasoner.getInstance(); List<AddressElement> elems = new NotNullList<>(); synchronized (r) { Map<String, String> backup = prim.getKeys(); r.openTransaction(); for (AddressElement elem : r.getCandidates(prim)) { r.unOverwrite(prim, elem); } prim.setKeys(null); prim.put(PrimUtils.KEY_ADDR_CP, alternateNumberEdit.getText()); r.update(prim); elems.addAll(r.getCandidates(prim)); prim.setKeys(backup); r.update(prim); r.closeTransaction(); } MatchesComboBoxModel matchesModel = ((MatchesComboBoxModel) matchesComboBox.getModel()); // Fill the combobox with suitable houses. matchesModel.setElements(elems); if (matchesModel.getSize() > 0) { matchesComboBox.setSelectedIndex(0); return; } // If there are no suitable houses, invent one! House fakeHouse = new House(alternateNumberEdit.getText(), null); fakeHouse.setParent(CzechAddressPlugin.getLocation()); proposalContainer.setProposals( fakeHouse.getDiff(proposalContainer.getTarget())); } @Override public void pluginStatusChanged(int message) { // If location changes, we block the dialog until reasoning is done. if (message == MESSAGE_LOCATION_CHANGED) { updateLocation(); mainPanel.setEnabled(false); } } /** * This method is called from within the constructor to * initialize the form. * * <p><b>WARNING:</b> Do NOT modify this code. The content of this method is * always regenerated by the Form Editor.</p> */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jLabel4 = new javax.swing.JLabel(); mainPanel = new javax.swing.JPanel(); jLabel1 = new javax.swing.JLabel(); alternateNumberEdit = new javax.swing.JTextField(); jLabel5 = new javax.swing.JLabel(); locationEdit = new javax.swing.JTextField(); changeLocationButton = new javax.swing.JButton(); jScrollPane1 = new javax.swing.JScrollPane(); proposalList = new javax.swing.JList<>(); matchesComboBox = new javax.swing.JComboBox<>(); jLabel6 = new javax.swing.JLabel(); statusLabel = new javax.swing.JLabel(); jLabel4.setText("jLabel4"); getContentPane().setLayout(new java.awt.GridLayout(1, 0)); jLabel1.setText("Číslo popisné:"); alternateNumberEdit.addKeyListener(new java.awt.event.KeyAdapter() { @Override public void keyReleased(java.awt.event.KeyEvent evt) { PointManipulatorDialog.this.keyReleased(evt); } }); jLabel5.setText("Místo:"); locationEdit.setEditable(false); locationEdit.setFocusable(false); changeLocationButton.setText("Změnit"); changeLocationButton.setFocusable(false); changeLocationButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent evt) { changeLocationButtonActionPerformed(evt); } }); proposalList.addKeyListener(new java.awt.event.KeyAdapter() { @Override public void keyReleased(java.awt.event.KeyEvent evt) { proposalListKeyReleased(evt); } }); jScrollPane1.setViewportView(proposalList); //matchesComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "" })); matchesComboBox.addItemListener(new java.awt.event.ItemListener() { @Override public void itemStateChanged(java.awt.event.ItemEvent evt) { matchChanged(evt); } }); jLabel6.setText("Zaznam v databazi:"); statusLabel.setForeground(new java.awt.Color(176, 1, 1)); statusLabel.setText(" "); javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel); mainPanel.setLayout(mainPanelLayout); mainPanelLayout.setHorizontalGroup( mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(mainPanelLayout.createSequentialGroup() .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel6) .addComponent(jLabel1) .addComponent(jLabel5)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() .addComponent(locationEdit, javax.swing.GroupLayout.DEFAULT_SIZE, 228, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(changeLocationButton)) .addComponent(alternateNumberEdit, javax.swing.GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE) .addComponent(matchesComboBox, 0, 293, Short.MAX_VALUE))) .addComponent(statusLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 433, Short.MAX_VALUE) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 433, Short.MAX_VALUE) ); mainPanelLayout.setVerticalGroup( mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(mainPanelLayout.createSequentialGroup() .addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(locationEdit, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(jLabel5) .addComponent(changeLocationButton)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(alternateNumberEdit, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(jLabel1)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(jLabel6) .addComponent(matchesComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 153, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(statusLabel)) ); getContentPane().add(mainPanel); } // </editor-fold>//GEN-END:initComponents private void changeLocationButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_changeLocationButtonActionPerformed CzechAddressPlugin.changeLocation(); } //GEN-LAST:event_changeLocationButtonActionPerformed private void keyReleased(java.awt.event.KeyEvent evt) { //GEN-FIRST:event_keyReleased if (updateMatchesTimer != null) updateMatchesTimer.stop(); updateMatchesTimer = new Timer(300, updateMatchesAction); updateMatchesTimer.setRepeats(false); updateMatchesTimer.start(); } //GEN-LAST:event_keyReleased private void proposalListKeyReleased(java.awt.event.KeyEvent evt) { //GEN-FIRST:event_proposalListKeyReleased if (evt.getKeyCode() == KeyEvent.VK_DELETE) { for (Object o : proposalList.getSelectedValuesList()) { proposalContainer.removeProposal((Proposal) o); } } } //GEN-LAST:event_proposalListKeyReleased private void matchChanged(java.awt.event.ItemEvent evt) { //GEN-FIRST:event_matchChanged if (matchesComboBox.getSelectedItem() == null) return; AddressElement selectedElement = (AddressElement) matchesComboBox.getSelectedItem(); proposalContainer.setProposals(selectedElement.getDiff(proposalContainer.getTarget())); Preferences p = Preferences.getInstance(); if (p.addNewTag && proposalContainer.getTarget().keySet().size() == 0) proposalContainer.addProposal(new AddKeyValueProposal( p.addNewTagKey, p.addNewTagValue)); if (p.addBuildingTag && proposalContainer.getTarget() instanceof Way && proposalContainer.getTarget().get("building") == null) proposalContainer.addProposal(new AddKeyValueProposal("building", "yes")); } //GEN-LAST:event_matchChanged // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField alternateNumberEdit; private javax.swing.JButton changeLocationButton; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField locationEdit; private javax.swing.JPanel mainPanel; private javax.swing.JComboBox<AddressElement> matchesComboBox; private javax.swing.JList<Proposal> proposalList; private javax.swing.JLabel statusLabel; // End of variables declaration//GEN-END:variables /** * Container for all Houses, which match the given 'alternatenumber'. */ private class MatchesComboBoxModel extends HalfCookedComboBoxModel<AddressElement> { private List<AddressElement> matches = null; AddressElement selected = null; public void setElements(List<AddressElement> elements) { this.matches = elements; selected = null; notifyAllListeners(); } @Override public void setSelectedItem(Object anItem) { if (matches == null) return; selected = (AddressElement) anItem; if (Reasoner.getInstance().translate(selected) != null) statusLabel.setText("Vybraná adresa už v mapě existuje."+ " Potvrzením vznikne konflikt."); else statusLabel.setText(" "); } @Override public Object getSelectedItem() { return selected; } @Override public int getSize() { if (matches == null) return 0; return matches.size(); } @Override public AddressElement getElementAt(int index) { if (matches == null) return null; if (index >= matches.size()) return null; return matches.get(index); } } }