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