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

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

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