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

Revision 5200, 16.3 KB checked in by akks, 5 weeks ago (diff)

see #7626, fix #7463: keys Ctrl-Shift-Up/Down, Enter, Spacebar work better in toggle dialogs
Enter and Spacebar = useful actions for list items (select, toggle, etc.)

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