From 2b77243b1a6c750ca283e192efa2caf7d8e86ae4 Mon Sep 17 00:00:00 2001
From: Simon Legner <Simon.Legner@gmail.com>
Date: Sun, 18 Oct 2015 20:39:06 +0200
Subject: [PATCH 2/2] fix #9463 - Properties dialog: add text field for tag
 filtering

---
 .../gui/dialogs/properties/PropertiesDialog.java   | 38 ++++++++++-
 .../dialogs/properties/SearchBasedRowFilter.java   | 78 ++++++++++++++++++++++
 2 files changed, 114 insertions(+), 2 deletions(-)
 create mode 100644 src/org/openstreetmap/josm/gui/dialogs/properties/SearchBasedRowFilter.java

diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
index 6142c9f..26d18dc 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
@@ -8,11 +8,14 @@
 import java.awt.Font;
 import java.awt.GridBagLayout;
 import java.awt.Point;
+import java.awt.TextField;
 import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -41,12 +44,14 @@
 import javax.swing.JTable;
 import javax.swing.KeyStroke;
 import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 import javax.swing.table.DefaultTableCellRenderer;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableColumnModel;
 import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.JosmAction;
@@ -56,6 +61,7 @@
 import org.openstreetmap.josm.actions.relation.SelectMembersAction;
 import org.openstreetmap.josm.actions.relation.SelectRelationAction;
 import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
+import org.openstreetmap.josm.actions.search.SearchCompiler;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.ChangePropertyCommand;
 import org.openstreetmap.josm.command.Command;
@@ -67,6 +73,7 @@
 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.Tagged;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
 import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
@@ -84,11 +91,14 @@
 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
+import org.openstreetmap.josm.gui.widgets.CompileSearchTextDecorator;
+import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -127,7 +137,9 @@
     /**
      * The tag data of selected objects.
      */
-    private final DefaultTableModel tagData = new ReadOnlyTableModel();
+    private final ReadOnlyTableModel tagData = new ReadOnlyTableModel();
+    private final TableRowSorter<ReadOnlyTableModel> tagRowSorter = new TableRowSorter<>(tagData);
+    private final JosmTextField tagTableFilter;
 
     /**
      * The membership data of selected objects.
@@ -242,6 +254,8 @@ public PropertiesDialog() {
         setupMembershipMenu();
         buildMembershipTable();
 
+        tagTableFilter = setupFilter();
+
         // combine both tables and wrap them in a scrollPane
         boolean top = Main.pref.getBoolean("properties.presets.top", true);
         bothTables.setLayout(new GridBagLayout());
@@ -251,6 +265,7 @@ public PropertiesDialog() {
             bothTables.add(pluginHook, GBC.eol().insets(0, 1, 1, 1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon));
         }
         bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10));
+        bothTables.add(tagTableFilter, GBC.eol().fill(GBC.HORIZONTAL));
         bothTables.add(tagTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
         bothTables.add(tagTable, GBC.eol().fill(GBC.BOTH));
         bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
@@ -293,6 +308,7 @@ private void buildTagsTable() {
         PropertiesCellRenderer cellRenderer = new PropertiesCellRenderer();
         tagTable.getColumnModel().getColumn(0).setCellRenderer(cellRenderer);
         tagTable.getColumnModel().getColumn(1).setCellRenderer(cellRenderer);
+        tagTable.setRowSorter(tagRowSorter);
     }
 
     private void buildMembershipTable() {
@@ -455,6 +471,10 @@ private void setupTagsMenu() {
         tagTable.addMouseListener(new PopupMenuLauncher(tagMenu));
     }
 
+    public void setFilter(final SearchCompiler.Match filter) {
+        this.tagRowSorter.setRowFilter(new SearchBasedRowFilter(filter));
+    }
+
     /**
      * Assigns all needed keys like Enter and Spacebar to most important actions.
      */
@@ -499,6 +519,19 @@ private void setupKeyboardShortcuts() {
         getActionMap().put("onHelp", helpAction);
     }
 
+    private JosmTextField setupFilter() {
+        final JosmTextField f = new DisableShortcutsOnFocusGainedTextField();
+        f.setToolTipText(tr("Tag filter"));
+        final CompileSearchTextDecorator decorator = CompileSearchTextDecorator.decorate(f);
+        f.addPropertyChangeListener("filter", new PropertyChangeListener() {
+            @Override
+            public void propertyChange(PropertyChangeEvent evt) {
+                setFilter(decorator.getMatch());
+            }
+        });
+        return f;
+    }
+
      /**
      * This simply fires up an {@link RelationEditor} for the relation shown; everything else
      * is the editor's business.
@@ -685,6 +718,7 @@ public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
         deleteAction.setEnabled(hasTags || hasMemberships);
         tagTable.setVisible(hasTags);
         tagTable.getTableHeader().setVisible(hasTags);
+        tagTableFilter.setVisible(hasTags);
         selectSth.setVisible(!hasSelection);
         pluginHook.setVisible(hasSelection);
 
diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/SearchBasedRowFilter.java b/src/org/openstreetmap/josm/gui/dialogs/properties/SearchBasedRowFilter.java
new file mode 100644
index 0000000..f14c527
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/dialogs/properties/SearchBasedRowFilter.java
@@ -0,0 +1,78 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.properties;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.swing.RowFilter;
+import javax.swing.table.TableModel;
+
+import org.openstreetmap.josm.actions.search.SearchCompiler;
+import org.openstreetmap.josm.data.osm.Tagged;
+
+public class SearchBasedRowFilter extends RowFilter<TableModel, Integer> {
+
+    final SearchCompiler.Match filter;
+
+    public SearchBasedRowFilter(SearchCompiler.Match filter) {
+        this.filter = filter;
+    }
+
+    @Override
+    public boolean include(Entry entry) {
+        final String key = entry.getStringValue(0);
+        final String value = entry.getStringValue(1);
+        return filter.match(new OneKeyValue(key, value));
+    }
+
+    static class OneKeyValue implements Tagged {
+        private final String key;
+        private final String value;
+
+        public OneKeyValue(String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public void setKeys(Map<String, String> keys) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Map<String, String> getKeys() {
+            return Collections.singletonMap(key, value);
+        }
+
+        @Override
+        public void put(String key, String value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String get(String k) {
+            return key.equals(k) ? value : null;
+        }
+
+        @Override
+        public void remove(String key) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean hasKeys() {
+            return true;
+        }
+
+        @Override
+        public Collection<String> keySet() {
+            return Collections.singleton(key);
+        }
+
+        @Override
+        public void removeAll() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
-- 
2.6.1

