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

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

see #14929 - fix NPE in unit tests

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