source: josm/trunk/src/org/openstreetmap/josm/data/osm/Filters.java@ 3354

Last change on this file since 3354 was 3354, checked in by jttt, 14 years ago

Fix #5193 josm got very slow recently (reverted fix for #5018)

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1package org.openstreetmap.josm.data.osm;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4import static org.openstreetmap.josm.tools.I18n.trc;
5
6import java.awt.Color;
7import java.awt.Font;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Map;
15
16import javax.swing.BorderFactory;
17import javax.swing.JLabel;
18import javax.swing.table.AbstractTableModel;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.actions.search.SearchAction;
22import org.openstreetmap.josm.tools.Property;
23
24/**
25 *
26 * @author Petr_Dlouhý
27 */
28public class Filters extends AbstractTableModel {
29
30 // number of primitives that are disabled but not hidden
31 public int disabledCount;
32 // number of primitives that are disabled and hidden
33 public int disabledAndHiddenCount;
34
35 public Filters() {
36 loadPrefs();
37 }
38
39 private List<Filter> filters = new LinkedList<Filter>();
40
41 /**
42 * Apply the filters to the primitives of the data set.
43 *
44 * There are certain rules to ensure that a way is not displayed "naked"
45 * without its nodes (1) and on the other hand to avoid hiding a way but
46 * leaving its nodes visible as a cloud of points (2).
47 *
48 * In normal (non-inverted) mode only problem (2) is relevant.
49 * Untagged child nodes of filtered ways that are not used by other
50 * unfiltered ways are filtered as well.
51 *
52 * If a filter applies explicitly to a node, (2) is ignored and it
53 * is filtered in any case.
54 *
55 * In inverted mode usually only problem (1) is relevant.
56 * If the inverted filter applies explicitly to a node, this no longer
57 * means it is filtered in any case:
58 * E.g. the filter [searchtext="highway=footway", inverted=true] displays
59 * the footways only. But that does not mean, the nodes of the footway
60 * (which do not have the highway tag) should be filtered as well.
61 *
62 * So first the Filter is applied for ways and relations. Then to nodes
63 * (but hides them only if they are not used by any unfiltered way).
64 */
65 public void executeFilters() {
66 DataSet ds = Main.main.getCurrentDataSet();
67 if (ds == null)
68 return;
69
70 final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives();
71 // temporary set to collect the primitives returned by the search engine
72 final Collection<OsmPrimitive> collect = new HashSet<OsmPrimitive>();
73
74 // an auxiliary property to collect the results of the search engine
75 class CollectProperty implements Property<OsmPrimitive,Boolean> {
76 boolean collectValue;
77 boolean hidden;
78
79 /**
80 * Depending on the parameters, there are 4 different instances
81 * of this class.
82 *
83 * @param collectValue
84 * If true: collect only those primitives that are added
85 * by the search engine.
86 * If false: Collect only those primitives that are removed
87 * by the search engine.
88 * @param hidden Whether the property refers to primitives that
89 * are disabled and hidden or to primitives
90 * that are disabled only.
91 */
92 public CollectProperty(boolean collectValue, boolean hidden) {
93 this.collectValue = collectValue;
94 this.hidden = hidden;
95 }
96
97 public Boolean get(OsmPrimitive osm) {
98 if (hidden)
99 return osm.isDisabledAndHidden();
100 else
101 return osm.isDisabled();
102 }
103
104 public void set(OsmPrimitive osm, Boolean value) {
105 if (collectValue == value.booleanValue()) {
106 collect.add(osm);
107 }
108 }
109 }
110
111 clearFilterFlags();
112
113 for (Filter flt : filters){
114 if (flt.enable) {
115 collect.clear();
116 // Decide, whether primitives are collected that are added to the current
117 // selection or those that are removed from the current selection
118 boolean collectValue = flt.mode == SearchAction.SearchMode.replace || flt.mode == SearchAction.SearchMode.add;
119 Property<OsmPrimitive,Boolean> collectProp = new CollectProperty(collectValue, flt.hiding);
120
121 SearchAction.getSelection(flt, all, collectProp);
122
123 switch (flt.mode) {
124 case replace:
125 for (OsmPrimitive osm : all) {
126 osm.unsetDisabledState();
127 }
128 case add:
129 if (!flt.inverted) {
130 for (OsmPrimitive osm : collect) {
131 osm.setDisabledState(flt.hiding);
132 }
133
134 // Find child nodes of hidden ways and add them to the hidden nodes
135 for (OsmPrimitive osm : collect) {
136 if (osm instanceof Way) {
137 nodes:
138 for (Node n : ((Way)osm).getNodes()) {
139 // if node is already disabled, there is nothing to do
140 if (n.isDisabledAndHidden() || (!flt.hiding && n.isDisabled())) {
141 continue;
142 }
143
144 // if the node is tagged, don't disable it
145 if (n.isTagged()) {
146 continue;
147 }
148
149 // if the node has undisabled parent ways, don't disable it
150 for (OsmPrimitive ref : n.getReferrers()) {
151 if (ref instanceof Way) {
152 if (!ref.isDisabled()) {
153 continue nodes;
154 }
155 if (flt.hiding && !ref.isDisabledAndHidden()) {
156 continue nodes;
157 }
158 }
159 }
160 n.setDisabledState(flt.hiding);
161 }
162 }
163 }
164 } else { // inverted filter in add mode
165 // update flags, except for nodes
166 for (OsmPrimitive osm : collect) {
167 if (!(osm instanceof Node)) {
168 osm.setDisabledState(flt.hiding);
169 }
170 }
171
172 // update flags for nodes
173 nodes:
174 for (OsmPrimitive osm : collect) {
175 if (osm instanceof Node) {
176 // if node is already disabled, there is nothing to do
177 if (osm.isDisabledAndHidden() || (!flt.hiding && osm.isDisabled())) {
178 continue;
179 }
180
181 // if the node has undisabled parent ways, don't disable it
182 for (OsmPrimitive ref : osm.getReferrers()) {
183 if (ref instanceof Way) {
184 if (!ref.isDisabled()) {
185 continue nodes;
186 }
187 if (flt.hiding && !ref.isDisabledAndHidden()) {
188 continue nodes;
189 }
190 }
191 }
192 osm.setDisabledState(flt.hiding);
193 }
194 }
195 }
196 break;
197 case remove:
198 case in_selection:
199 if (!flt.inverted) {
200 // make the described primitive undisabled again
201 for (OsmPrimitive osm : collect) {
202 osm.unsetDisabledState();
203 }
204
205 // Undisable the child nodes of undisabled ways
206 for (OsmPrimitive osm : collect) {
207 if (osm instanceof Way) {
208 for (Node n : ((Way) osm).getNodes()) {
209 n.unsetDisabledState();
210 }
211 }
212 }
213 } else { // inverted filter in remove mode
214 // make the described primitive undisabled again
215 for (OsmPrimitive osm : collect) {
216 osm.unsetDisabledState();
217 }
218
219 // Undisable the child nodes of undisabled ways
220 for (OsmPrimitive osm : collect) {
221 if (osm instanceof Way) {
222 for (Node n : ((Way) osm).getNodes()) {
223 n.unsetDisabledState();
224 }
225 }
226 }
227 }
228 break;
229 default:
230 throw new IllegalStateException();
231 }
232 }
233 }
234
235 disabledCount = 0;
236 disabledAndHiddenCount = 0;
237 // collect disabled and selected the primitives
238 final Collection<OsmPrimitive> deselect = new HashSet<OsmPrimitive>();
239 for (OsmPrimitive osm : all) {
240 if (osm.isDisabled()) {
241 disabledCount++;
242 if (osm.isSelected()) {
243 deselect.add(osm);
244 }
245 if (osm.isDisabledAndHidden()) {
246 disabledAndHiddenCount++;
247 }
248 }
249 }
250 disabledCount -= disabledAndHiddenCount;
251 if (!deselect.isEmpty()) {
252 ds.clearSelection(deselect);
253 }
254
255 Main.map.mapView.repaint();
256 Main.map.filterDialog.updateDialogHeader();
257 }
258
259 public void clearFilterFlags() {
260 DataSet ds = Main.main.getCurrentDataSet();
261 if (ds != null) {
262 for (OsmPrimitive osm : ds.allPrimitives()) {
263 osm.unsetDisabledState();
264 }
265 }
266 disabledCount = 0;
267 disabledAndHiddenCount = 0;
268 }
269
270 private void loadPrefs() {
271 Map<String,String> prefs = Main.pref.getAllPrefix("filters.filter");
272 for (String value : prefs.values()) {
273 filters.add(new Filter(value));
274 }
275 }
276
277 private void savePrefs(){
278 Map<String,String> prefs = Main.pref.getAllPrefix("filters.filter");
279 for (String key : prefs.keySet()) {
280 String[] sts = key.split("\\.");
281 if (sts.length != 3)throw new Error("Incompatible filter preferences");
282 Main.pref.put("filters.filter." + sts[2], null);
283 }
284
285 int i = 0;
286 for (Filter flt : filters){
287 Main.pref.put("filters.filter." + i++, flt.getPrefString());
288 }
289 }
290
291 private void savePref(int i){
292 if(i >= filters.size()) {
293 Main.pref.put("filters.filter." + i, null);
294 } else {
295 Main.pref.put("filters.filter." + i, filters.get(i).getPrefString());
296 }
297 }
298
299 public void addFilter(Filter f){
300 filters.add(f);
301 savePref(filters.size()-1);
302 executeFilters();
303 fireTableRowsInserted(filters.size()-1, filters.size()-1);
304 }
305
306 public void moveDownFilter(int i){
307 if(i >= filters.size()-1) return;
308 filters.add(i+1, filters.remove(i));
309 savePref(i);
310 savePref(i+1);
311 executeFilters();
312 fireTableRowsUpdated(i, i+1);
313 }
314
315 public void moveUpFilter(int i){
316 if(i == 0) return;
317 filters.add(i-1, filters.remove(i));
318 savePref(i);
319 savePref(i-1);
320 executeFilters();
321 fireTableRowsUpdated(i-1, i);
322 }
323
324 public void removeFilter(int i){
325 filters.remove(i);
326 savePrefs();
327 executeFilters();
328 fireTableRowsDeleted(i, i);
329 }
330
331 public void setFilter(int i, Filter f){
332 filters.set(i, f);
333 savePref(i);
334 executeFilters();
335 fireTableRowsUpdated(i, i);
336 }
337
338 public Filter getFilter(int i){
339 return filters.get(i);
340 }
341
342 public int getRowCount(){
343 return filters.size();
344 }
345
346 public int getColumnCount(){
347 return 5;
348 }
349
350 @Override
351 public String getColumnName(int column){
352 String[] names = { /* translators notes must be in front */
353 /* column header: enable filter */ trc("filter","E"),
354 /* column header: hide filter */ trc("filter", "H"),
355 /* column header: filter text */ trc("filter", "Text"),
356 /* column header: inverted filter */ trc("filter", "I"),
357 /* column header: filter mode */ trc("filter", "M")
358 };
359 return names[column];
360 }
361
362 @Override
363 public Class<?> getColumnClass(int column){
364 Class<?>[] classes = { Boolean.class, Boolean.class, String.class, Boolean.class, String.class };
365 return classes[column];
366 }
367
368 public boolean isCellEnabled(int row, int column){
369 if(!filters.get(row).enable && column!=0) return false;
370 return true;
371 }
372
373 @Override
374 public boolean isCellEditable(int row, int column){
375 if(!filters.get(row).enable && column!=0) return false;
376 if(column < 4)return true;
377 return false;
378 }
379
380 @Override
381 public void setValueAt(Object aValue, int row, int column){
382 Filter f = filters.get(row);
383 switch(column){
384 case 0:
385 f.enable = (Boolean)aValue;
386 savePref(row);
387 executeFilters();
388 fireTableRowsUpdated(row, row);
389 break;
390 case 1:
391 f.hiding = (Boolean)aValue;
392 savePref(row);
393 executeFilters();
394 break;
395 case 2:
396 f.text = (String)aValue;
397 savePref(row);
398 break;
399 case 3:
400 f.inverted = (Boolean)aValue;
401 savePref(row);
402 executeFilters();
403 break;
404 }
405 if(column!=0) {
406 fireTableCellUpdated(row, column);
407 }
408 }
409
410 public Object getValueAt(int row, int column){
411 Filter f = filters.get(row);
412 switch(column){
413 case 0: return f.enable;
414 case 1: return f.hiding;
415 case 2: return f.text;
416 case 3: return f.inverted;
417 case 4:
418 switch(f.mode){ /* translators notes must be in front */
419 case replace: /* filter mode: replace */ return trc("filter", "R");
420 case add: /* filter mode: add */ return trc("filter", "A");
421 case remove: /* filter mode: remove */ return trc("filter", "D");
422 case in_selection: /* filter mode: in selection */ return trc("filter", "F");
423 }
424 }
425 return null;
426 }
427
428 /**
429 * On screen display label
430 */
431 private static class OSDLabel extends JLabel {
432 public OSDLabel(String text) {
433 super(text);
434 setOpaque(true);
435 setForeground(Color.black);
436 setBackground(new Color(0,0,0,0));
437 setFont(getFont().deriveFont(Font.PLAIN));
438 setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
439 }
440
441 @Override
442 public void paintComponent(Graphics g) {
443 g.setColor(new Color(255, 255, 255, 140));
444 g.fillRoundRect(getX(), getY(), getWidth(), getHeight(), 10, 10);
445 super.paintComponent(g);
446 }
447 }
448
449 private OSDLabel lblOSD = new OSDLabel("");
450
451 public void drawOSDText(Graphics2D g) {
452 String message = "<html>"+tr("<h2>Filter active</h2>");
453
454 if (disabledCount == 0 && disabledAndHiddenCount == 0)
455 return;
456
457 if (disabledAndHiddenCount != 0) {
458 message += tr("<p><b>{0}</b> objects hidden", disabledAndHiddenCount);
459 }
460
461 if (disabledAndHiddenCount != 0 && disabledCount != 0) {
462 message += "<br>";
463 }
464
465 if (disabledCount != 0) {
466 message += tr("<b>{0}</b> objects disabled", disabledCount);
467 }
468
469 message += tr("</p><p>Close the filter dialog to see all objects.<p></html>");
470
471 lblOSD.setText(message);
472 lblOSD.setSize(lblOSD.getPreferredSize());
473
474 int dx = Main.map.mapView.getWidth() - lblOSD.getPreferredSize().width - 15;
475 int dy = 15;
476 g.translate(dx, dy);
477 lblOSD.paintComponent(g);
478 g.translate(-dx, -dy);
479 }
480}
Note: See TracBrowser for help on using the repository browser.