// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.osmrec; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.osm.IRelation; 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.Tag; import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.dialogs.ToggleDialog; import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; import org.openstreetmap.josm.tools.Shortcut; /** * This class is a modification of the PropertiesDialog for the OSMRec. * * * This dialog displays the tags of the current selected primitives. * * If no object is selected, the dialog list is empty. * If only one is selected, all tags of this object are selected. * If more than one object are selected, the sum of all tags are displayed. If the * different objects share the same tag, the shared value is displayed. If they have * different values, all of them are put in a combo box and the string "<different>" * is displayed in italic. * * Below the list, the user can click on an add, modify and delete tag button to * edit the table selection value. * * The command is applied to all selected entries. * * @author imi * @author nkaragiannakis */ public class OSMRecToggleDialog extends ToggleDialog implements SelectionChangedListener, DataSetListenerAdapter.Listener { /** * The tag data of selected objects. */ private final DefaultTableModel tagData = new ReadOnlyTableModel(); /** * The membership data of selected objects. */ private final DefaultTableModel membershipData = new ReadOnlyTableModel(); /** * The tags table. */ private final JTable tagTable = new JTable(tagData); /** * The membership table. */ private final JTable membershipTable = new JTable(membershipData); /** JPanel containing both previous tables */ private final JPanel bothTables = new JPanel(); private final transient Map<String, Map<String, Integer>> valueCount = new TreeMap<>(); /** * This sub-object is responsible for all adding and editing of tags */ private final transient OSMRecPluginHelper editHelper = new OSMRecPluginHelper(tagData, valueCount); private final AddAction addAction = new AddAction(); private final EditActionTrain editAction = new EditActionTrain(); // private final DeleteAction deleteAction = new DeleteAction(); // private final JosmAction[] josmActions = new JosmAction[]{addAction, editAction, deleteAction}; /** * The Add button (needed to be able to disable it) */ private final SideButton btnAdd = new SideButton(addAction); /** * The Edit button (needed to be able to disable it) */ private final SideButton btnEdit = new SideButton(editAction); /** * Text to display when nothing selected. */ private final JLabel selectSth = new JLabel("<html><p>" + tr("Select objects or create new objects and get recommendation.") + "</p></html>"); // <editor-fold defaultstate="collapsed" desc="Dialog construction and helper methods"> /** * Create a new OSMRecToggleDialog */ public OSMRecToggleDialog() { super(tr("Tags/Memberships"), "propertiesdialog", tr("Tags for selected objects."), Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Tags/Memberships")), KeyEvent.VK_P, Shortcut.ALT_SHIFT), 150, true); System.out.println("cleaning test.."); bothTables.setLayout(new GridBagLayout()); bothTables.setVisible(false); //my // Let the actions know when selection in the tables change tagTable.getSelectionModel().addListSelectionListener(editAction); membershipTable.getSelectionModel().addListSelectionListener(editAction); JScrollPane scrollPane = (JScrollPane) createLayout(bothTables, true, Arrays.asList(this.btnAdd, this.btnEdit)); MouseClickWatch mouseClickWatch = new MouseClickWatch(); tagTable.addMouseListener(mouseClickWatch); membershipTable.addMouseListener(mouseClickWatch); scrollPane.addMouseListener(mouseClickWatch); editHelper.loadTagsIfNeeded(); } /** * This simply fires up an {@link RelationEditor} for the relation shown; everything else * is the editor's business. * * @param row position */ private void editMembership(int row) { Relation relation = (Relation) membershipData.getValueAt(row, 0); Main.map.relationListDialog.selectRelation(relation); } private int findRow(TableModel model, Object value) { for (int i = 0; i < model.getRowCount(); i++) { if (model.getValueAt(i, 0).equals(value)) return i; } return -1; } /** * Update selection status, call @{link #selectionChanged} function. */ private void updateSelection() { // Parameter is ignored in this class selectionChanged(null); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Event listeners methods"> @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { if (tagTable == null) return; // selection changed may be received in base class constructor before init if (tagTable.getCellEditor() != null) { tagTable.getCellEditor().cancelCellEditing(); } // Ignore parameter as we do not want to operate always on real selection here, especially in draw mode Collection<OsmPrimitive> newSel = Main.main.getInProgressSelection(); if (newSel == null) { newSel = Collections.<OsmPrimitive>emptyList(); } String selectedTag; Relation selectedRelation = null; selectedTag = editHelper.getChangedKey(); // select last added or last edited key by default if (selectedTag == null && tagTable.getSelectedRowCount() == 1) { selectedTag = (String) tagData.getValueAt(tagTable.getSelectedRow(), 0); } if (membershipTable.getSelectedRowCount() == 1) { selectedRelation = (Relation) membershipData.getValueAt(membershipTable.getSelectedRow(), 0); } // re-load tag data tagData.setRowCount(0); final Map<String, String> tags = new HashMap<>(); valueCount.clear(); for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) { int count = 0; for (Entry<String, Integer> e1 : e.getValue().entrySet()) { count += e1.getValue(); } if (count < newSel.size()) { e.getValue().put("", newSel.size() - count); } tagData.addRow(new Object[]{e.getKey(), e.getValue()}); tags.put(e.getKey(), e.getValue().size() == 1 ? e.getValue().keySet().iterator().next() : tr("<different>")); } membershipData.setRowCount(0); Map<Relation, MemberInfo> roles = new HashMap<>(); for (OsmPrimitive primitive: newSel) { for (OsmPrimitive ref: primitive.getReferrers(true)) { if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) { Relation r = (Relation) ref; MemberInfo mi = roles.get(r); if (mi == null) { mi = new MemberInfo(newSel); } roles.put(r, mi); int i = 1; for (RelationMember m : r.getMembers()) { if (m.getMember() == primitive) { mi.add(m, i); } ++i; } } } } List<Relation> sortedRelations = new ArrayList<>(roles.keySet()); Collections.sort(sortedRelations, new Comparator<Relation>() { @Override public int compare(Relation o1, Relation o2) { int comp = Boolean.valueOf(o1.isDisabledAndHidden()).compareTo(o2.isDisabledAndHidden()); return comp != 0 ? comp : DefaultNameFormatter.getInstance().getRelationComparator().compare(o1, o2); } }); for (Relation r: sortedRelations) { membershipData.addRow(new Object[]{r, roles.get(r)}); } membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0); membershipTable.setVisible(membershipData.getRowCount() > 0); boolean hasSelection = !newSel.isEmpty(); boolean hasTags = hasSelection && tagData.getRowCount() > 0; boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0; addAction.setEnabled(hasSelection); //editAction.setEnabled(hasTags || hasMemberships); editAction.setEnabled(true); tagTable.setVisible(hasTags); tagTable.getTableHeader().setVisible(hasTags); selectSth.setVisible(!hasSelection); int selectedIndex; if (selectedTag != null && (selectedIndex = findRow(tagData, selectedTag)) != -1) { tagTable.changeSelection(selectedIndex, 0, false, false); } else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) { membershipTable.changeSelection(selectedIndex, 0, false, false); } else if (hasTags) { tagTable.changeSelection(0, 0, false, false); } else if (hasMemberships) { membershipTable.changeSelection(0, 0, false, false); } } @Override public void processDatasetEvent(AbstractDatasetChangedEvent event) { updateSelection(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Methods that are called by plugins to extend fuctionality "> /** * Returns the selected tag. * @return The current selected tag */ @SuppressWarnings("unchecked") public Tag getSelectedProperty() { int row = tagTable.getSelectedRow(); if (row == -1) return null; Map<String, Integer> map = (TreeMap<String, Integer>) tagData.getValueAt(row, 1); return new Tag( tagData.getValueAt(row, 0).toString(), map.size() > 1 ? "" : map.keySet().iterator().next()); } /** * Returns the selected relation membership. * @return The current selected relation membership */ public IRelation getSelectedMembershipRelation() { int row = membershipTable.getSelectedRow(); return row > -1 ? (IRelation) membershipData.getValueAt(row, 0) : null; } // </editor-fold> /** * Class that watches for mouse clicks * @author imi */ public class MouseClickWatch extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() < 2) { // single click, clear selection in other table not clicked in if (e.getSource() == tagTable) { membershipTable.clearSelection(); } else if (e.getSource() == membershipTable) { tagTable.clearSelection(); } } else if (e.getSource() == tagTable) { // double click, edit or add tag int row = tagTable.rowAtPoint(e.getPoint()); if (row > -1) { boolean focusOnKey = tagTable.columnAtPoint(e.getPoint()) == 0; editHelper.editTag(row, focusOnKey); } else { editHelper.addTag(); } } else if (e.getSource() == membershipTable) { int row = membershipTable.rowAtPoint(e.getPoint()); if (row > -1) { editMembership(row); } } else { editHelper.addTag(); } } @Override public void mousePressed(MouseEvent e) { if (e.getSource() == tagTable) { membershipTable.clearSelection(); } else if (e.getSource() == membershipTable) { tagTable.clearSelection(); } } } static class MemberInfo { private List<RelationMember> role = new ArrayList<>(); private Set<OsmPrimitive> members = new HashSet<>(); private List<Integer> position = new ArrayList<>(); private Iterable<OsmPrimitive> selection; private String positionString; private String roleString; MemberInfo(Iterable<OsmPrimitive> selection) { this.selection = selection; } void add(RelationMember r, Integer p) { role.add(r); members.add(r.getMember()); position.add(p); } @Override public String toString() { return "MemberInfo{" + "roles='" + roleString + '\'' + ", positions='" + positionString + '\'' + '}'; } } /** * Class that allows fast creation of read-only table model with String columns */ public static class ReadOnlyTableModel extends DefaultTableModel { @Override public boolean isCellEditable(int row, int column) { return false; } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } } /** * Action handling add button press in properties dialog. */ class AddAction extends JosmAction { AddAction() { super(tr("Add Recommendation"), /* ICON() */ "dialogs/add", tr("Add a recommended key/value pair to your object"), Shortcut.registerShortcut("properties:add", tr("Add Tag"), KeyEvent.VK_A, Shortcut.ALT), false); } @Override public void actionPerformed(ActionEvent e) { editHelper.addTag(); btnAdd.requestFocusInWindow(); } } /** * Action handling edit button press in properties dialog. * training process dialog/configuration */ class EditActionTrain extends JosmAction implements ListSelectionListener { EditActionTrain() { super(tr("Train a Model"), /* ICON() */ "dialogs/fix", tr("Start the training engine!"), Shortcut.registerShortcut("properties:edit", tr("Edit Tags"), KeyEvent.VK_S, Shortcut.ALT), false); setEnabled(true); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { if (!isEnabled()) return; if (tagTable.getSelectedRowCount() == 1) { int row = tagTable.getSelectedRow(); editHelper.editTag(row, false); } else if (membershipTable.getSelectedRowCount() == 1) { int row = membershipTable.getSelectedRow(); editHelper.editTag(row, false); } else { editHelper.editTag(1, false); } } @Override protected void updateEnabledState() { setEnabled(true); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } }