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

Last change on this file since 15226 was 15226, checked in by Don-vip, 5 years ago

fix #14208 - add dedicated buttons in filter dialog to sort/reverse filters order.

Major overhaul/harmonization of our reorderable/sortable models.

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