// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.czechaddress.gui; import java.awt.Component; import java.awt.Font; import java.awt.LayoutManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.swing.DefaultListCellRenderer; import javax.swing.GroupLayout; import javax.swing.ImageIcon; import javax.swing.JList; import javax.swing.LayoutStyle; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.gui.dialogs.ToggleDialog; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.plugins.czechaddress.CzechAddressPlugin; import org.openstreetmap.josm.plugins.czechaddress.MapUtils; import org.openstreetmap.josm.plugins.czechaddress.StatusListener; import org.openstreetmap.josm.plugins.czechaddress.StringUtils; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.AddressElement; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.ElementWithHouses; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.ElementWithStreets; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.House; import org.openstreetmap.josm.plugins.czechaddress.addressdatabase.Street; import org.openstreetmap.josm.plugins.czechaddress.gui.utils.HalfCookedComboBoxModel; import org.openstreetmap.josm.plugins.czechaddress.gui.utils.HalfCookedListModel; import org.openstreetmap.josm.plugins.czechaddress.intelligence.Reasoner; import org.openstreetmap.josm.plugins.czechaddress.intelligence.ReasonerListener; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; /** * A dockable dialog for controlling the "one click" address node creation. * * @author Radomír Černoch, radomir.cernoch@gmail.com */ public final class FactoryDialog extends ToggleDialog implements SelectionChangedListener, StatusListener, ReasonerListener { private static FactoryDialog singleton = null; public static FactoryDialog getInstance() { if (singleton == null) singleton = new FactoryDialog(); return singleton; } private HouseListModel houseModel = new HouseListModel(); private StreetListModel streetModel = new StreetListModel(); private FactoryDialog() { super("Továrna na adresy", "envelope-scrollbar.png", "Umožňuje rychlé vytváření adresních bodů „jedním kliknutím.“", Shortcut.registerShortcut("subwindow:addressfactory", "Přepnout: Továrna na adresy", KeyEvent.VK_Q, Shortcut.ALT_CTRL_SHIFT), 200); // Hack the ToggleDialog to allow using NetBeans form editor LayoutManager originalManager = getLayout(); initComponents(); setLayout(originalManager); createLayout(mainPanel, false, null); // Register to all messages CzechAddressPlugin.addStatusListener(this); // And init all listeners to data model streetModel.notifyAllListeners(); houseModel.notifyAllListeners(); streetComboBox.setModel(streetModel); streetComboBox.setRenderer(new StreetListRenderer()); houseList.setModel(houseModel); houseList.setCellRenderer(new HouseListRenderer()); } @Override public void destroy() { super.destroy(); singleton = null; } @Override public void pluginStatusChanged(int message) { if (message == MESSAGE_DATABASE_LOADED) { GuiHelper.runInEDTAndWait(new Runnable() { @Override public void run() { relocateButton.setEnabled(true); } }); return; } if (message == MESSAGE_LOCATION_CHANGED) { DataSet.addSelectionListener(this); GuiHelper.runInEDTAndWait(new Runnable() { @Override public void run() { streetModel.setParent(CzechAddressPlugin.getLocation()); relocateButton.setText("Změnit místo"); streetComboBox.setEnabled(true); houseList.setEnabled(true); keepOddityCheckBox.setEnabled(true); } }); return; } } public void setSelectedHouse(House house) { for (int i = 0; i < streetModel.getSize(); i++) { if (streetModel.getElementAt(i) == house.getParent()) { streetComboBox.setSelectedIndex(i); streetComboBox.repaint(); break; } } for (int i = 0; i < houseModel.getSize(); i++) { if (houseModel.getElementAt(i) == house) { houseList.setSelectedIndex(i); houseList.ensureIndexIsVisible(i); break; } } } public House getSelectedHouse() { if (houseList.getSelectedValue() instanceof House) return houseList.getSelectedValue(); return null; } public boolean existsAvailableHouse() { Reasoner r = Reasoner.getInstance(); int i = houseList.getSelectedIndex(); while (i < houseModel.getSize()) { if (r.translate(houseModel.getElementAt(i)) == null) return true; i++; } i = 0; while (i < houseList.getSelectedIndex()) { if (r.translate(houseModel.getElementAt(i)) == null) return true; i++; } return false; } public void selectNextUnmatchedHouse() { int index = houseList.getSelectedIndex(); index++; // Initial kick to do at least one move. House current; while ((current = houseModel.getElementAt(index)) != null && Reasoner.getInstance().translate(current) != null) { index++; } if (index >= houseModel.getSize()) index = 0; houseList.setSelectedIndex(index); houseList.ensureIndexIsVisible(index); } public void selectNextUnmatchedHouseMaintainOddity() { if (getSelectedHouse().getCO() == null) { selectNextUnmatchedHouse(); return; } String oldStr = StringUtils.extractNumber(getSelectedHouse().getCO()); try { int oldNum = -1, newNum = -1; do { selectNextUnmatchedHouse(); String newStr = StringUtils.extractNumber(getSelectedHouse().getCO()); if (oldNum == -1) oldNum = Integer.valueOf(oldStr); newNum = Integer.valueOf(newStr); } while ((oldNum + newNum) % 2 == 1 && houseList.getSelectedIndex() != 0); // If anything goes wrong, we can silently ignore the errors. // The selected item just does not get updated... } catch (Exception exp) { Main.trace(exp); } } public void selectNextUnmatchedHouseByCheckBox() { if (keepOddityCheckBox.isSelected()) selectNextUnmatchedHouseMaintainOddity(); else selectNextUnmatchedHouse(); } public boolean selectionListenerActivated = true; @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { if (!selectionListenerActivated) return; if (newSelection.size() != 1) return; OsmPrimitive selectedPrim = (OsmPrimitive) newSelection.toArray()[0]; String streetName; if ((streetName = selectedPrim.get("addr:street")) == null) { if (selectedPrim.get("highway") == null) return; else if ((streetName = selectedPrim.get("name")) == null) return; } Street selectedStreet = null; for (Street street : CzechAddressPlugin.getLocation().getStreets()) { if (street.getName().toUpperCase().equals(streetName.toUpperCase())) { selectedStreet = street; break; } } if (selectedStreet == null) return; streetComboBox.setSelectedItem(selectedStreet); streetModel.notifyAllListeners(); int bestQuality = -5; House bestHouse = null; for (House currHouse : selectedStreet.getHouses()) { int currQuality = currHouse.getQ(selectedPrim); if (currQuality > bestQuality) { bestQuality = currQuality; bestHouse = currHouse; } } if (bestHouse == null) return; houseList.setSelectedValue(bestHouse, true); houseList.ensureIndexIsVisible(houseList.getSelectedIndex()); houseModel.notifyAllListeners(); } /** 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(); jScrollPane1 = new javax.swing.JScrollPane(); houseList = new javax.swing.JList<>(); keepOddityCheckBox = new javax.swing.JCheckBox(); relocateButton = new javax.swing.JButton(); streetComboBox = new javax.swing.JComboBox<>(); setLayout(new java.awt.GridLayout(1, 0)); /* houseList.setModel(new javax.swing.AbstractListModel<String>() { String[] strings = { " " }; @Override public int getSize() { return strings.length; } @Override public String getElementAt(int i) { return strings[i]; } });*/ houseList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); houseList.setEnabled(false); houseList.setFocusable(false); houseList.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mouseClicked(java.awt.event.MouseEvent evt) { houseListClicked(evt); } }); jScrollPane1.setViewportView(houseList); keepOddityCheckBox.setSelected(true); keepOddityCheckBox.setText("Zachovávat sudost / lichost"); keepOddityCheckBox.setEnabled(false); relocateButton.setText("Inicializovat"); relocateButton.setEnabled(false); relocateButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { relocateButtonActionPerformed(evt); } }); streetComboBox.setModel(streetModel); streetComboBox.setEnabled(false); streetComboBox.setFocusable(false); GroupLayout mainPanelLayout = new GroupLayout(mainPanel); mainPanel.setLayout(mainPanelLayout); mainPanelLayout.setHorizontalGroup( mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() .addComponent(streetComboBox, 0, 199, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(relocateButton)) .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(keepOddityCheckBox, GroupLayout.DEFAULT_SIZE, 278, Short.MAX_VALUE) .addContainerGap()) .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 290, Short.MAX_VALUE) ); mainPanelLayout.setVerticalGroup( mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(mainPanelLayout.createSequentialGroup() .addGroup(mainPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(streetComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(relocateButton)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 125, Short.MAX_VALUE) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(keepOddityCheckBox)) ); add(mainPanel); } // </editor-fold>//GEN-END:initComponents private void relocateButtonActionPerformed(java.awt.event.ActionEvent evt) { //GEN-FIRST:event_relocateButtonActionPerformed CzechAddressPlugin.changeLocation(); } //GEN-LAST:event_relocateButtonActionPerformed private void houseListClicked(java.awt.event.MouseEvent evt) { //GEN-FIRST:event_houseListClicked if (evt.getClickCount() == 2 && evt.getButton() == MouseEvent.BUTTON1) { Reasoner r = Reasoner.getInstance(); if (r.translate(getSelectedHouse()) != null) MapUtils.zoomTo(r.translate(getSelectedHouse())); else ConflictResolver.getInstance().focusElement(getSelectedHouse()); } } //GEN-LAST:event_houseListClicked // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JList<House> houseList; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JCheckBox keepOddityCheckBox; private javax.swing.JPanel mainPanel; private javax.swing.JButton relocateButton; private javax.swing.JComboBox<ElementWithHouses> streetComboBox; // End of variables declaration//GEN-END:variables @Override public void elementChanged(AddressElement elem) { houseModel.notifyAllListeners(); } @Override public void primitiveChanged(OsmPrimitive prim) {} @Override public void resonerReseted() {} //============================================================================== private static class StreetListRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value instanceof Street) { setFont(getFont().deriveFont(Font.PLAIN)); setText(((Street) value).getName()); } else if (value instanceof ElementWithHouses) { setFont(getFont().deriveFont(Font.BOLD)); setText("[" + ((ElementWithHouses) value).getName() + "]"); } return c; } } //============================================================================== private static class HouseListRenderer extends DefaultListCellRenderer { Font plainFont = null; Font boldFont = null; ImageIcon envelopeNormIcon = ImageProvider.get("envelope-closed-small.png"); ImageIcon envelopeStarIcon = ImageProvider.get("envelope-closed-star-small.png"); ImageIcon envelopeExclIcon = ImageProvider.get("envelope-closed-exclamation-small.png"); @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (plainFont == null) plainFont = getFont().deriveFont(Font.PLAIN); if (boldFont == null) boldFont = getFont().deriveFont(Font.BOLD); if (value instanceof House) { House house = (House) value; setIcon(envelopeNormIcon); setFont(plainFont); if (Reasoner.getInstance().inConflict(house)) setIcon(envelopeExclIcon); else if (Reasoner.getInstance().translate(house) == null) { setIcon(envelopeStarIcon); setFont(boldFont); } setText(house.getName()); } return c; } } //============================================================================== private static class AllStreetProvider extends ElementWithHouses { AllStreetProvider() { super("všechny domy"); } @Override public void setHouses(List<House> houses) { this.houses = houses; } } private class FreeStreetProvider extends ElementWithHouses implements ReasonerListener { FreeStreetProvider() { super("nepřiřazené domy"); Reasoner.getInstance().addListener(this); } @Override public void resonerReseted() { houses.clear(); } @Override public void primitiveChanged(OsmPrimitive prim) {} @Override public void elementChanged(AddressElement elem) { if (!(elem instanceof House)) return; House house = (House) elem; int index = Collections.binarySearch(houses, house); if (Reasoner.getInstance().translate(house) != null) { if (index >= 0) houses.remove(index); } else { if (index < 0) houses.add(-index-1, house); } houseModel.notifyAllListeners(); } } //============================================================================== private class StreetListModel extends HalfCookedComboBoxModel<ElementWithHouses> { private ElementWithHouses selected = null; private ElementWithStreets parent = null; private List<ElementWithHouses> metaElem = new ArrayList<>(); StreetListModel() { metaElem.add(null); metaElem.add(new AllStreetProvider()); metaElem.add(new FreeStreetProvider()); } @Override public int getSize() { if (parent == null) return 0; return parent.getStreets().size() + metaElem.size(); } public void setParent(ElementWithStreets parent) { if (parent == null) return; this.selected = parent; this.parent = parent; metaElem.set(0, parent); metaElem.get(1).setHouses(parent.getAllHouses()); notifyAllListeners(); } @Override public ElementWithHouses getElementAt(int index) { if (parent == null) return null; if (index < metaElem.size()) return metaElem.get(index); index -= metaElem.size(); if (index < parent.getStreets().size()) return parent.getStreets().get(index); return null; } @Override public void setSelectedItem(Object anItem) { assert anItem instanceof ElementWithHouses; selected = (ElementWithHouses) anItem; houseModel.notifyAllListeners(); } @Override public Object getSelectedItem() { return selected; } } //============================================================================== private class HouseListModel extends HalfCookedListModel<House> implements ReasonerListener { HouseListModel() { Reasoner.getInstance().addListener(this); } @Override public int getSize() { if (streetComboBox.getSelectedItem() == null) return 0; ElementWithHouses selected = (ElementWithHouses) streetComboBox.getSelectedItem(); return selected.getHouses().size(); } @Override public House getElementAt(int index) { if (streetComboBox.getSelectedItem() == null) return null; ElementWithHouses selected = (ElementWithHouses) streetComboBox.getSelectedItem(); if ((index < 0) || (index >= selected.getHouses().size())) return null; return selected.getHouses().get(index); } @Override public void primitiveChanged(OsmPrimitive prim) {} @Override public void elementChanged(AddressElement elem) { notifyAllListeners(); } @Override public void resonerReseted() { notifyAllListeners(); } } }