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

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

fix #16785 - fix IOOBE

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