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