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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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