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}