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