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 List<Filter> filters = new LinkedList<>();
057    private final 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        public 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 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}