source: josm/trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java@ 16445

Last change on this file since 16445 was 16445, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

File size: 13.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Graphics2D;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashSet;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Set;
15import java.util.Stack;
16import java.util.stream.Collectors;
17
18import javax.swing.JOptionPane;
19
20import org.openstreetmap.josm.data.SortableModel;
21import org.openstreetmap.josm.data.StructUtils;
22import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
23import org.openstreetmap.josm.data.osm.search.SearchParseError;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.widgets.OSDLabel;
26import org.openstreetmap.josm.spi.preferences.Config;
27import org.openstreetmap.josm.tools.Logging;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * The model that is used both for auto and manual filters.
32 * @since 12400
33 */
34public class FilterModel implements SortableModel<Filter> {
35
36 /**
37 * number of primitives that are disabled but not hidden
38 */
39 private int disabledCount;
40 /**
41 * number of primitives that are disabled and hidden
42 */
43 private int disabledAndHiddenCount;
44 /**
45 * true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process
46 */
47 private boolean changed;
48
49 private final List<Filter> filters = new LinkedList<>();
50 private final FilterMatcher filterMatcher = new FilterMatcher();
51
52 private void updateFilterMatcher() {
53 filterMatcher.reset();
54 for (Filter filter : filters) {
55 try {
56 filterMatcher.add(filter);
57 } catch (SearchParseError e) {
58 Logging.error(e);
59 JOptionPane.showMessageDialog(
60 MainApplication.getMainFrame(),
61 tr("<html>Error in filter <code>{0}</code>:<br>{1}",
62 Utils.escapeReservedCharactersHTML(Utils.shortenString(filter.text, 80)),
63 Utils.escapeReservedCharactersHTML(e.getMessage())),
64 tr("Error in filter"),
65 JOptionPane.ERROR_MESSAGE);
66 filter.enable = false;
67 }
68 }
69 }
70
71 /**
72 * Initializes the model from preferences.
73 * @param prefEntry preference key
74 */
75 public void loadPrefs(String prefEntry) {
76 List<FilterPreferenceEntry> entries = StructUtils.getListOfStructs(
77 Config.getPref(), prefEntry, null, FilterPreferenceEntry.class);
78 if (entries != null) {
79 for (FilterPreferenceEntry e : entries) {
80 filters.add(new Filter(e));
81 }
82 updateFilterMatcher();
83 }
84 }
85
86 /**
87 * Saves the model to preferences.
88 * @param prefEntry preferences key
89 */
90 public void savePrefs(String prefEntry) {
91 Collection<FilterPreferenceEntry> entries = filters.stream()
92 .map(Filter::getPreferenceEntry)
93 .collect(Collectors.toList());
94 StructUtils.putListOfStructs(Config.getPref(), prefEntry, entries, FilterPreferenceEntry.class);
95 }
96
97 /**
98 * Runs the filters on the current edit data set.
99 */
100 public void executeFilters() {
101 DataSet ds = OsmDataManager.getInstance().getActiveDataSet();
102 changed = false;
103 if (ds == null) {
104 disabledAndHiddenCount = 0;
105 disabledCount = 0;
106 changed = true;
107 } else {
108 final Collection<OsmPrimitive> deselect = new HashSet<>();
109
110 ds.beginUpdate();
111 try {
112 final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives();
113
114 changed = FilterWorker.executeFilters(all, filterMatcher);
115
116 disabledCount = 0;
117 disabledAndHiddenCount = 0;
118 // collect disabled and selected the primitives
119 for (OsmPrimitive osm : all) {
120 if (osm.isDisabled()) {
121 disabledCount++;
122 if (osm.isSelected()) {
123 deselect.add(osm);
124 }
125 if (osm.isDisabledAndHidden()) {
126 disabledAndHiddenCount++;
127 }
128 }
129 }
130 disabledCount -= disabledAndHiddenCount;
131 } finally {
132 if (changed) {
133 ds.fireFilterChanged();
134 }
135 ds.endUpdate();
136 }
137
138 if (!deselect.isEmpty()) {
139 ds.clearSelection(deselect);
140 }
141 }
142 if (changed) {
143 updateMap();
144 }
145 }
146
147 /**
148 * Runs the filter on a list of primitives that are part of the edit data set.
149 * @param primitives The primitives
150 */
151 public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
152 DataSet ds = OsmDataManager.getInstance().getActiveDataSet();
153 if (ds == null)
154 return;
155
156 changed = false;
157 List<OsmPrimitive> deselect = new ArrayList<>();
158
159 ds.update(() -> {
160 for (int i = 0; i < 2; i++) {
161 for (OsmPrimitive primitive: primitives) {
162
163 if (i == 0 && primitive instanceof Node) {
164 continue;
165 }
166
167 if (i == 1 && !(primitive instanceof Node)) {
168 continue;
169 }
170
171 if (primitive.isDisabled()) {
172 disabledCount--;
173 }
174 if (primitive.isDisabledAndHidden()) {
175 disabledAndHiddenCount--;
176 }
177 changed |= FilterWorker.executeFilters(primitive, filterMatcher);
178 if (primitive.isDisabled()) {
179 disabledCount++;
180 }
181 if (primitive.isDisabledAndHidden()) {
182 disabledAndHiddenCount++;
183 }
184
185 if (primitive.isSelected() && primitive.isDisabled()) {
186 deselect.add(primitive);
187 }
188 }
189 }
190 });
191
192 if (!deselect.isEmpty()) {
193 ds.clearSelection(deselect);
194 }
195 if (changed) {
196 updateMap();
197 }
198 }
199
200 private static void updateMap() {
201 MainApplication.getLayerManager().invalidateEditLayer();
202 }
203
204 /**
205 * Clears all filtered flags from all primitives in the dataset
206 */
207 public void clearFilterFlags() {
208 DataSet ds = OsmDataManager.getInstance().getActiveDataSet();
209 if (ds != null) {
210 FilterWorker.clearFilterFlags(ds.allPrimitives());
211 }
212 disabledCount = 0;
213 disabledAndHiddenCount = 0;
214 }
215
216 /**
217 * Removes all filters from this model.
218 */
219 public void clearFilters() {
220 filters.clear();
221 updateFilterMatcher();
222 }
223
224 /**
225 * Adds a new filter to the filter list.
226 * @param filter The new filter
227 * @return true (as specified by {@link Collection#add})
228 */
229 public boolean addFilter(Filter filter) {
230 filters.add(filter);
231 updateFilterMatcher();
232 return true;
233 }
234
235 /**
236 * Moves the filters in the given rows by a number of positions.
237 * @param delta negative or positive increment
238 * @param rowIndexes The filter rows
239 * @return true if the filters have been moved down
240 * @since 15226
241 */
242 public boolean moveFilters(int delta, int... rowIndexes) {
243 if (!canMove(delta, filters::size, rowIndexes))
244 return false;
245 doMove(delta, rowIndexes);
246 updateFilterMatcher();
247 return true;
248 }
249
250 /**
251 * Moves down the filter in the given row.
252 * @param rowIndex The filter row
253 * @return true if the filter has been moved down
254 */
255 public boolean moveDownFilter(int rowIndex) {
256 return moveFilters(1, rowIndex);
257 }
258
259 /**
260 * Moves up the filter in the given row
261 * @param rowIndex The filter row
262 * @return true if the filter has been moved up
263 */
264 public boolean moveUpFilter(int rowIndex) {
265 return moveFilters(-1, rowIndex);
266 }
267
268 /**
269 * Removes the filter that is displayed in the given row
270 * @param rowIndex The index of the filter to remove
271 * @return the filter previously at the specified position
272 */
273 public Filter removeFilter(int rowIndex) {
274 Filter result = filters.remove(rowIndex);
275 updateFilterMatcher();
276 return result;
277 }
278
279 @Override
280 public Filter setValue(int rowIndex, Filter filter) {
281 Filter result = filters.set(rowIndex, filter);
282 updateFilterMatcher();
283 return result;
284 }
285
286 @Override
287 public Filter getValue(int rowIndex) {
288 return filters.get(rowIndex);
289 }
290
291 /**
292 * Draws a text on the map display that indicates that filters are active.
293 * @param g The graphics to draw that text on.
294 * @param lblOSD On Screen Display label
295 * @param header The title to display at the beginning of OSD
296 * @param footer The message to display at the bottom of OSD. Must end by {@code </html>}
297 */
298 public void drawOSDText(Graphics2D g, OSDLabel lblOSD, String header, String footer) {
299 if (disabledCount == 0 && disabledAndHiddenCount == 0)
300 return;
301
302 String message = "<html>" + header;
303
304 if (disabledAndHiddenCount != 0) {
305 /* for correct i18n of plural forms - see #9110 */
306 message += trn("<p><b>{0}</b> object hidden", "<p><b>{0}</b> objects hidden", disabledAndHiddenCount, disabledAndHiddenCount);
307 }
308
309 if (disabledAndHiddenCount != 0 && disabledCount != 0) {
310 message += "<br>";
311 }
312
313 if (disabledCount != 0) {
314 /* for correct i18n of plural forms - see #9110 */
315 message += trn("<b>{0}</b> object disabled", "<b>{0}</b> objects disabled", disabledCount, disabledCount);
316 }
317
318 message += footer;
319
320 lblOSD.setText(message);
321 lblOSD.setSize(lblOSD.getPreferredSize());
322
323 int dx = MainApplication.getMap().mapView.getWidth() - lblOSD.getPreferredSize().width - 15;
324 int dy = 15;
325 g.translate(dx, dy);
326 lblOSD.paintComponent(g);
327 g.translate(-dx, -dy);
328 }
329
330 /**
331 * Returns the list of filters.
332 * @return the list of filters
333 */
334 public List<Filter> getFilters() {
335 return new ArrayList<>(filters);
336 }
337
338 /**
339 * Returns the number of filters.
340 * @return the number of filters
341 */
342 public int getFiltersCount() {
343 return filters.size();
344 }
345
346 /**
347 * Returns the number of primitives that are disabled but not hidden.
348 * @return the number of primitives that are disabled but not hidden
349 */
350 public int getDisabledCount() {
351 return disabledCount;
352 }
353
354 /**
355 * Returns the number of primitives that are disabled and hidden.
356 * @return the number of primitives that are disabled and hidden
357 */
358 public int getDisabledAndHiddenCount() {
359 return disabledAndHiddenCount;
360 }
361
362 /**
363 * Determines if the filter state (normal / disabled / hidden) of any primitive has changed in the process.
364 * @return true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process
365 */
366 public boolean isChanged() {
367 return changed;
368 }
369
370 /**
371 * Determines if at least one filter is enabled.
372 * @return {@code true} if at least one filter is enabled
373 * @since 14206
374 */
375 public boolean hasFilters() {
376 return filterMatcher.hasFilters();
377 }
378
379 /**
380 * Returns the list of primitives whose filtering can be affected by change in primitive
381 * @param primitives list of primitives to check
382 * @return List of primitives whose filtering can be affected by change in source primitives
383 */
384 public static Collection<OsmPrimitive> getAffectedPrimitives(Collection<? extends OsmPrimitive> primitives) {
385 // Filters can use nested parent/child expression so complete tree is necessary
386 Set<OsmPrimitive> result = new HashSet<>();
387 Stack<OsmPrimitive> stack = new Stack<>();
388 stack.addAll(primitives);
389
390 while (!stack.isEmpty()) {
391 OsmPrimitive p = stack.pop();
392
393 if (result.contains(p)) {
394 continue;
395 }
396
397 result.add(p);
398
399 if (p instanceof Way) {
400 for (OsmPrimitive n: ((Way) p).getNodes()) {
401 stack.push(n);
402 }
403 } else if (p instanceof Relation) {
404 for (RelationMember rm: ((Relation) p).getMembers()) {
405 stack.push(rm.getMember());
406 }
407 }
408
409 for (OsmPrimitive ref: p.getReferrers()) {
410 stack.push(ref);
411 }
412 }
413
414 return result;
415 }
416
417 @Override
418 public void sort() {
419 Collections.sort(filters);
420 updateFilterMatcher();
421 }
422
423 @Override
424 public void reverse() {
425 Collections.reverse(filters);
426 updateFilterMatcher();
427 }
428}
Note: See TracBrowser for help on using the repository browser.