Ticket #24482: autofilter_all.patch

File autofilter_all.patch, 33.3 KB (added by tordanik, 6 months ago)
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index 978c3f612b..f4034e72a4 100644
    a b LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    620620        }
    621621
    622622        MapFrame map = MainApplication.getMap();
    623         if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) {
     623        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() != null) {
    624624            AutoFilterManager.getInstance().drawOSDText(tempG);
    625625        } else if (MainApplication.isDisplayingMapView() && map.filterDialog != null) {
    626626            map.filterDialog.drawOSDText(tempG);
  • src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java

    diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
    index d12685529b..ec14133f2c 100644
    a b public class AutoFilterButton extends JButton {  
    3535        super(new JosmAction(filter.getLabel(), null, filter.getDescription(), null, false) {
    3636            @Override
    3737            public synchronized void actionPerformed(ActionEvent e) {
     38
    3839                AutoFilterManager afm = AutoFilterManager.getInstance();
    39                 if (filter.equals(afm.getCurrentAutoFilter())) {
    40                     afm.setCurrentAutoFilter(null);
    41                     MainApplication.getMap().filterDialog.getFilterModel().executeFilters(true);
    42                 } else {
     40
     41                if (afm.getCurrentAutoFilters().isEmpty()) {
    4342                    afm.setCurrentAutoFilter(filter);
     43                } else {
     44                    if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0
     45                            || (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
     46                        if (afm.getCurrentAutoFilters().contains(filter)) {
     47                            afm.removeCurrentAutoFilter(filter);
     48                        } else {
     49                            afm.addCurrentAutoFilter(filter);
     50                        }
     51                    } else {
     52                        if (afm.getCurrentAutoFilters().size() == 1 && afm.getCurrentAutoFilters().contains(filter)) {
     53                            afm.setCurrentAutoFilter(null);
     54                        } else {
     55                            afm.setCurrentAutoFilter(filter);
     56                        }
     57                    }
    4458                }
     59
    4560            }
    4661        });
    4762        this.filter = filter;
    public class AutoFilterButton extends JButton {  
    5469    protected void paintComponent(Graphics g) {
    5570        if (getModel().isPressed()) {
    5671            g.setColor(PROP_COLOR.get().darker().darker());
    57         } else if (getModel().isRollover() || AutoFilterManager.getInstance().getCurrentAutoFilter() == filter) {
     72        } else if (getModel().isRollover() || AutoFilterManager.getInstance().getCurrentAutoFilters().contains(filter)) {
    5873            g.setColor(PROP_COLOR.get().darker());
    5974        } else {
    6075            g.setColor(PROP_COLOR.get());
  • src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java

    diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
    index 3a7ccb67a2..e59bc85f18 100644
    a b import java.util.ArrayList;  
    88import java.util.Arrays;
    99import java.util.Collection;
    1010import java.util.Collections;
     11import java.util.HashMap;
    1112import java.util.List;
    1213import java.util.Map;
    1314import java.util.NavigableSet;
    1415import java.util.Objects;
    15 import java.util.TreeMap;
     16import java.util.OptionalInt;
    1617import java.util.TreeSet;
    17 import java.util.function.Consumer;
     18import java.util.stream.Collectors;
     19import java.util.stream.IntStream;
    1820
    1921import org.openstreetmap.josm.actions.mapmode.MapMode;
    2022import org.openstreetmap.josm.data.osm.BBox;
    public final class AutoFilterManager  
    6365implements ZoomChangeListener, MapModeChangeListener, DataSetListener, PreferenceChangedListener, LayerChangeListener {
    6466
    6567    /**
    66      * Property to determines if the auto filter feature is enabled.
     68     * Property to determine if the auto filter feature is enabled.
    6769     */
    6870    public static final BooleanProperty PROP_AUTO_FILTER_ENABLED = new BooleanProperty("auto.filter.enabled", true);
    6971
     72    /**
     73     * Property to determine if the auto filter feature completely hides elements instead of just disabling them.
     74     * Equivalent to {@link Filter#hiding}
     75     */
     76    public static final BooleanProperty PROP_AUTO_FILTER_HIDING = new BooleanProperty("auto.filter.hiding", false);
     77
     78
    7079    /**
    7180     * Property to determine the current auto filter rule.
    7281     */
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    8089    /**
    8190     * The buttons currently displayed in map view.
    8291     */
    83     private final Map<Integer, AutoFilterButton> buttons = new TreeMap<>();
     92    private final Map<OptionalInt, AutoFilterButton> buttons = new HashMap<>();
    8493
    8594    /**
    8695     * The list of registered auto filter rules.
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    103112    AutoFilterRule enabledRule;
    104113
    105114    /**
    106      * The currently selected auto filter, if any.
     115     * The currently selected auto filters, if any.
     116     * If more than one auto filter is active, elements will match if they match at least one of them.
    107117     */
    108     private AutoFilter currentAutoFilter;
     118    private final List<AutoFilter> currentAutoFilters = new ArrayList<>();
    109119
    110120    /**
    111121     * Returns the unique instance.
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    133143                && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(map.mapView.getDist100Pixel())) {
    134144            // Retrieve the values from current rule visible on screen
    135145            NavigableSet<Integer> values = getNumericValues();
    136             // Make sure current auto filter button remains visible even if no data is found, to allow user to disable it
    137             if (currentAutoFilter != null) {
    138                 values.add(currentAutoFilter.getFilter().value);
     146            // Make sure current auto filter buttons remain visible even if no data is found, to allow user to disable them
     147            for (var currentAutoFilter : currentAutoFilters) {
     148                if (currentAutoFilter.getFilter().value != null) {
     149                    values.add(currentAutoFilter.getFilter().value);
     150                }
    139151            }
    140             if (!values.equals(buttons.keySet())) {
     152            if (!values.equals(buttons.keySet().stream()
     153                    .filter(it -> it.isPresent() && it.getAsInt() != Integer.MIN_VALUE)
     154                    .map(OptionalInt::getAsInt).collect(Collectors.toSet()))) {
    141155                removeAllButtons();
    142156                addNewButtons(values);
    143157            }
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    146160
    147161    static class CompiledFilter extends Filter implements MatchSupplier {
    148162        final AutoFilterRule rule;
    149         final int value;
     163        final Integer value;
    150164
    151         CompiledFilter(AutoFilterRule rule, int value) {
     165        CompiledFilter(AutoFilterRule rule, Integer value, boolean hiding) {
    152166            this.rule = rule;
    153167            this.value = value;
     168            this.hiding = hiding;
    154169            this.enable = true;
    155170            this.inverted = true;
    156171            this.text = rule.getKey() + "=" + rule.formatValue(value);
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    173188            if (!super.equals(obj) || getClass() != obj.getClass())
    174189                return false;
    175190            CompiledFilter other = (CompiledFilter) obj;
    176             return Objects.equals(rule, other.rule) && value == other.value;
     191            return Objects.equals(rule, other.rule) && Objects.equals(value, other.value);
     192        }
     193    }
     194
     195    /** The combination of multiple {@link CompiledFilter}s */
     196    static class CombinedFilter extends Filter implements MatchSupplier {
     197
     198        private final List<CompiledFilter> filters;
     199
     200        CombinedFilter(List<CompiledFilter> filters) {
     201
     202            if (filters == null || filters.isEmpty()) throw new IllegalArgumentException("no filters provided");
     203
     204            this.filters = filters;
     205
     206            boolean hiding = filters.get(0).hiding;
     207            String key = filters.get(0).rule.getKey();
     208            List<String> values = new ArrayList<>();
     209
     210            for (CompiledFilter filter : filters) {
     211                if (hiding != filter.hiding) throw new IllegalArgumentException("non-matching hiding properties");
     212                if (!Objects.equals(key, filter.rule.getKey())) throw new IllegalArgumentException("non-matching keys");
     213                values.add(filter.rule.formatValue(filter.value));
     214            }
     215
     216            this.hiding = hiding;
     217            this.enable = true;
     218            this.inverted = true;
     219            this.text = key + "~" + String.join("|", values);
     220
     221        }
     222
     223        @Override
     224        public SearchCompiler.Match get() {
     225            return new SearchCompiler.Match() {
     226                @Override
     227                public boolean match(OsmPrimitive osm) {
     228                    return filters.stream().anyMatch(filter -> filter.get().match(osm));
     229                }
     230            };
     231        }
     232
     233        @Override
     234        public int hashCode() {
     235            return 31 * super.hashCode() + Objects.hash(filters);
     236        }
     237
     238        @Override
     239        public boolean equals(Object obj) {
     240            if (this == obj)
     241                return true;
     242            if (!super.equals(obj) || getClass() != obj.getClass())
     243                return false;
     244            CombinedFilter other = (CombinedFilter) obj;
     245            return Objects.equals(filters, other.filters);
    177246        }
    178247    }
    179248
    180249    static class Match extends SearchCompiler.Match {
    181250        final AutoFilterRule rule;
    182         final int value;
     251        final Integer value;
    183252
    184         Match(AutoFilterRule rule, int value) {
     253        Match(AutoFilterRule rule, Integer value) {
    185254            this.rule = rule;
    186255            this.value = value;
    187256        }
    188257
    189258        @Override
    190259        public boolean match(OsmPrimitive osm) {
    191             return rule.getTagValuesForPrimitive(osm).anyMatch(v -> v == value);
     260            IntStream values = rule.getTagValuesForPrimitive(osm, false);
     261            if (value != null) {
     262                return values.anyMatch(v -> v == value);
     263            } else {
     264                return values.findAny().isEmpty();
     265            }
    192266        }
    193267
    194268        @Override
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    196270            if (this == o) return true;
    197271            if (o == null || getClass() != o.getClass()) return false;
    198272            Match match = (Match) o;
    199             return value == match.value &&
     273            return Objects.equals(value, match.value) &&
    200274                    Objects.equals(rule, match.rule);
    201275        }
    202276
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    214288        int maxWidth = 16;
    215289        final AutoFilterButton keyButton = AutoFilterButton.forOsmKey(enabledRule.getKey());
    216290        addButton(keyButton, Integer.MIN_VALUE, i++);
    217         for (final Integer value : values.descendingSet()) {
    218             CompiledFilter filter = new CompiledFilter(enabledRule, value);
     291        var valueList = new ArrayList<>(values.descendingSet());
     292        if (enabledRule.getNoValueFilter()) {
     293            valueList.add(null);
     294        }
     295        for (final Integer value : valueList) {
     296            CompiledFilter filter = new CompiledFilter(enabledRule, value, PROP_AUTO_FILTER_HIDING.get());
    219297            String label = enabledRule.formatValue(value);
    220298            AutoFilter autoFilter = new AutoFilter(label, filter.text, filter);
    221299            AutoFilterButton button = new AutoFilterButton(autoFilter);
    222             if (autoFilter.equals(currentAutoFilter)) {
     300            if (currentAutoFilters.contains(autoFilter)) {
    223301                button.getModel().setPressed(true);
    224302            }
    225             maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
    226303            addButton(button, value, i++);
     304            maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
    227305        }
    228306        for (AutoFilterButton b : buttons.values()) {
    229307            b.setSize(b == keyButton ? b.getPreferredSize().width : maxWidth, 20);
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    231309        MainApplication.getMap().mapView.validate();
    232310    }
    233311
    234     private void addButton(AutoFilterButton button, int value, int i) {
     312    private void addButton(AutoFilterButton button, Integer value, int i) {
    235313        MapView mapView = MainApplication.getMap().mapView;
    236         buttons.put(value, button);
     314        buttons.put(value == null ? OptionalInt.empty() : OptionalInt.of(value), button);
    237315        mapView.add(button).setLocation(3, 60 + 22*i);
    238316    }
    239317
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    252330        }
    253331        BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
    254332        NavigableSet<Integer> values = new TreeSet<>();
    255         Consumer<OsmPrimitive> consumer = o -> enabledRule.getTagValuesForPrimitive(o).forEach(values::add);
    256         ds.searchNodes(bbox).forEach(consumer);
    257         ds.searchWays(bbox).forEach(consumer);
    258         ds.searchRelations(bbox).forEach(consumer);
     333        for (var primitiveList : List.of(ds.searchNodes(bbox), ds.searchWays(bbox), ds.searchRelations(bbox))) {
     334            // add all values that are directly mentioned
     335            primitiveList.forEach(o -> enabledRule.getTagValuesForPrimitive(o, true).forEach(values::add));
     336            // only add integer values from value ranges, not fractional values
     337            primitiveList.forEach(o -> enabledRule.getTagValuesForPrimitive(o, false)
     338                    .filter(v -> !enabledRule.formatValue(v).contains("."))
     339                    .forEach(values::add));
     340
     341        }
    259342        return values;
    260343    }
    261344
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    267350    @Override
    268351    public void dataChanged(DataChangedEvent event) {
    269352        updateFiltersFull();
     353        updateButtons();
    270354    }
    271355
    272356    @Override
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    313397    }
    314398
    315399    private synchronized void updateFiltersFull() {
    316         if (currentAutoFilter != null) {
     400        if (!currentAutoFilters.isEmpty()) {
    317401            model.executeFilters();
    318402        }
    319403    }
    320404
    321405    private synchronized void updateFiltersEvent(AbstractDatasetChangedEvent event, boolean affectedOnly) {
    322         if (currentAutoFilter != null) {
     406        if (!currentAutoFilters.isEmpty()) {
    323407            Collection<? extends OsmPrimitive> prims = event.getPrimitives();
    324408            model.executeFilters(affectedOnly ? FilterModel.getAffectedPrimitives(prims) : prims);
    325409        }
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    382466    }
    383467
    384468    /**
    385      * Returns the currently selected auto filter, if any.
    386      * @return the currently selected auto filter, or null
     469     * Returns the currently selected auto filters, if any.
     470     * @return the currently selected auto filters. Can be empty.
    387471     */
    388     public synchronized AutoFilter getCurrentAutoFilter() {
    389         return currentAutoFilter;
     472    public synchronized List<AutoFilter> getCurrentAutoFilters() {
     473        return currentAutoFilters;
     474    }
     475
     476    /**
     477     * Returns a combination of all {@link #getCurrentAutoFilters()}, if any.
     478     * @return a single combined filter, or null
     479     */
     480    public Filter getCurrentCombinedFilter() {
     481        if (currentAutoFilters.isEmpty()) {
     482            return null;
     483        } else if (currentAutoFilters.size() == 1) {
     484            return currentAutoFilters.get(0).getFilter();
     485        } else {
     486            return new CombinedFilter(currentAutoFilters.stream().map(AutoFilter::getFilter).collect(Collectors.toList()));
     487        }
    390488    }
    391489
    392490    /**
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    394492     * @param autoFilter the currently selected auto filter, or null
    395493     */
    396494    public synchronized void setCurrentAutoFilter(AutoFilter autoFilter) {
    397         model.clearFilters();
    398         currentAutoFilter = autoFilter;
     495        currentAutoFilters.clear();
    399496        if (autoFilter != null) {
    400             model.addFilter(autoFilter.getFilter());
     497            currentAutoFilters.add(autoFilter);
     498        }
     499        updateModelFilters();
     500    }
     501
     502    public synchronized void addCurrentAutoFilter(AutoFilter autoFilter) {
     503        if (!currentAutoFilters.contains(autoFilter)) {
     504            currentAutoFilters.add(autoFilter);
     505            updateModelFilters();
     506        }
     507    }
     508
     509    public synchronized void removeCurrentAutoFilter(AutoFilter autoFilter) {
     510        if (currentAutoFilters.contains(autoFilter)) {
     511            currentAutoFilters.removeIf(it -> Objects.equals(it, autoFilter));
     512            updateModelFilters();
     513        }
     514    }
     515
     516    private synchronized void updateModelFilters() {
     517        model.clearFilters();
     518        if (currentAutoFilters.isEmpty()) {
     519                        if (MainApplication.getMap() != null) {
     520                MainApplication.getMap().filterDialog.getFilterModel().executeFilters(true);
     521            }
     522        } else {
     523            model.addFilter(getCurrentCombinedFilter());
    401524            model.executeFilters();
    402             if (model.isChanged()) {
    403                 OsmDataLayer dataLayer = MainApplication.getLayerManager().getActiveDataLayer();
    404                 if (dataLayer != null) {
    405                     dataLayer.invalidate();
    406                 }
     525            // update the data layer (necessary even if model.isChanged() == false to update the OSDText)
     526            OsmDataLayer dataLayer = MainApplication.getLayerManager().getActiveDataLayer();
     527            if (dataLayer != null) {
     528                dataLayer.invalidate();
    407529            }
    408530        }
    409531    }
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    413535     * @param g The graphics to draw that text on.
    414536     */
    415537    public synchronized void drawOSDText(Graphics2D g) {
     538        String filterText = Objects.requireNonNull(getCurrentCombinedFilter()).text;
     539        String lengthLimitedFilterText = filterText.length() > 18 ? filterText.substring(0, 18) + "…" : filterText;
    416540        model.drawOSDText(g, lblOSD,
    417             tr("<h2>Filter active: {0}</h2>", currentAutoFilter.getFilter().text),
     541            tr("<h2>Filter active: {0}</h2>", lengthLimitedFilterText),
    418542            tr("</p><p>Click again on filter button to see all objects.</p></html>"));
    419543    }
    420544
    implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc  
    437561                enableAutoFilterRule((AutoFilterRule) null);
    438562                resetCurrentAutoFilter();
    439563            }
    440         } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())) {
     564        } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())
     565                || e.getKey().equals(PROP_AUTO_FILTER_HIDING.getKey())) {
    441566            enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
    442567            resetCurrentAutoFilter();
    443568            updateButtons();
  • src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java

    diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
    index 9c35c387e9..0710d4a03b 100644
    a b  
    22package org.openstreetmap.josm.gui.autofilter;
    33
    44import java.text.DecimalFormat;
    5 import java.util.Arrays;
    6 import java.util.Locale;
    7 import java.util.Objects;
    8 import java.util.Optional;
     5import java.util.*;
    96import java.util.function.Function;
    107import java.util.function.IntFunction;
    118import java.util.function.ToIntFunction;
    129import java.util.regex.Matcher;
    1310import java.util.regex.Pattern;
    1411import java.util.stream.IntStream;
     12import java.util.stream.Stream;
    1513
    1614import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1715import org.openstreetmap.josm.data.osm.OsmUtils;
    1816import org.openstreetmap.josm.data.preferences.BooleanProperty;
    1917import org.openstreetmap.josm.tools.Logging;
    2018
     19import static java.util.stream.Collectors.toList;
     20
    2121/**
    2222 * An auto filter rule determines how auto filter can be built from visible map data.
    2323 * Several rules can be registered, but only one rule is active at the same time.
    public class AutoFilterRule {  
    4242
    4343    private IntFunction<String> valueFormatter = Integer::toString;
    4444
     45    private boolean noValueFilter = false;
     46
     47    /** The union of {@link #key} and the keys provided by {@link #setExtraKeys(List)}. */
     48    private List<String> allKeys;
     49
    4550    /**
    4651     * Constructs a new {@code AutoFilterRule}.
    4752     * @param key the OSM key on which the rule applies
    public class AutoFilterRule {  
    5055    public AutoFilterRule(String key, int minZoomLevel) {
    5156        this.key = key;
    5257        this.minZoomLevel = minZoomLevel;
     58        this.allKeys = List.of(key);
    5359    }
    5460
    5561    /**
    public class AutoFilterRule {  
    6874        return minZoomLevel;
    6975    }
    7076
     77    /**
     78     * Returns true if there should be a filter button for OSM primitives which have no value for the key.
     79     */
     80    public boolean getNoValueFilter() {
     81        return noValueFilter;
     82    }
     83
    7184    /**
    7285     * Formats the numeric value
    7386     * @param value the numeric value to format
    7487     * @return the formatted value
    7588     */
    76     public String formatValue(int value) {
    77         return valueFormatter.apply(value);
     89    public String formatValue(Integer value) {
     90        return value == null ? "∅" : valueFormatter.apply(value);
    7891    }
    7992
    8093    /**
    public class AutoFilterRule {  
    111124        return this;
    112125    }
    113126
     127    /**
     128     * Adds a filter button for OSM primitives which have no value for the key.
     129     */
     130    public AutoFilterRule enableNoValueFilter() {
     131        this.noValueFilter = true;
     132        return this;
     133    }
     134
     135    /**
     136     * Sets extra OSM keys on which the rule applies in addition to the primary key ({@link #getKey()}).
     137     * This allows a filter to look at the values of more than one key at the same time.
     138     * @param extraKeys the list of extra keys, may be empty
     139     * @return {@code this}
     140     * @throws NullPointerException if {@code extraKeys} is null
     141     */
     142    public AutoFilterRule setExtraKeys(List<String> extraKeys) {
     143        Objects.requireNonNull(extraKeys);
     144        this.allKeys = Stream.concat(Stream.of(key), extraKeys.stream()).collect(toList());
     145        return this;
     146    }
     147
    114148    /**
    115149     * Returns the numeric values for the given OSM primitive
    116      * @param osm the primitive
     150     *
     151     * @param osm              the primitive
     152     * @param directValuesOnly whether "inner" values from ranges (such as 6 and 7 for 5-8) should be omitted
    117153     * @return a stream of numeric values
    118154     */
    119     public IntStream getTagValuesForPrimitive(OsmPrimitive osm) {
     155    public IntStream getTagValuesForPrimitive(OsmPrimitive osm, boolean directValuesOnly) {
     156        if (osm.isDeleted()) return IntStream.empty();
     157        if (allKeys.size() == 1) {
     158            IntStream values = getTagValuesForPrimitive(osm, key, directValuesOnly);
     159            if (values != null) return values;
     160        } else {
     161            Set<Integer> allValues = new HashSet<>();
     162            for (String k : allKeys) {
     163                IntStream values = getTagValuesForPrimitive(osm, k, directValuesOnly);
     164                if (values != null) { values.forEach(allValues::add); }
     165            }
     166            if (!allValues.isEmpty()) return allValues.stream().mapToInt(it -> it).sorted();
     167        }
     168        return Boolean.TRUE.equals(PROP_AUTO_FILTER_DEFAULTS.get()) ? defaultValueSupplier.apply(osm) : IntStream.empty();
     169    }
     170
     171    private IntStream getTagValuesForPrimitive(OsmPrimitive osm, String key, boolean directValuesOnly) {
    120172        String value = osm.get(key);
    121173        if (value != null) {
    122174            Pattern p = Pattern.compile("(-?[0-9]+)-(-?[0-9]+)");
    public class AutoFilterRule {  
    125177                if (m.matches()) {
    126178                    int a = valueExtractor.applyAsInt(m.group(1));
    127179                    int b = valueExtractor.applyAsInt(m.group(2));
    128                     return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
     180                    if (directValuesOnly && a != b) {
     181                        return IntStream.of(a, b).sorted();
     182                    } else {
     183                        return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
     184                    }
    129185                } else {
    130186                    try {
    131187                        return IntStream.of(valueExtractor.applyAsInt(v));
    public class AutoFilterRule {  
    136192                }
    137193            });
    138194        }
    139         return Boolean.TRUE.equals(PROP_AUTO_FILTER_DEFAULTS.get()) ? defaultValueSupplier.apply(osm) : IntStream.empty();
     195        return null;
    140196    }
    141197
    142198    /**
    public class AutoFilterRule {  
    156212            new AutoFilterRule("layer", 16)
    157213                    .setDefaultValueSupplier(AutoFilterRule::defaultLayer),
    158214            new AutoFilterRule("level", 17)
     215                .setExtraKeys(List.of("repeat_on"))
     216                .enableNoValueFilter()
    159217                // #17109, support values like 0.5 or 1.5 - level values are multiplied by 2 when parsing, values are divided by 2 for formatting
    160218                .setValueExtractor(s -> (int) (Double.parseDouble(s) * 2.))
    161219                .setValueFormatter(v -> DecimalFormat.getInstance(Locale.ROOT).format(v / 2.)),
  • src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
    index 595dbd9a9c..fc8553f65e 100644
    a b public class FilterTableModel extends AbstractTableModel implements SortableTabl  
    9898     * @since 14206
    9999     */
    100100    public void executeFilters(boolean force) {
    101         if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
     101        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() == null && (force || model.hasFilters())) {
    102102            model.executeFilters();
    103103            updateMap();
    104104        }
    public class FilterTableModel extends AbstractTableModel implements SortableTabl  
    111111     * @since 14206
    112112     */
    113113    public void executeFilters(Collection<? extends OsmPrimitive> primitives, boolean force) {
    114         if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
     114        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() == null && (force || model.hasFilters())) {
    115115            model.executeFilters(primitives);
    116116            updateMap();
    117117        }
  • src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java

    diff --git a/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java b/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java
    index c75c5fcfe9..f9dd0bf99d 100644
    a b public class DrawingPreference extends DefaultTabPreferenceSetting {  
    5656    private final JCheckBox inactive = new JCheckBox(tr("Draw inactive layers in other color"));
    5757    private final JCheckBox discardableKeys = new JCheckBox(tr("Display discardable keys"));
    5858    private final JCheckBox autoFilters = new JCheckBox(tr("Use auto filters"));
     59    private final JCheckBox autoFiltersHiding = new JCheckBox(tr("Hide auto-filtered elements"));
     60
    5961    private final JLabel lblRule = new JLabel(tr("Rule"));
    6062    private final JosmComboBox<AutoFilterRule> autoFilterRules = new JosmComboBox<>(
    6163            AutoFilterManager.getInstance().getAutoFilterRules().toArray(new AutoFilterRule[] {}));
    public class DrawingPreference extends DefaultTabPreferenceSetting {  
    149151        autoFilters.addActionListener(e -> {
    150152            lblRule.setEnabled(autoFilters.isSelected());
    151153            autoFilterRules.setEnabled(autoFilters.isSelected());
     154            autoFiltersHiding.setEnabled(autoFilters.isSelected());
    152155        });
    153156        autoFilterRules.setToolTipText("Rule defining which tag will provide automatic filters, below a certain zoom level");
    154157        autoFilterRules.setSelectedItem(AutoFilterManager.getInstance().getAutoFilterRule(AutoFilterManager.PROP_AUTO_FILTER_RULE.get()));
     158        autoFiltersHiding.setToolTipText(tr("Completely hide elements which do not match the automatic filter instead of merely disabling them"));
     159        autoFiltersHiding.setSelected(AutoFilterManager.PROP_AUTO_FILTER_HIDING.get());
    155160
    156161        JLabel performanceLabel = new JLabel(tr("Options that affect drawing performance"));
    157162
    public class DrawingPreference extends DefaultTabPreferenceSetting {  
    183188        panel.add(autoFilters, GBC.eop().insets(20, 0, 0, 0));
    184189        panel.add(lblRule, GBC.std().insets(40, 0, 0, 0));
    185190        panel.add(autoFilterRules, GBC.eop().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
     191        panel.add(autoFiltersHiding, GBC.eop().insets(40, 0, 0, 0));
    186192
    187193        ExpertToggleAction.addVisibilitySwitcher(performanceLabel);
    188194        ExpertToggleAction.addVisibilitySwitcher(useAntialiasing);
    public class DrawingPreference extends DefaultTabPreferenceSetting {  
    213219        Config.getPref().putBoolean("draw.helper-line", drawHelperLine.isSelected());
    214220        Config.getPref().putBoolean("display.discardable-keys", discardableKeys.isSelected());
    215221        AutoFilterManager.PROP_AUTO_FILTER_ENABLED.put(autoFilters.isSelected());
     222        AutoFilterManager.PROP_AUTO_FILTER_HIDING.put(autoFiltersHiding.isSelected());
    216223        AutoFilterManager.PROP_AUTO_FILTER_RULE.put(((AutoFilterRule) autoFilterRules.getSelectedItem()).getKey());
    217224        int vn = Config.getPref().getInt("mappaint.node.virtual-size", 8);
    218225        if (virtualNodes.isSelected()) {
  • test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java b/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java
    index d68b4bcaec..3d1914dcb6 100644
    a b import org.junit.jupiter.api.Test;  
    2222@I18n
    2323class AutoFilterRuleTest {
    2424    /**
    25      * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive}.
     25     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)}.
    2626     */
    2727    @Test
    2828    void testTagValuesForPrimitive() {
    class AutoFilterRuleTest {  
    3636        assertTagValuesForPrimitive(level, "way level=6-9", 12, 13, 14, 15, 16, 17, 18);
    3737        assertTagValuesForPrimitive(level, "way level=10;12-13", 20, 24, 25, 26);
    3838        assertTagValuesForPrimitive(level, "way level=0;0.5;1;1.5;2;2.5;3", 0, 1, 2, 3, 4, 5, 6);
     39        assertTagValuesForPrimitive(level, "node level=0 repeat_on=1;2", 0, 2, 4);
     40        assertTagValuesForPrimitive(level, "way level=4 repeat_on=4;5 layer=1", 8, 10);
    3941        assertEquals("0 0.5 1 1.5 2 2.5 3",
    4042                IntStream.of(0, 1, 2, 3, 4, 5, 6).mapToObj(level::formatValue).collect(Collectors.joining(" ")));
    4143    }
    4244
    4345    /**
    44      * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive} to deal with {@code %} of key {@code incline}.
     46     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)} to deal with {@code %} of key {@code incline}.
    4547     */
    4648    @Test
    4749    void testTagValuesForPrimitiveInclineUnit() {
    class AutoFilterRuleTest {  
    5254    }
    5355
    5456    /**
    55      * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive} provides sensible defaults, see #17496.
     57     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)} provides sensible defaults, see #17496.
    5658     */
    5759    @Test
    5860    void testTagValuesForPrimitivesDefaults() {
    class AutoFilterRuleTest {  
    6971
    7072    private void assertTagValuesForPrimitive(AutoFilterRule rule, String assertion, int... expected) {
    7173        final OsmPrimitive primitive = OsmUtils.createPrimitive(assertion);
    72         final int[] actual = rule.getTagValuesForPrimitive(primitive).toArray();
     74        final int[] actual = rule.getTagValuesForPrimitive(primitive, false).toArray();
    7375        assertArrayEquals(expected, actual);
    7476    }
    7577