Index: /trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java	(revision 12400)
+++ /trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java	(revision 12400)
@@ -0,0 +1,414 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
+import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.widgets.OSDLabel;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * The model that is used both for auto and manual filters.
+ * @since 12400
+ */
+public class FilterModel {
+
+    /**
+     * number of primitives that are disabled but not hidden
+     */
+    private int disabledCount;
+    /**
+     * number of primitives that are disabled and hidden
+     */
+    private int disabledAndHiddenCount;
+    /**
+     * true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process
+     */
+    private boolean changed;
+
+    /**
+     * Constructs a new {@code FilterTableModel}.
+     */
+    public FilterModel() {
+    }
+
+    private final transient List<Filter> filters = new LinkedList<>();
+    private final transient FilterMatcher filterMatcher = new FilterMatcher();
+
+    private void updateFilterMatcher() {
+        filterMatcher.reset();
+        for (Filter filter : filters) {
+            try {
+                filterMatcher.add(filter);
+            } catch (ParseError e) {
+                Main.error(e);
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        tr("<html>Error in filter <code>{0}</code>:<br>{1}",
+                                Utils.escapeReservedCharactersHTML(Utils.shortenString(filter.text, 80)),
+                                Utils.escapeReservedCharactersHTML(e.getMessage())),
+                        tr("Error in filter"),
+                        JOptionPane.ERROR_MESSAGE);
+                filter.enable = false;
+            }
+        }
+    }
+
+    /**
+     * Initializes the model from preferences.
+     * @param prefEntry preference key
+     */
+    public void loadPrefs(String prefEntry) {
+        List<FilterPreferenceEntry> entries = Main.pref.getListOfStructs(prefEntry, null, FilterPreferenceEntry.class);
+        if (entries != null) {
+            for (FilterPreferenceEntry e : entries) {
+                filters.add(new Filter(e));
+            }
+            updateFilterMatcher();
+        }
+    }
+
+    /**
+     * Saves the model to preferences.
+     * @param prefEntry preferences key
+     */
+    public void savePrefs(String prefEntry) {
+        Collection<FilterPreferenceEntry> entries = new ArrayList<>();
+        for (Filter flt : filters) {
+            entries.add(flt.getPreferenceEntry());
+        }
+        Main.pref.putListOfStructs(prefEntry, entries, FilterPreferenceEntry.class);
+    }
+
+    /**
+     * Runs the filters on the current edit data set.
+     */
+    public void executeFilters() {
+        DataSet ds = Main.getLayerManager().getEditDataSet();
+        changed = false;
+        if (ds == null) {
+            disabledAndHiddenCount = 0;
+            disabledCount = 0;
+            changed = true;
+        } else {
+            final Collection<OsmPrimitive> deselect = new HashSet<>();
+
+            ds.beginUpdate();
+            try {
+
+                final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives();
+
+                changed = FilterWorker.executeFilters(all, filterMatcher);
+
+                disabledCount = 0;
+                disabledAndHiddenCount = 0;
+                // collect disabled and selected the primitives
+                for (OsmPrimitive osm : all) {
+                    if (osm.isDisabled()) {
+                        disabledCount++;
+                        if (osm.isSelected()) {
+                            deselect.add(osm);
+                        }
+                        if (osm.isDisabledAndHidden()) {
+                            disabledAndHiddenCount++;
+                        }
+                    }
+                }
+                disabledCount -= disabledAndHiddenCount;
+            } finally {
+                ds.endUpdate();
+            }
+
+            if (!deselect.isEmpty()) {
+                ds.clearSelection(deselect);
+            }
+        }
+        if (changed) {
+            updateMap();
+        }
+    }
+
+    /**
+     * Runs the filter on a list of primitives that are part of the edit data set.
+     * @param primitives The primitives
+     */
+    public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
+        DataSet ds = Main.getLayerManager().getEditDataSet();
+        if (ds == null)
+            return;
+
+        changed = false;
+        List<OsmPrimitive> deselect = new ArrayList<>();
+
+        ds.beginUpdate();
+        try {
+            for (int i = 0; i < 2; i++) {
+                for (OsmPrimitive primitive: primitives) {
+
+                    if (i == 0 && primitive instanceof Node) {
+                        continue;
+                    }
+
+                    if (i == 1 && !(primitive instanceof Node)) {
+                        continue;
+                    }
+
+                    if (primitive.isDisabled()) {
+                        disabledCount--;
+                    }
+                    if (primitive.isDisabledAndHidden()) {
+                        disabledAndHiddenCount--;
+                    }
+                    changed |= FilterWorker.executeFilters(primitive, filterMatcher);
+                    if (primitive.isDisabled()) {
+                        disabledCount++;
+                    }
+                    if (primitive.isDisabledAndHidden()) {
+                        disabledAndHiddenCount++;
+                    }
+
+                    if (primitive.isSelected() && primitive.isDisabled()) {
+                        deselect.add(primitive);
+                    }
+                }
+            }
+        } finally {
+            ds.endUpdate();
+        }
+
+        if (!deselect.isEmpty()) {
+            ds.clearSelection(deselect);
+        }
+        if (changed) {
+            updateMap();
+        }
+    }
+
+    private static void updateMap() {
+        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
+        if (editLayer != null) {
+            editLayer.invalidate();
+        }
+    }
+
+    /**
+     * Clears all filtered flags from all primitives in the dataset
+     */
+    public void clearFilterFlags() {
+        DataSet ds = Main.getLayerManager().getEditDataSet();
+        if (ds != null) {
+            FilterWorker.clearFilterFlags(ds.allPrimitives());
+        }
+        disabledCount = 0;
+        disabledAndHiddenCount = 0;
+    }
+
+    /**
+     * Removes all filters from this model.
+     */
+    public void clearFilters() {
+        filters.clear();
+        updateFilterMatcher();
+    }
+
+    /**
+     * Adds a new filter to the filter list.
+     * @param filter The new filter
+     * @return true (as specified by {@link Collection#add})
+     */
+    public boolean addFilter(Filter filter) {
+        filters.add(filter);
+        updateFilterMatcher();
+        return true;
+    }
+
+    /**
+     * Moves down the filter in the given row.
+     * @param rowIndex The filter row
+     * @return true if the filter has been moved down
+     */
+    public boolean moveDownFilter(int rowIndex) {
+        if (rowIndex >= filters.size() - 1)
+            return false;
+        filters.add(rowIndex + 1, filters.remove(rowIndex));
+        updateFilterMatcher();
+        return true;
+    }
+
+    /**
+     * Moves up the filter in the given row
+     * @param rowIndex The filter row
+     * @return true if the filter has been moved up
+     */
+    public boolean moveUpFilter(int rowIndex) {
+        if (rowIndex == 0)
+            return false;
+        filters.add(rowIndex - 1, filters.remove(rowIndex));
+        updateFilterMatcher();
+        return true;
+    }
+
+    /**
+     * Removes the filter that is displayed in the given row
+     * @param rowIndex The index of the filter to remove
+     * @return the filter previously at the specified position
+     */
+    public Filter removeFilter(int rowIndex) {
+        Filter result = filters.remove(rowIndex);
+        updateFilterMatcher();
+        return result;
+    }
+
+    /**
+     * Sets/replaces the filter for a given row.
+     * @param rowIndex The row index
+     * @param filter The filter that should be placed in that row
+     * @return the filter previously at the specified position
+     */
+    public Filter setFilter(int rowIndex, Filter filter) {
+        Filter result = filters.set(rowIndex, filter);
+        updateFilterMatcher();
+        return result;
+    }
+
+    /**
+     * Gets the filter by row index
+     * @param rowIndex The row index
+     * @return The filter in that row
+     */
+    public Filter getFilter(int rowIndex) {
+        return filters.get(rowIndex);
+    }
+
+    /**
+     * Draws a text on the map display that indicates that filters are active.
+     * @param g The graphics to draw that text on.
+     * @param lblOSD On Screen Display label
+     * @param header The title to display at the beginning of OSD
+     * @param footer The message to display at the bottom of OSD. Must end by {@code </html>}
+     */
+    public void drawOSDText(Graphics2D g, OSDLabel lblOSD, String header, String footer) {
+        if (disabledCount == 0 && disabledAndHiddenCount == 0)
+            return;
+
+        String message = "<html>" + header;
+
+        if (disabledAndHiddenCount != 0) {
+            /* for correct i18n of plural forms - see #9110 */
+            message += trn("<p><b>{0}</b> object hidden", "<p><b>{0}</b> objects hidden", disabledAndHiddenCount, disabledAndHiddenCount);
+        }
+
+        if (disabledAndHiddenCount != 0 && disabledCount != 0) {
+            message += "<br>";
+        }
+
+        if (disabledCount != 0) {
+            /* for correct i18n of plural forms - see #9110 */
+            message += trn("<b>{0}</b> object disabled", "<b>{0}</b> objects disabled", disabledCount, disabledCount);
+        }
+
+        message += footer;
+
+        lblOSD.setText(message);
+        lblOSD.setSize(lblOSD.getPreferredSize());
+
+        int dx = Main.map.mapView.getWidth() - lblOSD.getPreferredSize().width - 15;
+        int dy = 15;
+        g.translate(dx, dy);
+        lblOSD.paintComponent(g);
+        g.translate(-dx, -dy);
+    }
+
+    /**
+     * Returns the list of filters.
+     * @return the list of filters
+     */
+    public List<Filter> getFilters() {
+        return new ArrayList<>(filters);
+    }
+
+    /**
+     * Returns the number of filters.
+     * @return the number of filters
+     */
+    public int getFiltersCount() {
+        return filters.size();
+    }
+
+    /**
+     * Returns the number of primitives that are disabled but not hidden.
+     * @return the number of primitives that are disabled but not hidden
+     */
+    public int getDisabledCount() {
+        return disabledCount;
+    }
+
+    /**
+     * Returns the number of primitives that are disabled and hidden.
+     * @return the number of primitives that are disabled and hidden
+     */
+    public int getDisabledAndHiddenCount() {
+        return disabledAndHiddenCount;
+    }
+
+    /**
+     * Determines if the filter state (normal / disabled / hidden) of any primitive has changed in the process.
+     * @return true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process
+     */
+    public boolean isChanged() {
+        return changed;
+    }
+
+    /**
+     * Returns the list of primitives whose filtering can be affected by change in primitive
+     * @param primitives list of primitives to check
+     * @return List of primitives whose filtering can be affected by change in source primitives
+     */
+    public static Collection<OsmPrimitive> getAffectedPrimitives(Collection<? extends OsmPrimitive> primitives) {
+        // Filters can use nested parent/child expression so complete tree is necessary
+        Set<OsmPrimitive> result = new HashSet<>();
+        Stack<OsmPrimitive> stack = new Stack<>();
+        stack.addAll(primitives);
+
+        while (!stack.isEmpty()) {
+            OsmPrimitive p = stack.pop();
+
+            if (result.contains(p)) {
+                continue;
+            }
+
+            result.add(p);
+
+            if (p instanceof Way) {
+                for (OsmPrimitive n: ((Way) p).getNodes()) {
+                    stack.push(n);
+                }
+            } else if (p instanceof Relation) {
+                for (RelationMember rm: ((Relation) p).getMembers()) {
+                    stack.push(rm.getMember());
+                }
+            }
+
+            for (OsmPrimitive ref: p.getReferrers()) {
+                stack.push(ref);
+            }
+        }
+
+        return result;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12399)
+++ /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12400)
@@ -50,4 +50,5 @@
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
 import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
+import org.openstreetmap.josm.gui.autofilter.AutoFilterManager;
 import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
@@ -289,4 +290,7 @@
             add(c);
         }
+        if (AutoFilterManager.PROP_AUTO_FILTER_ENABLED.get()) {
+            AutoFilterManager.getInstance().enableAutoFilterRule(AutoFilterManager.PROP_AUTO_FILTER_RULE.get());
+        }
         setTransferHandler(new OsmTransferHandler());
     }
@@ -558,5 +562,7 @@
         }
 
-        if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
+        if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) {
+            AutoFilterManager.getInstance().drawOSDText(tempG);
+        } else if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
             Main.map.filterDialog.drawOSDText(tempG);
         }
Index: /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilter.java	(revision 12400)
+++ /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilter.java	(revision 12400)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.autofilter;
+
+import org.openstreetmap.josm.data.osm.Filter;
+
+/**
+ * An auto filter is a graphical shortcut to enable a filter for a specific tag.
+ * @since 12400
+ */
+public class AutoFilter {
+    private final String label;
+    private final String description;
+    private final Filter filter;
+
+    /**
+     * Constructs a new {@code AutoFilter}.
+     * @param label button label
+     * @param description button tooltip
+     * @param filter associated filter
+     */
+    public AutoFilter(String label, String description, Filter filter) {
+        this.label = label;
+        this.description = description;
+        this.filter = filter;
+    }
+
+    /**
+     * Returns the button label.
+     * @return the button label
+     */
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * Returns the button tooltip.
+     * @return the button tooltip
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Returns the filter.
+     * @return the filter
+     */
+    public Filter getFilter() {
+        return filter;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java	(revision 12400)
+++ /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java	(revision 12400)
@@ -0,0 +1,65 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.autofilter;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
+
+/**
+ * A button associated to an auto filter. If clicked twice, the filter is reset.
+ * @since 12400
+ */
+public class AutoFilterButton extends JButton {
+
+    private static final ColorProperty PROP_COLOR = new ColorProperty("auto.filter.button.color", new Color(0, 160, 160));
+
+    private final AutoFilter filter;
+
+    /**
+     * Constructs a new {@code AutoFilterButton}.
+     * @param filter auto filter associated to this button
+     */
+    public AutoFilterButton(final AutoFilter filter) {
+        super(new JosmAction(filter.getLabel(), null, filter.getDescription(), null, false) {
+            @Override
+            public synchronized void actionPerformed(ActionEvent e) {
+                AutoFilterManager afm = AutoFilterManager.getInstance();
+                if (afm.getCurrentAutoFilter() == filter) {
+                    afm.setCurrentAutoFilter(null);
+                    Main.map.filterDialog.getFilterModel().executeFilters();
+                } else {
+                    afm.setCurrentAutoFilter(filter);
+                }
+            }
+        });
+        this.filter = filter;
+        setForeground(Color.WHITE);
+        setContentAreaFilled(false);
+        setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
+    }
+
+    @Override
+    protected void paintComponent(Graphics g) {
+        if (getModel().isPressed()) {
+            g.setColor(PROP_COLOR.get().darker().darker());
+        } else if (getModel().isRollover() || AutoFilterManager.getInstance().getCurrentAutoFilter() == filter) {
+            g.setColor(PROP_COLOR.get().darker());
+        } else {
+            g.setColor(PROP_COLOR.get());
+        }
+        if (g instanceof Graphics2D) {
+            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        }
+        g.fillRoundRect(0, 0, getWidth(), getHeight(), 3, 3);
+        super.paintComponent(g);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java	(revision 12400)
+++ /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java	(revision 12400)
@@ -0,0 +1,373 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.autofilter;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Filter;
+import org.openstreetmap.josm.data.osm.FilterModel;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSetListener;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
+import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
+import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
+import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
+import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.data.preferences.StringProperty;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
+import org.openstreetmap.josm.gui.widgets.OSDLabel;
+
+/**
+ * The auto filter manager keeps track of registered auto filter rules and applies the active one on the fly,
+ * when the map contents, location or zoom changes.
+ * @since 12400
+ */
+public final class AutoFilterManager implements ZoomChangeListener, MapModeChangeListener, DataSetListener, PreferenceChangedListener {
+
+    /**
+     * Property to determines if the auto filter feature is enabled.
+     */
+    public static BooleanProperty PROP_AUTO_FILTER_ENABLED = new BooleanProperty("auto.filter.enabled", Boolean.TRUE);
+
+    /**
+     * Property to determine the current auto filter rule.
+     */
+    public static StringProperty PROP_AUTO_FILTER_RULE = new StringProperty("auto.filter.rule", "level");
+
+    /**
+     * The unique instance.
+     */
+    private static AutoFilterManager instance;
+
+    /**
+     * The buttons currently displayed in map view.
+     */
+    private final Map<String, AutoFilterButton> buttons = new TreeMap<>();
+
+    /**
+     * The list of registered auto filter rules.
+     */
+    private final List<AutoFilterRule> rules = new ArrayList<>();
+
+    /**
+     * A helper for {@link #drawOSDText(Graphics2D)}.
+     */
+    private final OSDLabel lblOSD = new OSDLabel("");
+
+    /**
+     * The filter model.
+     */
+    private final FilterModel model = new FilterModel();
+
+    /**
+     * The currently enabled rule, if any.
+     */
+    private AutoFilterRule enabledRule;
+
+    /**
+     * The currently selected auto filter, if any.
+     */
+    private AutoFilter currentAutoFilter;
+
+    /**
+     * Returns the unique instance.
+     * @return the unique instance
+     */
+    public static AutoFilterManager getInstance() {
+        if (instance == null) {
+            instance = new AutoFilterManager();
+        }
+        return instance;
+    }
+
+    private AutoFilterManager() {
+        MapFrame.addMapModeChangeListener(this);
+        Main.pref.addPreferenceChangeListener(this);
+        NavigatableComponent.addZoomChangeListener(this);
+        DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
+        registerAutoFilterRules(AutoFilterRule.defaultRules());
+    }
+
+    private synchronized void updateButtons() {
+        if (enabledRule != null && Main.map != null
+                && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(Main.map.mapView.getDist100Pixel())) {
+            NavigableSet<String> values = getNumericValues(enabledRule.getKey(), enabledRule.getValueComparator());
+            if (!values.equals(buttons.keySet())) {
+                removeAllButtons();
+                addNewButtons(values);
+            }
+        }
+    }
+
+    private void addNewButtons(NavigableSet<String> values) {
+        int i = 0;
+        int maxWidth = 16;
+        for (final String value : values.descendingSet()) {
+            Filter filter = new Filter();
+            filter.enable = true;
+            filter.inverted = true;
+            filter.text = enabledRule.getKey() + "=" + value;
+            String label = enabledRule.getValueFormatter().apply(value);
+            AutoFilterButton button = new AutoFilterButton(new AutoFilter(label, filter.text, filter));
+            buttons.put(value, button);
+            maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
+            Main.map.mapView.add(button).setLocation(3, 60 + 22*i++);
+        }
+        for (AutoFilterButton b : buttons.values()) {
+            b.setSize(maxWidth, 20);
+        }
+        Main.map.mapView.validate();
+    }
+
+    private void removeAllButtons() {
+        for (Iterator<String> it = buttons.keySet().iterator(); it.hasNext();) {
+            Main.map.mapView.remove(buttons.get(it.next()));
+            it.remove();
+        }
+    }
+
+    private static NavigableSet<String> getNumericValues(String key, Comparator<String> comparator) {
+        NavigableSet<String> values = new TreeSet<>(comparator);
+        for (String s : getTagValues(key)) {
+            try {
+                Integer.parseInt(s);
+                values.add(s);
+            } catch (NumberFormatException e) {
+                Main.trace(e);
+            }
+        }
+        return values;
+    }
+
+    private static Set<String> getTagValues(String key) {
+        BBox bbox = Main.map.mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
+        DataSet ds = Main.getLayerManager().getEditDataSet();
+        Set<String> values = new TreeSet<>();
+        Consumer<OsmPrimitive> consumer = o -> {
+            String value = o.get(key);
+            if (value != null) {
+                for (String v : value.split(";")) {
+                    values.add(v);
+                }
+            }
+        };
+        ds.searchNodes(bbox).forEach(consumer);
+        ds.searchWays(bbox).forEach(consumer);
+        ds.searchRelations(bbox).forEach(consumer);
+        return values;
+    }
+
+    @Override
+    public void zoomChanged() {
+        updateButtons();
+    }
+
+    @Override
+    public void dataChanged(DataChangedEvent event) {
+        updateFiltersFull();
+    }
+
+    @Override
+    public void nodeMoved(NodeMovedEvent event) {
+        updateFiltersFull();
+    }
+
+    @Override
+    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
+        updateFiltersFull();
+    }
+
+    @Override
+    public void primitivesAdded(PrimitivesAddedEvent event) {
+        updateFiltersEvent(event, false);
+        updateButtons();
+    }
+
+    @Override
+    public void primitivesRemoved(PrimitivesRemovedEvent event) {
+        updateFiltersFull();
+        updateButtons();
+    }
+
+    @Override
+    public void relationMembersChanged(RelationMembersChangedEvent event) {
+        updateFiltersEvent(event, true);
+    }
+
+    @Override
+    public void tagsChanged(TagsChangedEvent event) {
+        updateFiltersEvent(event, true);
+        updateButtons();
+    }
+
+    @Override
+    public void wayNodesChanged(WayNodesChangedEvent event) {
+        updateFiltersEvent(event, true);
+    }
+
+    @Override
+    public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
+        updateFiltersFull();
+    }
+
+    private void updateFiltersFull() {
+        if (currentAutoFilter != null) {
+            model.executeFilters();
+        }
+    }
+
+    private void updateFiltersEvent(AbstractDatasetChangedEvent event, boolean affectedOnly) {
+        if (currentAutoFilter != null) {
+            Collection<? extends OsmPrimitive> prims = event.getPrimitives();
+            model.executeFilters(affectedOnly ? FilterModel.getAffectedPrimitives(prims) : prims);
+        }
+    }
+
+    /**
+     * Registers new auto filter rule(s).
+     * @param filterRules new auto filter rules. Must not be null
+     * @return {@code true} if the list changed as a result of the call
+     * @throws NullPointerException if {@code filterRules} is null
+     */
+    public synchronized boolean registerAutoFilterRules(AutoFilterRule... filterRules) {
+        return rules.addAll(Arrays.asList(filterRules));
+    }
+
+    /**
+     * Unregisters an auto filter rule.
+     * @param rule auto filter rule to remove. Must not be null
+     * @return {@code true} if the list contained the specified rule
+     * @throws NullPointerException if {@code rule} is null
+     */
+    public synchronized boolean unregisterAutoFilterRule(AutoFilterRule rule) {
+        return rules.remove(Objects.requireNonNull(rule, "rule"));
+    }
+
+    /**
+     * Returns the list of registered auto filter rules.
+     * @return the list of registered rules
+     */
+    public synchronized List<AutoFilterRule> getAutoFilterRules() {
+        return new ArrayList<>(rules);
+    }
+
+    /**
+     * Returns the auto filter rule defined for the given OSM key.
+     * @param key OSM key used to identify rule. Can't be null.
+     * @return the auto filter rule defined for the given OSM key, or null
+     * @throws NullPointerException if key is null
+     */
+    public synchronized AutoFilterRule getAutoFilterRule(String key) {
+        for (AutoFilterRule r : rules) {
+            if (key.equals(r.getKey())) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the currently enabled auto filter rule to the one defined for the given OSM key.
+     * @param key OSM key used to identify new rule to enable. Null to disable the auto filter feature.
+     */
+    public synchronized void enableAutoFilterRule(String key) {
+        enableAutoFilterRule(key == null ? null : getAutoFilterRule(key));
+    }
+
+    /**
+     * Sets the currently enabled auto filter rule.
+     * @param rule new rule to enable. Null to disable the auto filter feature.
+     */
+    public synchronized void enableAutoFilterRule(AutoFilterRule rule) {
+        enabledRule = rule;
+    }
+
+    /**
+     * Returns the currently selected auto filter, if any.
+     * @return the currently selected auto filter, or null
+     */
+    public AutoFilter getCurrentAutoFilter() {
+        return currentAutoFilter;
+    }
+
+    /**
+     * Sets the currently selected auto filter, if any.
+     * @param autoFilter the currently selected auto filter, or null
+     */
+    public synchronized void setCurrentAutoFilter(AutoFilter autoFilter) {
+        model.clearFilters();
+        currentAutoFilter = autoFilter;
+        if (autoFilter != null) {
+            model.addFilter(autoFilter.getFilter());
+            model.executeFilters();
+            if (model.isChanged()) {
+                Main.getLayerManager().getEditLayer().invalidate();
+            }
+        }
+    }
+
+    /**
+     * Draws a text on the map display that indicates that filters are active.
+     * @param g The graphics to draw that text on.
+     */
+    public void drawOSDText(Graphics2D g) {
+        model.drawOSDText(g, lblOSD,
+            tr("<h2>Filter active: {0}</h2>", currentAutoFilter.getFilter().text),
+            tr("</p><p>Click again on filter button to see all objects.</p></html>"));
+    }
+
+    private void resetCurrentAutoFilter() {
+        setCurrentAutoFilter(null);
+        removeAllButtons();
+        if (Main.map != null) {
+            Main.map.filterDialog.getFilterModel().executeFilters();
+        }
+    }
+
+    @Override
+    public void preferenceChanged(PreferenceChangeEvent e) {
+        if (e.getKey().equals(PROP_AUTO_FILTER_ENABLED.getKey())) {
+            if (PROP_AUTO_FILTER_ENABLED.get()) {
+                enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
+                updateButtons();
+            } else {
+                enableAutoFilterRule((AutoFilterRule) null);
+                resetCurrentAutoFilter();
+            }
+        } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())) {
+            enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
+            resetCurrentAutoFilter();
+            updateButtons();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java	(revision 12400)
+++ /trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java	(revision 12400)
@@ -0,0 +1,109 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.autofilter;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * An auto filter rule determines how auto filter can be built from visible map data.
+ * Several rules can be registered, but only one rule is active at the same time.
+ * Rules are identified by the OSM key on which they apply.
+ * The dynamic values discovering operates only below a certain zoom level, for performance reasons.
+ * @since 12400
+ */
+public class AutoFilterRule {
+
+    private final String key;
+
+    private final int minZoomLevel;
+
+    private Function<String, String> valueFormatter = s -> s;
+
+    private Comparator<String> valueComparator = Comparator.comparingInt(s -> Integer.valueOf(valueFormatter.apply(s)));
+
+    /**
+     * Constructs a new {@code AutoFilterRule}.
+     * @param key the OSM key on which the rule applies
+     * @param minZoomLevel the minimum zoom level at which the rule applies
+     */
+    public AutoFilterRule(String key, int minZoomLevel) {
+        this.key = key;
+        this.minZoomLevel = minZoomLevel;
+    }
+
+    /**
+     * Returns the OSM key on which the rule applies.
+     * @return the OSM key on which the rule applies
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Returns the minimum zoom level at which the rule applies.
+     * @return the minimum zoom level at which the rule applies
+     */
+    public int getMinZoomLevel() {
+        return minZoomLevel;
+    }
+
+    /**
+     * Returns the OSM value formatter that defines the associated button label.
+     * @return the OSM value formatter that defines the associated button label (identity by default)
+     */
+    public Function<String, String> getValueFormatter() {
+        return valueFormatter;
+    }
+
+    /**
+     * Sets a OSM value formatter that defines the associated button label.
+     * @param valueFormatter OSM value formatter. Cannot be null
+     * @return {@code this}
+     * @throws NullPointerException if {@code valueFormatter} is null
+     */
+    public AutoFilterRule setValueFormatter(Function<String, String> valueFormatter) {
+        this.valueFormatter = Objects.requireNonNull(valueFormatter);
+        return this;
+    }
+
+    /**
+     * Returns the OSM value comparator used to order the buttons.
+     * @return the OSM value comparator
+     */
+    public Comparator<String> getValueComparator() {
+        return valueComparator;
+    }
+
+    /**
+     * Sets the OSM value comparator used to order the buttons.
+     * @param valueComparator the OSM value comparator
+     * @return {@code this}
+     * @throws NullPointerException if {@code valueComparator} is null
+     */
+    public AutoFilterRule setValueComparator(Comparator<String> valueComparator) {
+        this.valueComparator = valueComparator;
+        return this;
+    }
+
+    /**
+     * Returns the default list of auto filter rules. Plugins can extend the list by registering additional rules.
+     * @return the default list of auto filter rules
+     */
+    public static AutoFilterRule[] defaultRules() {
+        return new AutoFilterRule[] {
+            new AutoFilterRule("level", 17),
+            new AutoFilterRule("layer", 16),
+            new AutoFilterRule("maxspeed", 16)
+                .setValueFormatter(s -> s.replaceAll(" mph", "")),
+            new AutoFilterRule("voltage", 5)
+                .setValueFormatter(s -> s.replaceAll("000$", "k") + 'V')
+                .setValueComparator(Comparator.comparingInt(s -> Integer.valueOf(s)))
+        };
+    }
+
+    @Override
+    public String toString() {
+        return key + '[' + minZoomLevel + ']';
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java	(revision 12399)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java	(revision 12400)
@@ -11,9 +11,5 @@
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.Stack;
 
 import javax.swing.AbstractAction;
@@ -32,8 +28,5 @@
 import org.openstreetmap.josm.actions.search.SearchAction;
 import org.openstreetmap.josm.data.osm.Filter;
-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.Way;
+import org.openstreetmap.josm.data.osm.FilterModel;
 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
@@ -94,5 +87,5 @@
         DatasetEventManager.getInstance().removeDatasetListener(this);
         MapFrame.removeMapModeChangeListener(this);
-        filterModel.clearFilterFlags();
+        filterModel.model.clearFilterFlags();
         Main.map.mapView.repaint();
     }
@@ -302,5 +295,6 @@
     public void updateDialogHeader() {
         SwingUtilities.invokeLater(() -> setTitle(
-                tr("Filter Hidden:{0} Disabled:{1}", filterModel.disabledAndHiddenCount, filterModel.disabledCount)));
+                tr("Filter Hidden:{0} Disabled:{1}",
+                        filterModel.model.getDisabledAndHiddenCount(), filterModel.model.getDisabledCount())));
     }
 
@@ -313,42 +307,4 @@
     }
 
-    /**
-     * Returns the list of primitives whose filtering can be affected by change in primitive
-     * @param primitives list of primitives to check
-     * @return List of primitives whose filtering can be affected by change in source primitives
-     */
-    private static Collection<OsmPrimitive> getAffectedPrimitives(Collection<? extends OsmPrimitive> primitives) {
-        // Filters can use nested parent/child expression so complete tree is necessary
-        Set<OsmPrimitive> result = new HashSet<>();
-        Stack<OsmPrimitive> stack = new Stack<>();
-        stack.addAll(primitives);
-
-        while (!stack.isEmpty()) {
-            OsmPrimitive p = stack.pop();
-
-            if (result.contains(p)) {
-                continue;
-            }
-
-            result.add(p);
-
-            if (p instanceof Way) {
-                for (OsmPrimitive n: ((Way) p).getNodes()) {
-                    stack.push(n);
-                }
-            } else if (p instanceof Relation) {
-                for (RelationMember rm: ((Relation) p).getMembers()) {
-                    stack.push(rm.getMember());
-                }
-            }
-
-            for (OsmPrimitive ref: p.getReferrers()) {
-                stack.push(ref);
-            }
-        }
-
-        return result;
-    }
-
     @Override
     public void dataChanged(DataChangedEvent event) {
@@ -378,15 +334,15 @@
     @Override
     public void relationMembersChanged(RelationMembersChangedEvent event) {
-        filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
+        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
     }
 
     @Override
     public void tagsChanged(TagsChangedEvent event) {
-        filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
+        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
     }
 
     @Override
     public void wayNodesChanged(WayNodesChangedEvent event) {
-        filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
+        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 12399)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 12400)
@@ -4,28 +4,17 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trc;
-import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.Graphics2D;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 
-import javax.swing.JOptionPane;
 import javax.swing.table.AbstractTableModel;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
-import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Filter;
-import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
-import org.openstreetmap.josm.data.osm.FilterMatcher;
-import org.openstreetmap.josm.data.osm.FilterWorker;
-import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.FilterModel;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.autofilter.AutoFilterManager;
 import org.openstreetmap.josm.gui.widgets.OSDLabel;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -54,11 +43,7 @@
 
     /**
-     * number of primitives that are disabled but not hidden
-     */
-    public int disabledCount;
-    /**
-     * number of primitives that are disabled and hidden
-     */
-    public int disabledAndHiddenCount;
+     * The filter model
+     */
+    final FilterModel model = new FilterModel();
 
     /**
@@ -74,25 +59,6 @@
     }
 
-    private final transient List<Filter> filters = new LinkedList<>();
-    private final transient FilterMatcher filterMatcher = new FilterMatcher();
-
     private void updateFilters() {
-        filterMatcher.reset();
-        for (Filter filter : filters) {
-            try {
-                filterMatcher.add(filter);
-            } catch (ParseError e) {
-                Main.error(e);
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("<html>Error in filter <code>{0}</code>:<br>{1}",
-                                Utils.escapeReservedCharactersHTML(Utils.shortenString(filter.text, 80)),
-                                Utils.escapeReservedCharactersHTML(e.getMessage())),
-                        tr("Error in filter"),
-                        JOptionPane.ERROR_MESSAGE);
-                filter.enable = false;
-                savePrefs();
-            }
-        }
+        AutoFilterManager.getInstance().setCurrentAutoFilter(null);
         executeFilters();
     }
@@ -102,45 +68,6 @@
      */
     public void executeFilters() {
-        DataSet ds = Main.getLayerManager().getEditDataSet();
-        boolean changed = false;
-        if (ds == null) {
-            disabledAndHiddenCount = 0;
-            disabledCount = 0;
-            changed = true;
-        } else {
-            final Collection<OsmPrimitive> deselect = new HashSet<>();
-
-            ds.beginUpdate();
-            try {
-
-                final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives();
-
-                changed = FilterWorker.executeFilters(all, filterMatcher);
-
-                disabledCount = 0;
-                disabledAndHiddenCount = 0;
-                // collect disabled and selected the primitives
-                for (OsmPrimitive osm : all) {
-                    if (osm.isDisabled()) {
-                        disabledCount++;
-                        if (osm.isSelected()) {
-                            deselect.add(osm);
-                        }
-                        if (osm.isDisabledAndHidden()) {
-                            disabledAndHiddenCount++;
-                        }
-                    }
-                }
-                disabledCount -= disabledAndHiddenCount;
-            } finally {
-                ds.endUpdate();
-            }
-
-            if (!deselect.isEmpty()) {
-                ds.clearSelection(deselect);
-            }
-        }
-
-        if (changed && Main.isDisplayingMapView()) {
+        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null) {
+            model.executeFilters();
             updateMap();
         }
@@ -152,90 +79,22 @@
      */
     public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
-        DataSet ds = Main.getLayerManager().getEditDataSet();
-        if (ds == null)
-            return;
-
-        boolean changed = false;
-        List<OsmPrimitive> deselect = new ArrayList<>();
-
-        ds.beginUpdate();
-        try {
-            for (int i = 0; i < 2; i++) {
-                for (OsmPrimitive primitive: primitives) {
-
-                    if (i == 0 && primitive instanceof Node) {
-                        continue;
-                    }
-
-                    if (i == 1 && !(primitive instanceof Node)) {
-                        continue;
-                    }
-
-                    if (primitive.isDisabled()) {
-                        disabledCount--;
-                    }
-                    if (primitive.isDisabledAndHidden()) {
-                        disabledAndHiddenCount--;
-                    }
-                    changed |= FilterWorker.executeFilters(primitive, filterMatcher);
-                    if (primitive.isDisabled()) {
-                        disabledCount++;
-                    }
-                    if (primitive.isDisabledAndHidden()) {
-                        disabledAndHiddenCount++;
-                    }
-
-                    if (primitive.isSelected() && primitive.isDisabled()) {
-                        deselect.add(primitive);
-                    }
-
-                }
-            }
-        } finally {
-            ds.endUpdate();
-        }
-
-        if (changed) {
+        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null) {
+            model.executeFilters(primitives);
             updateMap();
-            ds.clearSelection(deselect);
-        }
-    }
-
-    private static void updateMap() {
-        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
-        if (editLayer != null) {
-            editLayer.invalidate();
-        }
-        Main.map.filterDialog.updateDialogHeader();
-    }
-
-    /**
-     * Clears all filtered flags from all primitives in the dataset
-     */
-    public void clearFilterFlags() {
-        DataSet ds = Main.getLayerManager().getEditDataSet();
-        if (ds != null) {
-            FilterWorker.clearFilterFlags(ds.allPrimitives());
-        }
-        disabledCount = 0;
-        disabledAndHiddenCount = 0;
+        }
+    }
+
+    private void updateMap() {
+        if (Main.map != null && model.isChanged()) {
+            Main.map.filterDialog.updateDialogHeader();
+        }
     }
 
     private void loadPrefs() {
-        List<FilterPreferenceEntry> entries = Main.pref.getListOfStructs("filters.entries", null, FilterPreferenceEntry.class);
-        if (entries != null) {
-            for (FilterPreferenceEntry e : entries) {
-                filters.add(new Filter(e));
-            }
-            updateFilters();
-        }
+        model.loadPrefs("filters.entries");
     }
 
     private void savePrefs() {
-        Collection<FilterPreferenceEntry> entries = new ArrayList<>();
-        for (Filter flt : filters) {
-            entries.add(flt.getPreferenceEntry());
-        }
-        Main.pref.putListOfStructs("filters.entries", entries, FilterPreferenceEntry.class);
+        model.savePrefs("filters.entries");
     }
 
@@ -245,8 +104,10 @@
      */
     public void addFilter(Filter filter) {
-        filters.add(filter);
-        savePrefs();
-        updateFilters();
-        fireTableRowsInserted(filters.size() - 1, filters.size() - 1);
+        if (model.addFilter(filter)) {
+            savePrefs();
+            updateFilters();
+            int size = model.getFiltersCount();
+            fireTableRowsInserted(size - 1, size - 1);
+        }
     }
 
@@ -256,10 +117,9 @@
      */
     public void moveDownFilter(int rowIndex) {
-        if (rowIndex >= filters.size() - 1)
-            return;
-        filters.add(rowIndex + 1, filters.remove(rowIndex));
-        savePrefs();
-        updateFilters();
-        fireTableRowsUpdated(rowIndex, rowIndex + 1);
+        if (model.moveDownFilter(rowIndex)) {
+            savePrefs();
+            updateFilters();
+            fireTableRowsUpdated(rowIndex, rowIndex + 1);
+        }
     }
 
@@ -269,10 +129,9 @@
      */
     public void moveUpFilter(int rowIndex) {
-        if (rowIndex == 0)
-            return;
-        filters.add(rowIndex - 1, filters.remove(rowIndex));
-        savePrefs();
-        updateFilters();
-        fireTableRowsUpdated(rowIndex - 1, rowIndex);
+        if (model.moveUpFilter(rowIndex)) {
+            savePrefs();
+            updateFilters();
+            fireTableRowsUpdated(rowIndex - 1, rowIndex);
+        }
     }
 
@@ -282,8 +141,9 @@
      */
     public void removeFilter(int rowIndex) {
-        filters.remove(rowIndex);
-        savePrefs();
-        updateFilters();
-        fireTableRowsDeleted(rowIndex, rowIndex);
+        if (model.removeFilter(rowIndex) != null) {
+            savePrefs();
+            updateFilters();
+            fireTableRowsDeleted(rowIndex, rowIndex);
+        }
     }
 
@@ -294,5 +154,5 @@
      */
     public void setFilter(int rowIndex, Filter filter) {
-        filters.set(rowIndex, filter);
+        model.setFilter(rowIndex, filter);
         savePrefs();
         updateFilters();
@@ -306,10 +166,10 @@
      */
     public Filter getFilter(int rowIndex) {
-        return filters.get(rowIndex);
+        return model.getFilter(rowIndex);
     }
 
     @Override
     public int getRowCount() {
-        return filters.size();
+        return model.getFiltersCount();
     }
 
@@ -343,5 +203,5 @@
      */
     public boolean isCellEnabled(int row, int column) {
-        return filters.get(row).enable || column == 0;
+        return model.getFilter(row).enable || column == 0;
     }
 
@@ -353,19 +213,16 @@
     @Override
     public void setValueAt(Object aValue, int row, int column) {
-        if (row >= filters.size()) {
+        if (row >= model.getFiltersCount()) {
             return;
         }
-        Filter f = filters.get(row);
+        Filter f = model.getFilter(row);
         switch (column) {
         case COL_ENABLED:
             f.enable = (Boolean) aValue;
-            savePrefs();
-            updateFilters();
-            fireTableRowsUpdated(row, row);
+            setFilter(row, f);
             break;
         case COL_HIDING:
             f.hiding = (Boolean) aValue;
-            savePrefs();
-            updateFilters();
+            setFilter(row, f);
             break;
         case COL_TEXT:
@@ -375,6 +232,5 @@
         case COL_INVERTED:
             f.inverted = (Boolean) aValue;
-            savePrefs();
-            updateFilters();
+            setFilter(row, f);
             break;
         default: // Do nothing
@@ -387,8 +243,8 @@
     @Override
     public Object getValueAt(int row, int column) {
-        if (row >= filters.size()) {
+        if (row >= model.getFiltersCount()) {
             return null;
         }
-        Filter f = filters.get(row);
+        Filter f = model.getFilter(row);
         switch (column) {
         case COL_ENABLED:
@@ -424,33 +280,7 @@
      */
     public void drawOSDText(Graphics2D g) {
-        String message = "<html>" + tr("<h2>Filter active</h2>");
-
-        if (disabledCount == 0 && disabledAndHiddenCount == 0)
-            return;
-
-        if (disabledAndHiddenCount != 0) {
-            /* for correct i18n of plural forms - see #9110 */
-            message += trn("<p><b>{0}</b> object hidden", "<p><b>{0}</b> objects hidden", disabledAndHiddenCount, disabledAndHiddenCount);
-        }
-
-        if (disabledAndHiddenCount != 0 && disabledCount != 0) {
-            message += "<br>";
-        }
-
-        if (disabledCount != 0) {
-            /* for correct i18n of plural forms - see #9110 */
-            message += trn("<b>{0}</b> object disabled", "<b>{0}</b> objects disabled", disabledCount, disabledCount);
-        }
-
-        message += tr("</p><p>Close the filter dialog to see all objects.<p></html>");
-
-        lblOSD.setText(message);
-        lblOSD.setSize(lblOSD.getPreferredSize());
-
-        int dx = Main.map.mapView.getWidth() - lblOSD.getPreferredSize().width - 15;
-        int dy = 15;
-        g.translate(dx, dy);
-        lblOSD.paintComponent(g);
-        g.translate(-dx, -dy);
+        model.drawOSDText(g, lblOSD,
+                tr("<h2>Filter active</h2>"),
+                tr("</p><p>Close the filter dialog to see all objects.<p></html>"));
     }
 
@@ -460,5 +290,5 @@
      */
     public List<Filter> getFilters() {
-        return filters;
+        return model.getFilters();
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java	(revision 12399)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java	(revision 12400)
@@ -15,4 +15,6 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.gui.autofilter.AutoFilterManager;
+import org.openstreetmap.josm.gui.autofilter.AutoFilterRule;
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
@@ -21,4 +23,5 @@
 import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
 import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.widgets.JosmComboBox;
 import org.openstreetmap.josm.tools.GBC;
 
@@ -48,4 +51,8 @@
     private final JCheckBox inactive = new JCheckBox(tr("Draw inactive layers in other color"));
     private final JCheckBox discardableKeys = new JCheckBox(tr("Display discardable keys"));
+    private final JCheckBox autoFilters = new JCheckBox(tr("Use auto filters"));
+    private final JLabel lblRule = new JLabel(tr("Rule"));
+    private final JosmComboBox<AutoFilterRule> autoFilterRules = new JosmComboBox<>(
+            AutoFilterManager.getInstance().getAutoFilterRules().toArray(new AutoFilterRule[] {}));
 
     // Options that affect performance
@@ -131,4 +138,14 @@
         discardableKeys.setSelected(Main.pref.getBoolean("display.discardable-keys", false));
 
+        // auto filters
+        autoFilters.setToolTipText(tr("Display buttons to automatically filter numeric values of a predefined tag"));
+        autoFilters.setSelected(AutoFilterManager.PROP_AUTO_FILTER_ENABLED.get());
+        autoFilters.addActionListener(e -> {
+            lblRule.setEnabled(autoFilters.isSelected());
+            autoFilterRules.setEnabled(autoFilters.isSelected());
+        });
+        autoFilterRules.setToolTipText("Rule defining which tag will provide automatic filters, below a certain zoom level");
+        autoFilterRules.setSelectedItem(AutoFilterManager.getInstance().getAutoFilterRule(AutoFilterManager.PROP_AUTO_FILTER_RULE.get()));
+
         JLabel performanceLabel = new JLabel(tr("Options that affect drawing performance"));
 
@@ -157,4 +174,7 @@
         panel.add(inactive, GBC.eop().insets(20, 0, 0, 0));
         panel.add(discardableKeys, GBC.eop().insets(20, 0, 0, 0));
+        panel.add(autoFilters, GBC.eop().insets(20, 0, 0, 0));
+        panel.add(lblRule, GBC.std().insets(40, 0, 0, 0));
+        panel.add(autoFilterRules, GBC.eop().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
 
         ExpertToggleAction.addVisibilitySwitcher(performanceLabel);
@@ -188,4 +208,6 @@
         Main.pref.put("draw.helper-line", drawHelperLine.isSelected());
         Main.pref.put("display.discardable-keys", discardableKeys.isSelected());
+        AutoFilterManager.PROP_AUTO_FILTER_ENABLED.put(autoFilters.isSelected());
+        AutoFilterManager.PROP_AUTO_FILTER_RULE.put(((AutoFilterRule) autoFilterRules.getSelectedItem()).getKey());
         int vn = Main.pref.getInteger("mappaint.node.virtual-size", 8);
         if (virtualNodes.isSelected()) {
