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

Last change on this file since 13224 was 13208, checked in by Don-vip, 6 years ago

fix #15670 - fire a data set change event when applying filters (patch by cmuelle8)

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