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.Graphics2D;
008import java.util.Collection;
009import java.util.List;
010
011import javax.swing.ListSelectionModel;
012import javax.swing.table.AbstractTableModel;
013
014import org.openstreetmap.josm.data.osm.Filter;
015import org.openstreetmap.josm.data.osm.FilterModel;
016import org.openstreetmap.josm.data.osm.OsmPrimitive;
017import org.openstreetmap.josm.gui.MainApplication;
018import org.openstreetmap.josm.gui.MapFrame;
019import org.openstreetmap.josm.gui.autofilter.AutoFilterManager;
020import org.openstreetmap.josm.gui.util.SortableTableModel;
021import org.openstreetmap.josm.gui.widgets.OSDLabel;
022import org.openstreetmap.josm.tools.Logging;
023
024/**
025 * The model that is used for the table in the {@link FilterDialog}.
026 *
027 * @author Petr_DlouhĂ˝
028 * @since 2125
029 */
030public class FilterTableModel extends AbstractTableModel implements SortableTableModel<Filter> {
031
032    /**
033     * The filter enabled column
034     */
035    public static final int COL_ENABLED = 0;
036    /**
037     * The column indicating if the filter is hiding.
038     */
039    public static final int COL_HIDING = 1;
040    /**
041     * The column that displays the filter text
042     */
043    public static final int COL_TEXT = 2;
044    /**
045     * The column to invert the filter
046     */
047    public static final int COL_INVERTED = 3;
048
049    /**
050     * The filter model
051     */
052    final FilterModel model = new FilterModel();
053
054    /**
055     * The selection model
056     */
057    final ListSelectionModel selectionModel;
058
059    /**
060     * A helper for {@link #drawOSDText(Graphics2D)}
061     */
062    private final OSDLabel lblOSD = new OSDLabel("");
063
064    /**
065     * Constructs a new {@code FilterTableModel}.
066     * @param listSelectionModel selection model
067     */
068    public FilterTableModel(ListSelectionModel listSelectionModel) {
069        this.selectionModel = listSelectionModel;
070        loadPrefs();
071    }
072
073    private void updateFilters() {
074        AutoFilterManager.getInstance().setCurrentAutoFilter(null);
075        executeFilters(true);
076    }
077
078    /**
079     * Runs the filters on the current edit data set, if any. Does nothing if no filter is enabled.
080     */
081    public void executeFilters() {
082        executeFilters(false);
083    }
084
085    /**
086     * Runs the filter on a list of primitives that are part of the edit data set, if any. Does nothing if no filter is enabled.
087     * @param primitives The primitives
088     */
089    public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
090        executeFilters(primitives, false);
091    }
092
093    /**
094     * Runs the filters on the current edit data set, if any.
095     * @param force force execution of filters even if no filter is enabled. Useful to reset state after change of filters
096     * @since 14206
097     */
098    public void executeFilters(boolean force) {
099        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
100            model.executeFilters();
101            updateMap();
102        }
103    }
104
105    /**
106     * Runs the filter on a list of primitives that are part of the edit data set, if any.
107     * @param force force execution of filters even if no filter is enabled. Useful to reset state after change of filters
108     * @param primitives The primitives
109     * @since 14206
110     */
111    public void executeFilters(Collection<? extends OsmPrimitive> primitives, boolean force) {
112        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
113            model.executeFilters(primitives);
114            updateMap();
115        }
116    }
117
118    private void updateMap() {
119        MapFrame map = MainApplication.getMap();
120        if (map != null && model.isChanged()) {
121            map.filterDialog.updateDialogHeader();
122        }
123    }
124
125    private void loadPrefs() {
126        model.loadPrefs("filters.entries");
127    }
128
129    private void savePrefs() {
130        model.savePrefs("filters.entries");
131    }
132
133    /**
134     * Adds a new filter to the filter list.
135     * @param filter The new filter
136     */
137    public void addFilter(Filter filter) {
138        if (model.addFilter(filter)) {
139            savePrefs();
140            updateFilters();
141            int size = model.getFiltersCount();
142            fireTableRowsInserted(size - 1, size - 1);
143        }
144    }
145
146    @Override
147    public boolean doMove(int delta, int... selectedRows) {
148        return model.moveFilters(delta, selectedRows);
149    }
150
151    @Override
152    public boolean move(int delta, int... selectedRows) {
153        if (!SortableTableModel.super.move(delta, selectedRows))
154            return false;
155        savePrefs();
156        updateFilters();
157        int rowIndex = selectedRows[0];
158        if (delta < 0)
159            fireTableRowsUpdated(rowIndex + delta, rowIndex);
160        else if (delta > 0)
161            fireTableRowsUpdated(rowIndex, rowIndex + delta);
162        return true;
163    }
164
165    /**
166     * Moves down the filter in the given row.
167     * @param rowIndex The filter row
168     * @deprecated Use {@link #moveDown(int...)}
169     */
170    @Deprecated
171    public void moveDownFilter(int rowIndex) {
172        moveDown(rowIndex);
173    }
174
175    /**
176     * Moves up the filter in the given row
177     * @param rowIndex The filter row
178     * @deprecated Use {@link #moveUp(int...)}
179     */
180    @Deprecated
181    public void moveUpFilter(int rowIndex) {
182        moveUp(rowIndex);
183    }
184
185    /**
186     * Removes the filter that is displayed in the given row
187     * @param rowIndex The index of the filter to remove
188     */
189    public void removeFilter(int rowIndex) {
190        if (model.removeFilter(rowIndex) != null) {
191            savePrefs();
192            updateFilters();
193            fireTableRowsDeleted(rowIndex, rowIndex);
194        }
195    }
196
197    /**
198     * Sets/replaces the filter for a given row.
199     * @param rowIndex The row index
200     * @param filter The filter that should be placed in that row
201     * @deprecated Use {@link #setValue}
202     */
203    @Deprecated
204    public void setFilter(int rowIndex, Filter filter) {
205        setValue(rowIndex, filter);
206    }
207
208    @Override
209    public Filter setValue(int rowIndex, Filter filter) {
210        Filter result = model.setValue(rowIndex, filter);
211        savePrefs();
212        updateFilters();
213        fireTableRowsUpdated(rowIndex, rowIndex);
214        return result;
215    }
216
217    /**
218     * Gets the filter by row index
219     * @param rowIndex The row index
220     * @return The filter in that row
221     * @deprecated Use {@link #getValue}
222     */
223    @Deprecated
224    public Filter getFilter(int rowIndex) {
225        return getValue(rowIndex);
226    }
227
228    @Override
229    public Filter getValue(int rowIndex) {
230        return model.getValue(rowIndex);
231    }
232
233    @Override
234    public ListSelectionModel getSelectionModel() {
235        return selectionModel;
236    }
237
238    @Override
239    public int getRowCount() {
240        return model.getFiltersCount();
241    }
242
243    @Override
244    public int getColumnCount() {
245        return 5;
246    }
247
248    @Override
249    public String getColumnName(int column) {
250        String[] names = {/* translators notes must be in front */
251                /* column header: enable filter */trc("filter", "E"),
252                /* column header: hide filter */trc("filter", "H"),
253                /* column header: filter text */trc("filter", "Text"),
254                /* column header: inverted filter */trc("filter", "I"),
255                /* column header: filter mode */trc("filter", "M")};
256        return names[column];
257    }
258
259    @Override
260    public Class<?> getColumnClass(int column) {
261        Class<?>[] classes = {Boolean.class, Boolean.class, String.class, Boolean.class, String.class};
262        return classes[column];
263    }
264
265    /**
266     * Determines if a cell is enabled.
267     * @param row row index
268     * @param column column index
269     * @return {@code true} if the cell at (row, column) is enabled
270     */
271    public boolean isCellEnabled(int row, int column) {
272        return model.getValue(row).enable || column == 0;
273    }
274
275    @Override
276    public boolean isCellEditable(int row, int column) {
277        return column < 4 && isCellEnabled(row, column);
278    }
279
280    @Override
281    public void setValueAt(Object aValue, int row, int column) {
282        if (row >= model.getFiltersCount()) {
283            return;
284        }
285        Filter f = model.getValue(row);
286        switch (column) {
287        case COL_ENABLED:
288            f.enable = (Boolean) aValue;
289            break;
290        case COL_HIDING:
291            f.hiding = (Boolean) aValue;
292            break;
293        case COL_TEXT:
294            f.text = (String) aValue;
295            break;
296        case COL_INVERTED:
297            f.inverted = (Boolean) aValue;
298            break;
299        default: // Do nothing
300        }
301        setValue(row, f);
302    }
303
304    @Override
305    public Object getValueAt(int row, int column) {
306        if (row >= model.getFiltersCount()) {
307            return null;
308        }
309        Filter f = model.getValue(row);
310        switch (column) {
311        case COL_ENABLED:
312            return f.enable;
313        case COL_HIDING:
314            return f.hiding;
315        case COL_TEXT:
316            return f.text;
317        case COL_INVERTED:
318            return f.inverted;
319        case 4:
320            switch (f.mode) { /* translators notes must be in front */
321            case replace: /* filter mode: replace */
322                return trc("filter", "R");
323            case add: /* filter mode: add */
324                return trc("filter", "A");
325            case remove: /* filter mode: remove */
326                return trc("filter", "D");
327            case in_selection: /* filter mode: in selection */
328                return trc("filter", "F");
329            default:
330                Logging.warn("Unknown filter mode: " + f.mode);
331            }
332            break;
333        default: // Do nothing
334        }
335        return null;
336    }
337
338    /**
339     * Draws a text on the map display that indicates that filters are active.
340     * @param g The graphics to draw that text on.
341     */
342    public void drawOSDText(Graphics2D g) {
343        model.drawOSDText(g, lblOSD,
344                tr("<h2>Filter active</h2>"),
345                tr("</p><p>Close the filter dialog to see all objects.<p></html>"));
346    }
347
348    /**
349     * Returns the list of filters.
350     * @return the list of filters
351     */
352    public List<Filter> getFilters() {
353        return model.getFilters();
354    }
355
356    @Override
357    public void sort() {
358        model.sort();
359        fireTableDataChanged();
360    }
361
362    @Override
363    public void reverse() {
364        model.reverse();
365        fireTableDataChanged();
366    }
367}