// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.dialogs;
import static org.openstreetmap.josm.tools.I18n.tr;
import static org.openstreetmap.josm.tools.I18n.trc;
import static org.openstreetmap.josm.tools.I18n.trn;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.table.AbstractTableModel;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Filter;
import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
import org.openstreetmap.josm.data.osm.FilterMatcher;
import org.openstreetmap.josm.data.osm.FilterWorker;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.tools.Utils;
/**
*
* @author Petr_DlouhĂ˝
*/
public class FilterTableModel extends AbstractTableModel {
public static final int COL_ENABLED = 0;
public static final int COL_HIDING = 1;
public static final int COL_TEXT = 2;
public static final int COL_INVERTED = 3;
// number of primitives that are disabled but not hidden
public int disabledCount;
// number of primitives that are disabled and hidden
public int disabledAndHiddenCount;
/**
* Constructs a new {@code FilterTableModel}.
*/
public FilterTableModel() {
loadPrefs();
}
private final transient List<Filter> filters = new LinkedList<>();
private final transient FilterMatcher filterMatcher = new FilterMatcher();
private void updateFilters() {
filterMatcher.reset();
for (Filter filter : filters) {
try {
filterMatcher.add(filter);
} catch (ParseError e) {
Main.error(e);
JOptionPane.showMessageDialog(
Main.parent,
tr("<html>Error in filter <code>{0}</code>:<br>{1}",
Utils.escapeReservedCharactersHTML(Utils.shortenString(filter.text, 80)),
Utils.escapeReservedCharactersHTML(e.getMessage())),
tr("Error in filter"),
JOptionPane.ERROR_MESSAGE);
filter.enable = false;
savePrefs();
}
}
executeFilters();
}
public void executeFilters() {
DataSet ds = Main.getLayerManager().getEditDataSet();
boolean changed = false;
if (ds == null) {
disabledAndHiddenCount = 0;
disabledCount = 0;
changed = true;
} else {
final Collection<OsmPrimitive> deselect = new HashSet<>();
ds.beginUpdate();
try {
final Collection<OsmPrimitive> all = ds.allNonDeletedCompletePrimitives();
changed = FilterWorker.executeFilters(all, filterMatcher);
disabledCount = 0;
disabledAndHiddenCount = 0;
// collect disabled and selected the primitives
for (OsmPrimitive osm : all) {
if (osm.isDisabled()) {
disabledCount++;
if (osm.isSelected()) {
deselect.add(osm);
}
if (osm.isDisabledAndHidden()) {
disabledAndHiddenCount++;
}
}
}
disabledCount -= disabledAndHiddenCount;
} finally {
ds.endUpdate();
}
if (!deselect.isEmpty()) {
ds.clearSelection(deselect);
}
}
if (changed && Main.isDisplayingMapView()) {
Main.map.mapView.repaint();
Main.map.filterDialog.updateDialogHeader();
}
}
public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
DataSet ds = Main.getLayerManager().getEditDataSet();
if (ds == null)
return;
boolean changed = false;
List<OsmPrimitive> deselect = new ArrayList<>();
ds.beginUpdate();
try {
for (int i = 0; i < 2; i++) {
for (OsmPrimitive primitive: primitives) {
if (i == 0 && primitive instanceof Node) {
continue;
}
if (i == 1 && !(primitive instanceof Node)) {
continue;
}
if (primitive.isDisabled()) {
disabledCount--;
}
if (primitive.isDisabledAndHidden()) {
disabledAndHiddenCount--;
}
changed = changed | FilterWorker.executeFilters(primitive, filterMatcher);
if (primitive.isDisabled()) {
disabledCount++;
}
if (primitive.isDisabledAndHidden()) {
disabledAndHiddenCount++;
}
if (primitive.isSelected() && primitive.isDisabled()) {
deselect.add(primitive);
}
}
}
} finally {
ds.endUpdate();
}
if (changed) {
Main.map.mapView.repaint();
Main.map.filterDialog.updateDialogHeader();
ds.clearSelection(deselect);
}
}
public void clearFilterFlags() {
DataSet ds = Main.getLayerManager().getEditDataSet();
if (ds != null) {
FilterWorker.clearFilterFlags(ds.allPrimitives());
}
disabledCount = 0;
disabledAndHiddenCount = 0;
}
private void loadPrefs() {
List<FilterPreferenceEntry> entries = Main.pref.getListOfStructs("filters.entries", null, FilterPreferenceEntry.class);
if (entries != null) {
for (FilterPreferenceEntry e : entries) {
filters.add(new Filter(e));
}
updateFilters();
}
}
private void savePrefs() {
Collection<FilterPreferenceEntry> entries = new ArrayList<>();
for (Filter flt : filters) {
entries.add(flt.getPreferenceEntry());
}
Main.pref.putListOfStructs("filters.entries", entries, FilterPreferenceEntry.class);
}
public void addFilter(Filter f) {
filters.add(f);
savePrefs();
updateFilters();
fireTableRowsInserted(filters.size() - 1, filters.size() - 1);
}
public void moveDownFilter(int i) {
if (i >= filters.size() - 1)
return;
filters.add(i + 1, filters.remove(i));
savePrefs();
updateFilters();
fireTableRowsUpdated(i, i + 1);
}
public void moveUpFilter(int i) {
if (i == 0)
return;
filters.add(i - 1, filters.remove(i));
savePrefs();
updateFilters();
fireTableRowsUpdated(i - 1, i);
}
public void removeFilter(int i) {
filters.remove(i);
savePrefs();
updateFilters();
fireTableRowsDeleted(i, i);
}
public void setFilter(int i, Filter f) {
filters.set(i, f);
savePrefs();
updateFilters();
fireTableRowsUpdated(i, i);
}
public Filter getFilter(int i) {
return filters.get(i);
}
@Override
public int getRowCount() {
return filters.size();
}
@Override
public int getColumnCount() {
return 5;
}
@Override
public String getColumnName(int column) {
String[] names = {/* translators notes must be in front */
/* column header: enable filter */trc("filter", "E"),
/* column header: hide filter */trc("filter", "H"),
/* column header: filter text */trc("filter", "Text"),
/* column header: inverted filter */trc("filter", "I"),
/* column header: filter mode */trc("filter", "M")};
return names[column];
}
@Override
public Class<?> getColumnClass(int column) {
Class<?>[] classes = {Boolean.class, Boolean.class, String.class, Boolean.class, String.class};
return classes[column];
}
public boolean isCellEnabled(int row, int column) {
if (!filters.get(row).enable && column != 0)
return false;
return true;
}
@Override
public boolean isCellEditable(int row, int column) {
if (!filters.get(row).enable && column != 0)
return false;
if (column < 4)
return true;
return false;
}
@Override
public void setValueAt(Object aValue, int row, int column) {
if (row >= filters.size()) {
return;
}
Filter f = filters.get(row);
switch (column) {
case COL_ENABLED:
f.enable = (Boolean) aValue;
savePrefs();
updateFilters();
fireTableRowsUpdated(row, row);
break;
case COL_HIDING:
f.hiding = (Boolean) aValue;
savePrefs();
updateFilters();
break;
case COL_TEXT:
f.text = (String) aValue;
savePrefs();
break;
case COL_INVERTED:
f.inverted = (Boolean) aValue;
savePrefs();
updateFilters();
break;
default: // Do nothing
}
if (column != 0) {
fireTableCellUpdated(row, column);
}
}
@Override
public Object getValueAt(int row, int column) {
if (row >= filters.size()) {
return null;
}
Filter f = filters.get(row);
switch (column) {
case COL_ENABLED:
return f.enable;
case COL_HIDING:
return f.hiding;
case COL_TEXT:
return f.text;
case COL_INVERTED:
return f.inverted;
case 4:
switch (f.mode) { /* translators notes must be in front */
case replace: /* filter mode: replace */
return trc("filter", "R");
case add: /* filter mode: add */
return trc("filter", "A");
case remove: /* filter mode: remove */
return trc("filter", "D");
case in_selection: /* filter mode: in selection */
return trc("filter", "F");
default:
Main.warn("Unknown filter mode: " + f.mode);
}
break;
default: // Do nothing
}
return null;
}
/**
* On screen display label
*/
private static class OSDLabel extends JLabel {
OSDLabel(String text) {
super(text);
setOpaque(true);
setForeground(Color.black);
setBackground(new Color(0, 0, 0, 0));
setFont(getFont().deriveFont(Font.PLAIN));
setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
}
@Override
public void paintComponent(Graphics g) {
g.setColor(new Color(255, 255, 255, 140));
g.fillRoundRect(getX(), getY(), getWidth(), getHeight(), 10, 10);
super.paintComponent(g);
}
}
private final OSDLabel lblOSD = new OSDLabel("");
public void drawOSDText(Graphics2D g) {
String message = "<html>" + tr("<h2>Filter active</h2>");
if (disabledCount == 0 && disabledAndHiddenCount == 0)
return;
if (disabledAndHiddenCount != 0) {
/* for correct i18n of plural forms - see #9110 */
message += trn("<p><b>{0}</b> object hidden", "<p><b>{0}</b> objects hidden", disabledAndHiddenCount, disabledAndHiddenCount);
}
if (disabledAndHiddenCount != 0 && disabledCount != 0) {
message += "<br>";
}
if (disabledCount != 0) {
/* for correct i18n of plural forms - see #9110 */
message += trn("<b>{0}</b> object disabled", "<b>{0}</b> objects disabled", disabledCount, disabledCount);
}
message += tr("</p><p>Close the filter dialog to see all objects.<p></html>");
lblOSD.setText(message);
lblOSD.setSize(lblOSD.getPreferredSize());
int dx = Main.map.mapView.getWidth() - lblOSD.getPreferredSize().width - 15;
int dy = 15;
g.translate(dx, dy);
lblOSD.paintComponent(g);
g.translate(-dx, -dy);
}
/**
* Returns the list of filters.
* @return the list of filters
*/
public List<Filter> getFilters() {
return filters;
}
}