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

Last change on this file was 18715, checked in by taylor.smock, 12 months ago

Fix #22798: Convert dialog actions which extend AbstractAction to ones which extend JosmAction

This will allow users to bind shortcuts to actions they make frequently, such as
creating new relations.

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