source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java @ 14206

Last change on this file since 14206 was 14206, checked in by Don-vip, 7 weeks ago

fix #16698, see #15670 - make sure filters are executed (costly operation) only when necessary:

  • data changes imply execution of filters only when at least a filter is enabled
  • filter changes imply execution of filters even is no filter is enabled
  • filter dataset change events should not trigger a new filter execution!
  • Property svn:eol-style set to native
File size: 17.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Graphics2D;
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.awt.event.MouseEvent;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.List;
14
15import javax.swing.AbstractAction;
16import javax.swing.DefaultCellEditor;
17import javax.swing.JCheckBox;
18import javax.swing.JTable;
19import javax.swing.ListSelectionModel;
20import javax.swing.SwingUtilities;
21import javax.swing.table.DefaultTableCellRenderer;
22import javax.swing.table.JTableHeader;
23import javax.swing.table.TableCellRenderer;
24import javax.swing.table.TableColumnModel;
25import javax.swing.table.TableModel;
26
27import org.openstreetmap.josm.actions.mapmode.MapMode;
28import org.openstreetmap.josm.actions.search.SearchAction;
29import org.openstreetmap.josm.data.osm.Filter;
30import org.openstreetmap.josm.data.osm.FilterModel;
31import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
32import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent.DatasetEventType;
33import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
34import org.openstreetmap.josm.data.osm.event.DataSetListener;
35import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
36import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
37import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
38import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
39import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
40import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
41import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
42import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
43import org.openstreetmap.josm.gui.MainApplication;
44import org.openstreetmap.josm.gui.MapFrame;
45import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
46import org.openstreetmap.josm.gui.SideButton;
47import org.openstreetmap.josm.gui.util.MultikeyActionsHandler;
48import org.openstreetmap.josm.gui.util.MultikeyShortcutAction;
49import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.InputMapUtils;
52import org.openstreetmap.josm.tools.PlatformManager;
53import org.openstreetmap.josm.tools.Shortcut;
54
55/**
56 * The filter dialog displays a list of filters that are active on the current edit layer.
57 *
58 * @author Petr_Dlouhý
59 */
60public class FilterDialog extends ToggleDialog implements DataSetListener, MapModeChangeListener {
61
62    private JTable userTable;
63    private final FilterTableModel filterModel = new FilterTableModel();
64
65    private final EnableFilterAction enableFilterAction;
66    private final HidingFilterAction hidingFilterAction;
67
68    /**
69     * Constructs a new {@code FilterDialog}
70     */
71    public FilterDialog() {
72        super(tr("Filter"), "filter", tr("Filter objects and hide/disable them."),
73                Shortcut.registerShortcut("subwindow:filter", tr("Toggle: {0}", tr("Filter")),
74                        KeyEvent.VK_F, Shortcut.ALT_SHIFT), 162);
75        build();
76        enableFilterAction = new EnableFilterAction();
77        hidingFilterAction = new HidingFilterAction();
78        MultikeyActionsHandler.getInstance().addAction(enableFilterAction);
79        MultikeyActionsHandler.getInstance().addAction(hidingFilterAction);
80    }
81
82    @Override
83    public void showNotify() {
84        DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
85        MapFrame.addMapModeChangeListener(this);
86        filterModel.executeFilters(true);
87    }
88
89    @Override
90    public void hideNotify() {
91        DatasetEventManager.getInstance().removeDatasetListener(this);
92        MapFrame.removeMapModeChangeListener(this);
93        filterModel.model.clearFilterFlags();
94        MainApplication.getLayerManager().invalidateEditLayer();
95    }
96
97    private static final Shortcut ENABLE_FILTER_SHORTCUT
98    = Shortcut.registerShortcut("core_multikey:enableFilter", tr("Multikey: {0}", tr("Enable filter")),
99            KeyEvent.VK_E, Shortcut.ALT_CTRL);
100
101    private static final Shortcut HIDING_FILTER_SHORTCUT
102    = Shortcut.registerShortcut("core_multikey:hidingFilter", tr("Multikey: {0}", tr("Hide filter")),
103            KeyEvent.VK_H, Shortcut.ALT_CTRL);
104
105    private static final String[] COLUMN_TOOLTIPS = {
106            PlatformManager.getPlatform().makeTooltip(tr("Enable filter"), ENABLE_FILTER_SHORTCUT),
107            PlatformManager.getPlatform().makeTooltip(tr("Hiding filter"), HIDING_FILTER_SHORTCUT),
108            null,
109            tr("Inverse filter"),
110            tr("Filter mode")
111    };
112
113    /**
114     * Builds the GUI.
115     */
116    protected void build() {
117        userTable = new UserTable(filterModel);
118
119        userTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
120        userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
121
122        userTable.getColumnModel().getColumn(0).setMaxWidth(1);
123        userTable.getColumnModel().getColumn(1).setMaxWidth(1);
124        userTable.getColumnModel().getColumn(3).setMaxWidth(1);
125        userTable.getColumnModel().getColumn(4).setMaxWidth(1);
126
127        userTable.getColumnModel().getColumn(0).setResizable(false);
128        userTable.getColumnModel().getColumn(1).setResizable(false);
129        userTable.getColumnModel().getColumn(3).setResizable(false);
130        userTable.getColumnModel().getColumn(4).setResizable(false);
131
132        userTable.setDefaultRenderer(Boolean.class, new BooleanRenderer());
133        userTable.setDefaultRenderer(String.class, new StringRenderer());
134        userTable.setDefaultEditor(String.class, new DefaultCellEditor(new DisableShortcutsOnFocusGainedTextField()));
135
136        SideButton addButton = new SideButton(new AbstractAction() {
137            {
138                putValue(NAME, tr("Add"));
139                putValue(SHORT_DESCRIPTION, tr("Add filter."));
140                new ImageProvider("dialogs", "add").getResource().attachImageIcon(this, true);
141            }
142
143            @Override
144            public void actionPerformed(ActionEvent e) {
145                Filter filter = (Filter) SearchAction.showSearchDialog(new Filter());
146                if (filter != null) {
147                    filterModel.addFilter(filter);
148                }
149            }
150        });
151        SideButton editButton = new SideButton(new AbstractAction() {
152            {
153                putValue(NAME, tr("Edit"));
154                putValue(SHORT_DESCRIPTION, tr("Edit filter."));
155                new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this, true);
156            }
157
158            @Override
159            public void actionPerformed(ActionEvent e) {
160                int index = userTable.getSelectionModel().getMinSelectionIndex();
161                if (index < 0) return;
162                Filter f = filterModel.getFilter(index);
163                Filter filter = (Filter) SearchAction.showSearchDialog(f);
164                if (filter != null) {
165                    filterModel.setFilter(index, filter);
166                }
167            }
168        });
169        SideButton deleteButton = new SideButton(new AbstractAction() {
170            {
171                putValue(NAME, tr("Delete"));
172                putValue(SHORT_DESCRIPTION, tr("Delete filter."));
173                new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this, true);
174            }
175
176            @Override
177            public void actionPerformed(ActionEvent e) {
178                int index = userTable.getSelectionModel().getMinSelectionIndex();
179                if (index >= 0) {
180                    filterModel.removeFilter(index);
181                }
182            }
183        });
184        SideButton upButton = new SideButton(new AbstractAction() {
185            {
186                putValue(NAME, tr("Up"));
187                putValue(SHORT_DESCRIPTION, tr("Move filter up."));
188                new ImageProvider("dialogs", "up").getResource().attachImageIcon(this, true);
189            }
190
191            @Override
192            public void actionPerformed(ActionEvent e) {
193                int index = userTable.getSelectionModel().getMinSelectionIndex();
194                if (index >= 0) {
195                    filterModel.moveUpFilter(index);
196                    userTable.getSelectionModel().setSelectionInterval(index-1, index-1);
197                }
198            }
199        });
200        SideButton downButton = new SideButton(new AbstractAction() {
201            {
202                putValue(NAME, tr("Down"));
203                putValue(SHORT_DESCRIPTION, tr("Move filter down."));
204                new ImageProvider("dialogs", "down").getResource().attachImageIcon(this, true);
205            }
206
207            @Override
208            public void actionPerformed(ActionEvent e) {
209                int index = userTable.getSelectionModel().getMinSelectionIndex();
210                if (index >= 0) {
211                    filterModel.moveDownFilter(index);
212                    userTable.getSelectionModel().setSelectionInterval(index+1, index+1);
213                }
214            }
215        });
216
217        // Toggle filter "enabled" on Enter
218        InputMapUtils.addEnterAction(userTable, new AbstractAction() {
219            @Override
220            public void actionPerformed(ActionEvent e) {
221                int index = userTable.getSelectedRow();
222                if (index >= 0) {
223                    Filter filter = filterModel.getFilter(index);
224                    filterModel.setValueAt(!filter.enable, index, FilterTableModel.COL_ENABLED);
225                }
226            }
227        });
228
229        // Toggle filter "hiding" on Spacebar
230        InputMapUtils.addSpacebarAction(userTable, new AbstractAction() {
231            @Override
232            public void actionPerformed(ActionEvent e) {
233                int index = userTable.getSelectedRow();
234                if (index >= 0) {
235                    Filter filter = filterModel.getFilter(index);
236                    filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
237                }
238            }
239        });
240
241        createLayout(userTable, true, Arrays.asList(
242                addButton, editButton, deleteButton, upButton, downButton
243        ));
244    }
245
246    @Override
247    public void destroy() {
248        MultikeyActionsHandler.getInstance().removeAction(enableFilterAction);
249        MultikeyActionsHandler.getInstance().removeAction(hidingFilterAction);
250        super.destroy();
251    }
252
253    static final class UserTable extends JTable {
254        static final class UserTableHeader extends JTableHeader {
255            UserTableHeader(TableColumnModel cm) {
256                super(cm);
257            }
258
259            @Override
260            public String getToolTipText(MouseEvent e) {
261                int index = columnModel.getColumnIndexAtX(e.getPoint().x);
262                int realIndex = columnModel.getColumn(index).getModelIndex();
263                return COLUMN_TOOLTIPS[realIndex];
264            }
265        }
266
267        UserTable(TableModel dm) {
268            super(dm);
269        }
270
271        @Override
272        protected JTableHeader createDefaultTableHeader() {
273            return new UserTableHeader(columnModel);
274        }
275    }
276
277    static class StringRenderer extends DefaultTableCellRenderer {
278        @Override
279        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
280            Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
281            TableModel model = table.getModel();
282            if (model instanceof FilterTableModel) {
283                cell.setEnabled(((FilterTableModel) model).isCellEnabled(row, column));
284            }
285            return cell;
286        }
287    }
288
289    static class BooleanRenderer extends JCheckBox implements TableCellRenderer {
290        @Override
291        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
292            FilterTableModel model = (FilterTableModel) table.getModel();
293            setSelected(value != null && (Boolean) value);
294            setEnabled(model.isCellEnabled(row, column));
295            setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
296            return this;
297        }
298    }
299
300    /**
301     * Updates the headline of this dialog to display the number of active filters.
302     */
303    public void updateDialogHeader() {
304        SwingUtilities.invokeLater(() -> setTitle(
305                tr("Filter Hidden:{0} Disabled:{1}",
306                        filterModel.model.getDisabledAndHiddenCount(), filterModel.model.getDisabledCount())));
307    }
308
309    /**
310     * Draws a text on the map display that indicates that filters are active.
311     * @param g The graphics to draw that text on.
312     */
313    public void drawOSDText(Graphics2D g) {
314        filterModel.drawOSDText(g);
315    }
316
317    @Override
318    public void dataChanged(DataChangedEvent event) {
319        filterModel.executeFilters();
320    }
321
322    @Override
323    public void nodeMoved(NodeMovedEvent event) {
324        filterModel.executeFilters();
325    }
326
327    @Override
328    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
329        if (!DatasetEventType.FILTERS_CHANGED.equals(event.getType())) {
330            filterModel.executeFilters();
331        }
332    }
333
334    @Override
335    public void primitivesAdded(PrimitivesAddedEvent event) {
336        filterModel.executeFilters(event.getPrimitives());
337    }
338
339    @Override
340    public void primitivesRemoved(PrimitivesRemovedEvent event) {
341        filterModel.executeFilters();
342    }
343
344    @Override
345    public void relationMembersChanged(RelationMembersChangedEvent event) {
346        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
347    }
348
349    @Override
350    public void tagsChanged(TagsChangedEvent event) {
351        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
352    }
353
354    @Override
355    public void wayNodesChanged(WayNodesChangedEvent event) {
356        filterModel.executeFilters(FilterModel.getAffectedPrimitives(event.getPrimitives()));
357    }
358
359    @Override
360    public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
361        filterModel.executeFilters();
362    }
363
364    /**
365     * This method is intended for Plugins getting the filtermodel and using .addFilter() to
366     * add a new filter.
367     * @return the filtermodel
368     */
369    public FilterTableModel getFilterModel() {
370        return filterModel;
371    }
372
373    abstract class AbstractFilterAction extends AbstractAction implements MultikeyShortcutAction {
374
375        protected transient Filter lastFilter;
376
377        @Override
378        public void actionPerformed(ActionEvent e) {
379            throw new UnsupportedOperationException();
380        }
381
382        @Override
383        public List<MultikeyInfo> getMultikeyCombinations() {
384            List<MultikeyInfo> result = new ArrayList<>();
385
386            for (int i = 0; i < filterModel.getRowCount(); i++) {
387                Filter filter = filterModel.getFilter(i);
388                MultikeyInfo info = new MultikeyInfo(i, filter.text);
389                result.add(info);
390            }
391
392            return result;
393        }
394
395        protected final boolean isLastFilterValid() {
396            return lastFilter != null && filterModel.getFilters().contains(lastFilter);
397        }
398
399        @Override
400        public MultikeyInfo getLastMultikeyAction() {
401            if (isLastFilterValid())
402                return new MultikeyInfo(-1, lastFilter.text);
403            else
404                return null;
405        }
406    }
407
408    private class EnableFilterAction extends AbstractFilterAction {
409
410        EnableFilterAction() {
411            putValue(SHORT_DESCRIPTION, tr("Enable filter"));
412            ENABLE_FILTER_SHORTCUT.setAccelerator(this);
413        }
414
415        @Override
416        public Shortcut getMultikeyShortcut() {
417            return ENABLE_FILTER_SHORTCUT;
418        }
419
420        @Override
421        public void executeMultikeyAction(int index, boolean repeatLastAction) {
422            if (index >= 0 && index < filterModel.getRowCount()) {
423                Filter filter = filterModel.getFilter(index);
424                filterModel.setValueAt(!filter.enable, index, FilterTableModel.COL_ENABLED);
425                lastFilter = filter;
426            } else if (repeatLastAction && isLastFilterValid()) {
427                filterModel.setValueAt(!lastFilter.enable, filterModel.getFilters().indexOf(lastFilter), FilterTableModel.COL_ENABLED);
428            }
429        }
430    }
431
432    private class HidingFilterAction extends AbstractFilterAction {
433
434        HidingFilterAction() {
435            putValue(SHORT_DESCRIPTION, tr("Hiding filter"));
436            HIDING_FILTER_SHORTCUT.setAccelerator(this);
437        }
438
439        @Override
440        public Shortcut getMultikeyShortcut() {
441            return HIDING_FILTER_SHORTCUT;
442        }
443
444        @Override
445        public void executeMultikeyAction(int index, boolean repeatLastAction) {
446            if (index >= 0 && index < filterModel.getRowCount()) {
447                Filter filter = filterModel.getFilter(index);
448                filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
449                lastFilter = filter;
450            } else if (repeatLastAction && isLastFilterValid()) {
451                filterModel.setValueAt(!lastFilter.hiding, filterModel.getFilters().indexOf(lastFilter), FilterTableModel.COL_HIDING);
452            }
453        }
454    }
455}
Note: See TracBrowser for help on using the repository browser.