source: josm/trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java@ 15851

Last change on this file since 15851 was 15851, checked in by Don-vip, 4 years ago

fix recent spotbugs/checkstyle/javadoc issues

File size: 15.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.autofilter;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Graphics2D;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.List;
12import java.util.Map;
13import java.util.NavigableSet;
14import java.util.Objects;
15import java.util.TreeMap;
16import java.util.TreeSet;
17import java.util.function.Consumer;
18
19import org.openstreetmap.josm.actions.mapmode.MapMode;
20import org.openstreetmap.josm.data.osm.BBox;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.Filter;
23import org.openstreetmap.josm.data.osm.FilterModel;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
26import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
27import org.openstreetmap.josm.data.osm.event.DataSetListener;
28import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
29import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
30import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
31import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
32import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
33import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
34import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
35import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
36import org.openstreetmap.josm.data.osm.search.SearchCompiler;
37import org.openstreetmap.josm.data.osm.search.SearchCompiler.MatchSupplier;
38import org.openstreetmap.josm.data.preferences.BooleanProperty;
39import org.openstreetmap.josm.data.preferences.StringProperty;
40import org.openstreetmap.josm.gui.MainApplication;
41import org.openstreetmap.josm.gui.MapFrame;
42import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.NavigatableComponent;
45import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
46import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
47import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
48import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
49import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
50import org.openstreetmap.josm.gui.layer.OsmDataLayer;
51import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
52import org.openstreetmap.josm.gui.widgets.OSDLabel;
53import org.openstreetmap.josm.spi.preferences.Config;
54import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
55import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
56
57/**
58 * The auto filter manager keeps track of registered auto filter rules and applies the active one on the fly,
59 * when the map contents, location or zoom changes.
60 * @since 12400
61 */
62public final class AutoFilterManager
63implements ZoomChangeListener, MapModeChangeListener, DataSetListener, PreferenceChangedListener, LayerChangeListener {
64
65 /**
66 * Property to determines if the auto filter feature is enabled.
67 */
68 public static final BooleanProperty PROP_AUTO_FILTER_ENABLED = new BooleanProperty("auto.filter.enabled", true);
69
70 /**
71 * Property to determine the current auto filter rule.
72 */
73 public static final StringProperty PROP_AUTO_FILTER_RULE = new StringProperty("auto.filter.rule", "level");
74
75 /**
76 * The unique instance.
77 */
78 private static volatile AutoFilterManager instance;
79
80 /**
81 * The buttons currently displayed in map view.
82 */
83 private final Map<Integer, AutoFilterButton> buttons = new TreeMap<>();
84
85 /**
86 * The list of registered auto filter rules.
87 */
88 private final List<AutoFilterRule> rules = new ArrayList<>();
89
90 /**
91 * A helper for {@link #drawOSDText(Graphics2D)}.
92 */
93 private final OSDLabel lblOSD = new OSDLabel("");
94
95 /**
96 * The filter model.
97 */
98 private final FilterModel model = new FilterModel();
99
100 /**
101 * The currently enabled rule, if any.
102 */
103 AutoFilterRule enabledRule;
104
105 /**
106 * The currently selected auto filter, if any.
107 */
108 private AutoFilter currentAutoFilter;
109
110 /**
111 * Returns the unique instance.
112 * @return the unique instance
113 */
114 public static AutoFilterManager getInstance() {
115 if (instance == null) {
116 instance = new AutoFilterManager();
117 }
118 return instance;
119 }
120
121 private AutoFilterManager() {
122 MapFrame.addMapModeChangeListener(this);
123 Config.getPref().addPreferenceChangeListener(this);
124 NavigatableComponent.addZoomChangeListener(this);
125 MainApplication.getLayerManager().addLayerChangeListener(this);
126 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
127 registerAutoFilterRules(AutoFilterRule.defaultRules());
128 }
129
130 private synchronized void updateButtons() {
131 MapFrame map = MainApplication.getMap();
132 if (enabledRule != null && map != null
133 && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(map.mapView.getDist100Pixel())) {
134 // Retrieve the values from current rule visible on screen
135 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);
139 }
140 if (!values.equals(buttons.keySet())) {
141 removeAllButtons();
142 addNewButtons(values);
143 }
144 }
145 }
146
147 static class CompiledFilter extends Filter implements MatchSupplier {
148 final AutoFilterRule rule;
149 final int value;
150
151 CompiledFilter(AutoFilterRule rule, int value) {
152 this.rule = rule;
153 this.value = value;
154 this.enable = true;
155 this.inverted = true;
156 this.text = rule.getKey() + "=" + value;
157 }
158
159 @Override
160 public SearchCompiler.Match get() {
161 return new SearchCompiler.Match() {
162 @Override
163 public boolean match(OsmPrimitive osm) {
164 return rule.getTagValuesForPrimitive(osm).anyMatch(v -> v == value);
165 }
166 };
167 }
168 }
169
170 private synchronized void addNewButtons(NavigableSet<Integer> values) {
171 if (values.isEmpty()) {
172 return;
173 }
174 int i = 0;
175 int maxWidth = 16;
176 final AutoFilterButton keyButton = AutoFilterButton.forOsmKey(enabledRule.getKey());
177 addButton(keyButton, Integer.MIN_VALUE, i++);
178 for (final Integer value : values.descendingSet()) {
179 CompiledFilter filter = new CompiledFilter(enabledRule, value);
180 String label = enabledRule.formatValue(value);
181 AutoFilter autoFilter = new AutoFilter(label, filter.text, filter);
182 AutoFilterButton button = new AutoFilterButton(autoFilter);
183 if (autoFilter.equals(currentAutoFilter)) {
184 button.getModel().setPressed(true);
185 }
186 maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
187 addButton(button, value, i++);
188 }
189 for (AutoFilterButton b : buttons.values()) {
190 b.setSize(b == keyButton ? b.getPreferredSize().width : maxWidth, 20);
191 }
192 MainApplication.getMap().mapView.validate();
193 }
194
195 private void addButton(AutoFilterButton button, int value, int i) {
196 MapView mapView = MainApplication.getMap().mapView;
197 buttons.put(value, button);
198 mapView.add(button).setLocation(3, 60 + 22*i);
199 }
200
201 private void removeAllButtons() {
202 buttons.values().forEach(MainApplication.getMap().mapView::remove);
203 buttons.clear();
204 }
205
206 private synchronized NavigableSet<Integer> getNumericValues() {
207 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
208 if (ds == null) {
209 return Collections.emptyNavigableSet();
210 }
211 BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
212 NavigableSet<Integer> values = new TreeSet<>();
213 Consumer<OsmPrimitive> consumer = o -> enabledRule.getTagValuesForPrimitive(o).forEach(values::add);
214 ds.searchNodes(bbox).forEach(consumer);
215 ds.searchWays(bbox).forEach(consumer);
216 ds.searchRelations(bbox).forEach(consumer);
217 return values;
218 }
219
220 @Override
221 public void zoomChanged() {
222 updateButtons();
223 }
224
225 @Override
226 public void dataChanged(DataChangedEvent event) {
227 updateFiltersFull();
228 }
229
230 @Override
231 public void nodeMoved(NodeMovedEvent event) {
232 updateFiltersFull();
233 }
234
235 @Override
236 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
237 updateFiltersFull();
238 }
239
240 @Override
241 public void primitivesAdded(PrimitivesAddedEvent event) {
242 updateFiltersEvent(event, false);
243 updateButtons();
244 }
245
246 @Override
247 public void primitivesRemoved(PrimitivesRemovedEvent event) {
248 updateFiltersFull();
249 updateButtons();
250 }
251
252 @Override
253 public void relationMembersChanged(RelationMembersChangedEvent event) {
254 updateFiltersEvent(event, true);
255 }
256
257 @Override
258 public void tagsChanged(TagsChangedEvent event) {
259 updateFiltersEvent(event, true);
260 updateButtons();
261 }
262
263 @Override
264 public void wayNodesChanged(WayNodesChangedEvent event) {
265 updateFiltersEvent(event, true);
266 }
267
268 @Override
269 public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
270 updateFiltersFull();
271 }
272
273 private synchronized void updateFiltersFull() {
274 if (currentAutoFilter != null) {
275 model.executeFilters();
276 }
277 }
278
279 private synchronized void updateFiltersEvent(AbstractDatasetChangedEvent event, boolean affectedOnly) {
280 if (currentAutoFilter != null) {
281 Collection<? extends OsmPrimitive> prims = event.getPrimitives();
282 model.executeFilters(affectedOnly ? FilterModel.getAffectedPrimitives(prims) : prims);
283 }
284 }
285
286 /**
287 * Registers new auto filter rule(s).
288 * @param filterRules new auto filter rules. Must not be null
289 * @return {@code true} if the list changed as a result of the call
290 * @throws NullPointerException if {@code filterRules} is null
291 */
292 public synchronized boolean registerAutoFilterRules(AutoFilterRule... filterRules) {
293 return rules.addAll(Arrays.asList(filterRules));
294 }
295
296 /**
297 * Unregisters an auto filter rule.
298 * @param rule auto filter rule to remove. Must not be null
299 * @return {@code true} if the list contained the specified rule
300 * @throws NullPointerException if {@code rule} is null
301 */
302 public synchronized boolean unregisterAutoFilterRule(AutoFilterRule rule) {
303 return rules.remove(Objects.requireNonNull(rule, "rule"));
304 }
305
306 /**
307 * Returns the list of registered auto filter rules.
308 * @return the list of registered rules
309 */
310 public synchronized List<AutoFilterRule> getAutoFilterRules() {
311 return new ArrayList<>(rules);
312 }
313
314 /**
315 * Returns the auto filter rule defined for the given OSM key.
316 * @param key OSM key used to identify rule. Can't be null.
317 * @return the auto filter rule defined for the given OSM key, or null
318 * @throws NullPointerException if key is null
319 */
320 public synchronized AutoFilterRule getAutoFilterRule(String key) {
321 for (AutoFilterRule r : rules) {
322 if (key.equals(r.getKey())) {
323 return r;
324 }
325 }
326 return null;
327 }
328
329 /**
330 * Sets the currently enabled auto filter rule to the one defined for the given OSM key.
331 * @param key OSM key used to identify new rule to enable. Null to disable the auto filter feature.
332 */
333 public synchronized void enableAutoFilterRule(String key) {
334 enableAutoFilterRule(key == null ? null : getAutoFilterRule(key));
335 }
336
337 /**
338 * Sets the currently enabled auto filter rule.
339 * @param rule new rule to enable. Null to disable the auto filter feature.
340 */
341 public synchronized void enableAutoFilterRule(AutoFilterRule rule) {
342 enabledRule = rule;
343 }
344
345 /**
346 * Returns the currently selected auto filter, if any.
347 * @return the currently selected auto filter, or null
348 */
349 public synchronized AutoFilter getCurrentAutoFilter() {
350 return currentAutoFilter;
351 }
352
353 /**
354 * Sets the currently selected auto filter, if any.
355 * @param autoFilter the currently selected auto filter, or null
356 */
357 public synchronized void setCurrentAutoFilter(AutoFilter autoFilter) {
358 model.clearFilters();
359 currentAutoFilter = autoFilter;
360 if (autoFilter != null) {
361 model.addFilter(autoFilter.getFilter());
362 model.executeFilters();
363 if (model.isChanged()) {
364 OsmDataLayer dataLayer = MainApplication.getLayerManager().getActiveDataLayer();
365 if (dataLayer != null) {
366 dataLayer.invalidate();
367 }
368 }
369 }
370 }
371
372 /**
373 * Draws a text on the map display that indicates that filters are active.
374 * @param g The graphics to draw that text on.
375 */
376 public synchronized void drawOSDText(Graphics2D g) {
377 model.drawOSDText(g, lblOSD,
378 tr("<h2>Filter active: {0}</h2>", currentAutoFilter.getFilter().text),
379 tr("</p><p>Click again on filter button to see all objects.</p></html>"));
380 }
381
382 private void resetCurrentAutoFilter() {
383 setCurrentAutoFilter(null);
384 removeAllButtons();
385 MapFrame map = MainApplication.getMap();
386 if (map != null) {
387 map.filterDialog.getFilterModel().executeFilters(true);
388 }
389 }
390
391 @Override
392 public void preferenceChanged(PreferenceChangeEvent e) {
393 if (e.getKey().equals(PROP_AUTO_FILTER_ENABLED.getKey())) {
394 if (PROP_AUTO_FILTER_ENABLED.get()) {
395 enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
396 updateButtons();
397 } else {
398 enableAutoFilterRule((AutoFilterRule) null);
399 resetCurrentAutoFilter();
400 }
401 } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())) {
402 enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
403 resetCurrentAutoFilter();
404 updateButtons();
405 }
406 }
407
408 @Override
409 public void layerAdded(LayerAddEvent e) {
410 // Do nothing
411 }
412
413 @Override
414 public void layerRemoving(LayerRemoveEvent e) {
415 if (MainApplication.getLayerManager().getActiveDataLayer() == null) {
416 resetCurrentAutoFilter();
417 }
418 }
419
420 @Override
421 public void layerOrderChanged(LayerOrderChangeEvent e) {
422 // Do nothing
423 }
424}
Note: See TracBrowser for help on using the repository browser.