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

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

fix #17477 - AIOOBE

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