001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.Color; 009import java.awt.Font; 010import java.awt.Graphics; 011import java.awt.Graphics2D; 012import java.util.ArrayList; 013import java.util.Collection; 014import java.util.HashSet; 015import java.util.LinkedList; 016import java.util.List; 017 018import javax.swing.BorderFactory; 019import javax.swing.JLabel; 020import javax.swing.JOptionPane; 021import javax.swing.table.AbstractTableModel; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 025import org.openstreetmap.josm.data.osm.DataSet; 026import org.openstreetmap.josm.data.osm.Filter; 027import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry; 028import org.openstreetmap.josm.data.osm.FilterMatcher; 029import org.openstreetmap.josm.data.osm.FilterWorker; 030import org.openstreetmap.josm.data.osm.Node; 031import org.openstreetmap.josm.data.osm.OsmPrimitive; 032import org.openstreetmap.josm.tools.Utils; 033 034/** 035 * 036 * @author Petr_DlouhĂ˝ 037 */ 038public class FilterTableModel extends AbstractTableModel { 039 040 public static final int COL_ENABLED = 0; 041 public static final int COL_HIDING = 1; 042 public static final int COL_TEXT = 2; 043 public static final int COL_INVERTED = 3; 044 045 // number of primitives that are disabled but not hidden 046 public int disabledCount; 047 // number of primitives that are disabled and hidden 048 public int disabledAndHiddenCount; 049 050 /** 051 * Constructs a new {@code FilterTableModel}. 052 */ 053 public FilterTableModel() { 054 loadPrefs(); 055 } 056 057 private final transient List<Filter> filters = new LinkedList<>(); 058 private final transient FilterMatcher filterMatcher = new FilterMatcher(); 059 060 private void updateFilters() { 061 filterMatcher.reset(); 062 for (Filter filter : filters) { 063 try { 064 filterMatcher.add(filter); 065 } catch (ParseError e) { 066 JOptionPane.showMessageDialog( 067 Main.parent, 068 tr("<html>Error in filter <code>{0}</code>:<br>{1}", Utils.shortenString(filter.text, 80), e.getMessage()), 069 tr("Error in filter"), 070 JOptionPane.ERROR_MESSAGE); 071 filter.enable = false; 072 savePrefs(); 073 } 074 } 075 executeFilters(); 076 } 077 078 public void executeFilters() { 079 DataSet ds = Main.main.getCurrentDataSet(); 080 boolean changed = false; 081 if (ds == null) { 082 disabledAndHiddenCount = 0; 083 disabledCount = 0; 084 changed = true; 085 } else { 086 final Collection<OsmPrimitive> deselect = new HashSet<>(); 087 088 ds.beginUpdate(); 089 try { 090 091 final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives(); 092 093 changed = FilterWorker.executeFilters(all, filterMatcher); 094 095 disabledCount = 0; 096 disabledAndHiddenCount = 0; 097 // collect disabled and selected the primitives 098 for (OsmPrimitive osm : all) { 099 if (osm.isDisabled()) { 100 disabledCount++; 101 if (osm.isSelected()) { 102 deselect.add(osm); 103 } 104 if (osm.isDisabledAndHidden()) { 105 disabledAndHiddenCount++; 106 } 107 } 108 } 109 disabledCount -= disabledAndHiddenCount; 110 } finally { 111 ds.endUpdate(); 112 } 113 114 if (!deselect.isEmpty()) { 115 ds.clearSelection(deselect); 116 } 117 } 118 119 if (Main.isDisplayingMapView() && changed) { 120 Main.map.mapView.repaint(); 121 Main.map.filterDialog.updateDialogHeader(); 122 } 123 } 124 125 public void executeFilters(Collection<? extends OsmPrimitive> primitives) { 126 DataSet ds = Main.main.getCurrentDataSet(); 127 if (ds == null) 128 return; 129 130 boolean changed = false; 131 List<OsmPrimitive> deselect = new ArrayList<>(); 132 133 ds.beginUpdate(); 134 try { 135 for (int i = 0; i < 2; i++) { 136 for (OsmPrimitive primitive: primitives) { 137 138 if (i == 0 && primitive instanceof Node) { 139 continue; 140 } 141 142 if (i == 1 && !(primitive instanceof Node)) { 143 continue; 144 } 145 146 if (primitive.isDisabled()) { 147 disabledCount--; 148 } 149 if (primitive.isDisabledAndHidden()) { 150 disabledAndHiddenCount--; 151 } 152 changed = changed | FilterWorker.executeFilters(primitive, filterMatcher); 153 if (primitive.isDisabled()) { 154 disabledCount++; 155 } 156 if (primitive.isDisabledAndHidden()) { 157 disabledAndHiddenCount++; 158 } 159 160 if (primitive.isSelected() && primitive.isDisabled()) { 161 deselect.add(primitive); 162 } 163 164 } 165 } 166 } finally { 167 ds.endUpdate(); 168 } 169 170 if (changed) { 171 Main.map.mapView.repaint(); 172 Main.map.filterDialog.updateDialogHeader(); 173 ds.clearSelection(deselect); 174 } 175 176 } 177 178 public void clearFilterFlags() { 179 DataSet ds = Main.main.getCurrentDataSet(); 180 if (ds != null) { 181 FilterWorker.clearFilterFlags(ds.allPrimitives()); 182 } 183 disabledCount = 0; 184 disabledAndHiddenCount = 0; 185 } 186 187 private void loadPrefs() { 188 List<FilterPreferenceEntry> entries = Main.pref.getListOfStructs("filters.entries", null, FilterPreferenceEntry.class); 189 if (entries != null) { 190 for (FilterPreferenceEntry e : entries) { 191 filters.add(new Filter(e)); 192 } 193 updateFilters(); 194 } 195 } 196 197 private void savePrefs() { 198 Collection<FilterPreferenceEntry> entries = new ArrayList<>(); 199 for (Filter flt : filters) { 200 entries.add(flt.getPreferenceEntry()); 201 } 202 Main.pref.putListOfStructs("filters.entries", entries, FilterPreferenceEntry.class); 203 } 204 205 public void addFilter(Filter f) { 206 filters.add(f); 207 savePrefs(); 208 updateFilters(); 209 fireTableRowsInserted(filters.size() - 1, filters.size() - 1); 210 } 211 212 public void moveDownFilter(int i) { 213 if (i >= filters.size() - 1) 214 return; 215 filters.add(i + 1, filters.remove(i)); 216 savePrefs(); 217 updateFilters(); 218 fireTableRowsUpdated(i, i + 1); 219 } 220 221 public void moveUpFilter(int i) { 222 if (i == 0) 223 return; 224 filters.add(i - 1, filters.remove(i)); 225 savePrefs(); 226 updateFilters(); 227 fireTableRowsUpdated(i - 1, i); 228 } 229 230 public void removeFilter(int i) { 231 filters.remove(i); 232 savePrefs(); 233 updateFilters(); 234 fireTableRowsDeleted(i, i); 235 } 236 237 public void setFilter(int i, Filter f) { 238 filters.set(i, f); 239 savePrefs(); 240 updateFilters(); 241 fireTableRowsUpdated(i, i); 242 } 243 244 public Filter getFilter(int i) { 245 return filters.get(i); 246 } 247 248 @Override 249 public int getRowCount() { 250 return filters.size(); 251 } 252 253 @Override 254 public int getColumnCount() { 255 return 5; 256 } 257 258 @Override 259 public String getColumnName(int column) { 260 String[] names = {/* translators notes must be in front */ 261 /* column header: enable filter */trc("filter", "E"), 262 /* column header: hide filter */trc("filter", "H"), 263 /* column header: filter text */trc("filter", "Text"), 264 /* column header: inverted filter */trc("filter", "I"), 265 /* column header: filter mode */trc("filter", "M")}; 266 return names[column]; 267 } 268 269 @Override 270 public Class<?> getColumnClass(int column) { 271 Class<?>[] classes = {Boolean.class, Boolean.class, String.class, Boolean.class, String.class}; 272 return classes[column]; 273 } 274 275 public boolean isCellEnabled(int row, int column) { 276 if (!filters.get(row).enable && column != 0) 277 return false; 278 return true; 279 } 280 281 @Override 282 public boolean isCellEditable(int row, int column) { 283 if (!filters.get(row).enable && column != 0) 284 return false; 285 if (column < 4) 286 return true; 287 return false; 288 } 289 290 @Override 291 public void setValueAt(Object aValue, int row, int column) { 292 if (row >= filters.size()) { 293 return; 294 } 295 Filter f = filters.get(row); 296 switch (column) { 297 case COL_ENABLED: 298 f.enable = (Boolean) aValue; 299 savePrefs(); 300 updateFilters(); 301 fireTableRowsUpdated(row, row); 302 break; 303 case COL_HIDING: 304 f.hiding = (Boolean) aValue; 305 savePrefs(); 306 updateFilters(); 307 break; 308 case COL_TEXT: 309 f.text = (String) aValue; 310 savePrefs(); 311 break; 312 case COL_INVERTED: 313 f.inverted = (Boolean) aValue; 314 savePrefs(); 315 updateFilters(); 316 break; 317 default: // Do nothing 318 } 319 if (column != 0) { 320 fireTableCellUpdated(row, column); 321 } 322 } 323 324 @Override 325 public Object getValueAt(int row, int column) { 326 if (row >= filters.size()) { 327 return null; 328 } 329 Filter f = filters.get(row); 330 switch (column) { 331 case COL_ENABLED: 332 return f.enable; 333 case COL_HIDING: 334 return f.hiding; 335 case COL_TEXT: 336 return f.text; 337 case COL_INVERTED: 338 return f.inverted; 339 case 4: 340 switch (f.mode) { /* translators notes must be in front */ 341 case replace: /* filter mode: replace */ 342 return trc("filter", "R"); 343 case add: /* filter mode: add */ 344 return trc("filter", "A"); 345 case remove: /* filter mode: remove */ 346 return trc("filter", "D"); 347 case in_selection: /* filter mode: in selection */ 348 return trc("filter", "F"); 349 default: 350 Main.warn("Unknown filter mode: " + f.mode); 351 } 352 break; 353 default: // Do nothing 354 } 355 return null; 356 } 357 358 /** 359 * On screen display label 360 */ 361 private static class OSDLabel extends JLabel { 362 OSDLabel(String text) { 363 super(text); 364 setOpaque(true); 365 setForeground(Color.black); 366 setBackground(new Color(0, 0, 0, 0)); 367 setFont(getFont().deriveFont(Font.PLAIN)); 368 setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); 369 } 370 371 @Override 372 public void paintComponent(Graphics g) { 373 g.setColor(new Color(255, 255, 255, 140)); 374 g.fillRoundRect(getX(), getY(), getWidth(), getHeight(), 10, 10); 375 super.paintComponent(g); 376 } 377 } 378 379 private final OSDLabel lblOSD = new OSDLabel(""); 380 381 public void drawOSDText(Graphics2D g) { 382 String message = "<html>" + tr("<h2>Filter active</h2>"); 383 384 if (disabledCount == 0 && disabledAndHiddenCount == 0) 385 return; 386 387 if (disabledAndHiddenCount != 0) { 388 /* for correct i18n of plural forms - see #9110 */ 389 message += trn("<p><b>{0}</b> object hidden", "<p><b>{0}</b> objects hidden", disabledAndHiddenCount, disabledAndHiddenCount); 390 } 391 392 if (disabledAndHiddenCount != 0 && disabledCount != 0) { 393 message += "<br>"; 394 } 395 396 if (disabledCount != 0) { 397 /* for correct i18n of plural forms - see #9110 */ 398 message += trn("<b>{0}</b> object disabled", "<b>{0}</b> objects disabled", disabledCount, disabledCount); 399 } 400 401 message += tr("</p><p>Close the filter dialog to see all objects.<p></html>"); 402 403 lblOSD.setText(message); 404 lblOSD.setSize(lblOSD.getPreferredSize()); 405 406 int dx = Main.map.mapView.getWidth() - lblOSD.getPreferredSize().width - 15; 407 int dy = 15; 408 g.translate(dx, dy); 409 lblOSD.paintComponent(g); 410 g.translate(-dx, -dy); 411 } 412 413 /** 414 * Returns the list of filters. 415 * @return the list of filters 416 */ 417 public List<Filter> getFilters() { 418 return filters; 419 } 420}