// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.utilsplugin2.multitagger;
import static org.openstreetmap.josm.tools.I18n.marktr;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
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.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.AutoScaleAction;
import org.openstreetmap.josm.actions.search.SearchAction;
import org.openstreetmap.josm.data.SelectionChangedListener;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.preferences.ColorProperty;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
import org.openstreetmap.josm.gui.util.HighlightHelper;
import org.openstreetmap.josm.gui.util.TableHelper;
import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.WindowGeometry;
/**
* Dialog for editing multiple object tags
*/
public class MultiTagDialog extends ExtendedDialog implements SelectionChangedListener {
private final MultiTaggerTableModel tableModel = new MultiTaggerTableModel();
private final JTable tbl;
private final HighlightHelper highlightHelper = new HighlightHelper();
private final HistoryComboBox cbTagSet = new HistoryComboBox();
private List<OsmPrimitive> currentSelection;
private static final String HISTORY_KEY = "utilsplugin2.multitaghistory";
String[] defaultHistory = {"addr:street, addr:housenumber, building, ${area}",
"highway, name, ${id}, ${length}",
"name name:en name:ru name:de"};
public MultiTagDialog() {
super(Main.parent, tr("Edit tags"), new String[]{tr("Ok"), tr("Cancel")}, false);
JPanel pnl = new JPanel(new GridBagLayout());
tbl = createTable();
cbTagSet.addItemListener(tagSetChanger);
cbTagSet.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), "applyTagSet");
cbTagSet.getActionMap().put("applyTagSet", tagSetChanger);
tbl.addMouseListener(new PopupMenuLauncher(createPopupMenu()));
pnl.add(cbTagSet, GBC.std().fill(GBC.HORIZONTAL));
pnl.add(new JButton(new DeleteFromHistoryAction()), GBC.std());
pnl.add(new JButton(new FindMatchingAction()), GBC.std());
final JToggleButton jt = new JToggleButton("", ImageProvider.get("restart"), true);
jt.setToolTipText(tr("Sync with JOSM selection"));
jt.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
tableModel.setWatchSelection(jt.isSelected());
}
});
pnl.add(jt, GBC.eol());
pnl.add(createTypeFilterPanel(), GBC.eol().fill(GBC.HORIZONTAL));
pnl.add(tbl.getTableHeader(), GBC.eop().fill(GBC.HORIZONTAL));
pnl.add(new JScrollPane(tbl), GBC.eol().fill(GBC.BOTH));
setContent(pnl);
setDefaultButton(-1);
loadHistory();
WindowGeometry defaultGeometry = WindowGeometry.centerInWindow(Main.parent, new Dimension(500, 500));
setRememberWindowGeometry(getClass().getName() + ".geometry", defaultGeometry);
}
private JTable createTable() {
JTable t = new JTable(tableModel);
tableModel.setTable(t);
t.setFillsViewportHeight(true);
t.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
t.addMouseListener(tableMouseAdapter);
t.setRowSelectionAllowed(true);
t.setColumnSelectionAllowed(true);
t.setDefaultRenderer(OsmPrimitiveType.class, new PrimitiveTypeIconRenderer());
t.setDefaultRenderer(String.class, new ColoredRenderer());
t.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
t.getSelectionModel().addListSelectionListener(selectionListener);
return t;
}
private JPanel createTypeFilterPanel() {
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
for (final OsmPrimitiveType type: OsmPrimitiveType.values()) {
final JToggleButton jt = new JToggleButton("", ImageProvider.get(type), true);
jt.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (jt.isSelected()) tableModel.shownTypes.add(type); else tableModel.shownTypes.remove(type);
tableModel.updateData(Main.getLayerManager().getEditDataSet().getSelected());
}
});
ImageProvider.get(type);
p.add(jt);
}
return p;
}
private void loadHistory() {
List<String> cmtHistory = new LinkedList<>(
Main.pref.getCollection(HISTORY_KEY, Arrays.asList(defaultHistory)));
Collections.reverse(cmtHistory);
cbTagSet.setPossibleItems(cmtHistory);
String s = cmtHistory.get(cmtHistory.size()-1);
cbTagSet.setText(s);
specifyTagSet(s);
}
@Override
protected void buttonAction(int buttonIndex, ActionEvent evt) {
highlightHelper.clear();
tbl.getSelectionModel().removeListSelectionListener(selectionListener);
super.buttonAction(buttonIndex, evt);
}
@Override
public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
tableModel.selectionChanged(newSelection);
}
/*private OsmPrimitive getSelectedPrimitive() {
int idx = tbl.getSelectedRow();
if (idx>= 0) {
return tableModel.getPrimitiveAt(tbl.convertRowIndexToModel(idx));
} else {
return null;
}
}*/
private final MouseAdapter tableMouseAdapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 1 && Main.isDisplayingMapView()) {
AutoScaleAction.zoomTo(currentSelection);
}
}
};
private final ListSelectionListener selectionListener = new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
currentSelection = getSelectedPrimitives();
if (currentSelection != null && Main.isDisplayingMapView()) {
if (highlightHelper.highlightOnly(currentSelection)) {
Main.map.mapView.repaint();
}
}
}
};
public List<OsmPrimitive> getSelectedPrimitives() {
ArrayList<OsmPrimitive> sel = new ArrayList<>(100);
for (int idx: tbl.getSelectedRows()) {
sel.add(tableModel.getPrimitiveAt(tbl.convertRowIndexToModel(idx)));
}
return sel;
}
private final TagSetChanger tagSetChanger = new TagSetChanger();
private void initAutocompletion() {
OsmDataLayer l = Main.getLayerManager().getEditLayer();
AutoCompletionManager autocomplete = l.data.getAutoCompletionManager();
for (int i = 0; i < tableModel.mainTags.length; i++) {
if (tableModel.isSpecialTag[i]) continue;
AutoCompletingTextField tf = new AutoCompletingTextField(0, false);
AutoCompletionList acList = new AutoCompletionList();
autocomplete.populateWithTagValues(acList, tableModel.mainTags[i]);
tf.setAutoCompletionList(acList);
tbl.getColumnModel().getColumn(i+1).setCellEditor(tf);
}
}
private JPopupMenu createPopupMenu() {
JPopupMenu menu = new JPopupMenu();
menu.add(new AbstractAction(tr("Zoom to objects"), ImageProvider.get("dialogs/autoscale", "selection")) {
@Override
public void actionPerformed(ActionEvent e) {
if (Main.isDisplayingMapView()) {
AutoScaleAction.zoomTo(currentSelection);
}
}
});
menu.add(new AbstractAction(tr("Select"), ImageProvider.get("dialogs", "select")) {
@Override
public void actionPerformed(ActionEvent e) {
Main.getLayerManager().getEditDataSet().setSelected(getSelectedPrimitives());
}
});
menu.add(new AbstractAction(tr("Remove tag"), ImageProvider.get("dialogs", "delete")) {
@Override
public void actionPerformed(ActionEvent e) {
tableModel.setAutoCommit(false);
for (int c: tbl.getSelectedColumns()) {
for (int r: tbl.getSelectedRows()) {
tableModel.setValueAt("", tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
}
}
tableModel.commit(tr("Delete tags from multiple objects"));
tableModel.setAutoCommit(true);
}
});
menu.add(new AbstractAction(tr("Duplicate tags from the first"), ImageProvider.get("copy")) {
@Override
public void actionPerformed(ActionEvent e) {
tableModel.setAutoCommit(false);
for (int c: tbl.getSelectedColumns()) {
if (c == 0 || tableModel.isSpecialTag[c-1]) continue;
boolean first = true;
String value = "";
for (int r: tbl.getSelectedRows()) {
if (first) {
value = (String) tableModel.getValueAt(tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
}
first = false;
tableModel.setValueAt(value, tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
}
}
tableModel.commit(tr("Set tags for multiple objects"));
tableModel.setAutoCommit(true);
}
});
return menu;
}
private static class PrimitiveTypeIconRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (c instanceof JLabel) {
((JLabel) c).setIcon(ImageProvider.get((OsmPrimitiveType) value));
((JLabel) c).setText("");
}
return c;
}
}
private class DeleteFromHistoryAction extends AbstractAction {
DeleteFromHistoryAction() {
super("", ImageProvider.get("dialogs", "delete"));
putValue(SHORT_DESCRIPTION, tr("Delete from history"));
}
@Override
public void actionPerformed(ActionEvent e) {
String txt = cbTagSet.getText();
System.out.println(txt);
List<String> history = cbTagSet.getHistory();
history.remove(txt);
if (history.isEmpty()) {
history = Arrays.asList(defaultHistory);
}
Main.pref.putCollection(HISTORY_KEY, history);
loadHistory();
}
}
private class FindMatchingAction extends AbstractAction {
FindMatchingAction() {
super("", ImageProvider.get("dialogs", "search"));
putValue(SHORT_DESCRIPTION, tr("Find primitives with these tags"));
}
@Override
public void actionPerformed(ActionEvent e) {
SearchAction.search(tableModel.getSearchExpression(), SearchAction.SearchMode.replace);
}
}
private class TagSetChanger extends AbstractAction implements ItemListener {
String oldTags;
@Override
public void itemStateChanged(ItemEvent e) {
// skip text-changing enevts, we need only combobox-selecting ones
if (cbTagSet.getSelectedIndex() < 0) return;
actionPerformed(null);
}
@Override
public void actionPerformed(ActionEvent e) {
String s = cbTagSet.getText();
if (s == null || s.isEmpty() || s.equals(oldTags)) return;
oldTags = s;
cbTagSet.addCurrentItemToHistory();
Main.pref.putCollection(HISTORY_KEY, cbTagSet.getHistory());
specifyTagSet(s);
}
}
private void specifyTagSet(String s) {
Main.info("Multitagger tags="+s);
tableModel.setupColumnsFromText(s);
tbl.createDefaultColumnsFromModel();
tbl.setAutoCreateRowSorter(true);
tbl.getColumnModel().getColumn(0).setMaxWidth(20);
for (int i = 1; i < tableModel.getColumnCount(); i++) {
TableHelper.adjustColumnWidth(tbl, i, 100);
}
initAutocompletion();
tableModel.fireTableDataChanged();
}
class ColoredRenderer extends DefaultTableCellRenderer {
private final Color highlightColor = new ColorProperty(
marktr("Multitag Background: highlight"), new Color(255, 255, 200)).get();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean
isSelected, boolean hasFocus, int row, int column) {
int row1 = tbl.convertRowIndexToModel(row);
JLabel label = (JLabel) super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
if (tbl.isRowSelected(row1) && !tbl.isColumnSelected(column)) {
label.setBackground(highlightColor);
} else {
if (isSelected) {
label.setBackground(UIManager.getColor("Table.selectionBackground"));
} else {
label.setBackground(UIManager.getColor("Table.background"));
}
}
return label;
}
}
}