001 // License: GPL. Copyright 2007 by Immanuel Scholz and others
002 package org.openstreetmap.josm.gui.dialogs;
003
004 import static org.openstreetmap.josm.tools.I18n.marktr;
005 import static org.openstreetmap.josm.tools.I18n.tr;
006
007 import java.awt.Component;
008 import java.awt.Graphics2D;
009 import java.awt.event.ActionEvent;
010 import java.awt.event.KeyEvent;
011 import java.awt.event.MouseEvent;
012 import java.util.ArrayList;
013 import java.util.Arrays;
014 import java.util.Collection;
015 import java.util.HashSet;
016 import java.util.List;
017 import java.util.Set;
018 import java.util.Stack;
019
020 import javax.swing.AbstractAction;
021 import javax.swing.JCheckBox;
022 import javax.swing.JTable;
023 import javax.swing.ListSelectionModel;
024 import javax.swing.SwingUtilities;
025 import javax.swing.table.DefaultTableCellRenderer;
026 import javax.swing.table.JTableHeader;
027 import javax.swing.table.TableCellRenderer;
028
029 import org.openstreetmap.josm.Main;
030 import org.openstreetmap.josm.actions.search.SearchAction;
031 import org.openstreetmap.josm.data.osm.Filter;
032 import org.openstreetmap.josm.data.osm.OsmPrimitive;
033 import org.openstreetmap.josm.data.osm.Relation;
034 import org.openstreetmap.josm.data.osm.RelationMember;
035 import org.openstreetmap.josm.data.osm.Way;
036 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
037 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
038 import org.openstreetmap.josm.data.osm.event.DataSetListener;
039 import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
040 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
041 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
042 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
043 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
044 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
045 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
046 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
047 import org.openstreetmap.josm.gui.SideButton;
048 import org.openstreetmap.josm.tools.ImageProvider;
049 import org.openstreetmap.josm.tools.InputMapUtils;
050 import org.openstreetmap.josm.tools.MultikeyActionsHandler;
051 import org.openstreetmap.josm.tools.MultikeyShortcutAction;
052 import org.openstreetmap.josm.tools.Shortcut;
053
054 /**
055 *
056 * @author Petr_Dlouh??
057 */
058 public class FilterDialog extends ToggleDialog implements DataSetListener {
059
060 private JTable userTable;
061 private FilterTableModel filterModel = new FilterTableModel();
062 private SideButton addButton;
063 private SideButton editButton;
064 private SideButton deleteButton;
065 private SideButton upButton;
066 private SideButton downButton;
067
068 private EnableFilterAction enableFilterAction;
069 private HidingFilterAction hidingFilterAction;
070
071 public FilterDialog(){
072 super(tr("Filter"), "filter", tr("Filter objects and hide/disable them."),
073 Shortcut.registerShortcut("subwindow:filter", tr("Toggle: {0}", tr("Filter")),
074 KeyEvent.VK_F, Shortcut.ALT_SHIFT), 162);
075 build();
076 enableFilterAction = new EnableFilterAction();
077 hidingFilterAction = new HidingFilterAction();
078 MultikeyActionsHandler.getInstance().addAction(enableFilterAction);
079 MultikeyActionsHandler.getInstance().addAction(hidingFilterAction);
080 }
081
082 @Override
083 public void showNotify() {
084 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
085 filterModel.executeFilters();
086 }
087
088 @Override
089 public void hideNotify() {
090 DatasetEventManager.getInstance().removeDatasetListener(this);
091 filterModel.clearFilterFlags();
092 Main.map.mapView.repaint();
093 }
094
095 private static final Shortcut ENABLE_FILTER_SHORTCUT
096 = Shortcut.registerShortcut("core_multikey:enableFilter", tr("Multikey: {0}", tr("Enable filter")),
097 KeyEvent.VK_E, Shortcut.ALT_CTRL);
098
099 private static final Shortcut HIDING_FILTER_SHORTCUT
100 = Shortcut.registerShortcut("core_multikey:hidingFilter", tr("Multikey: {0}", tr("Hide filter")),
101 KeyEvent.VK_H, Shortcut.ALT_CTRL);
102
103
104 protected final String[] columnToolTips = {
105 Main.platform.makeTooltip(tr("Enable filter"), ENABLE_FILTER_SHORTCUT),
106 Main.platform.makeTooltip(tr("Hiding filter"), HIDING_FILTER_SHORTCUT),
107 null,
108 tr("Inverse filter"),
109 tr("Filter mode")
110 };
111
112 protected void build() {
113 userTable = new JTable(filterModel){
114 @Override
115 protected JTableHeader createDefaultTableHeader() {
116 return new JTableHeader(columnModel) {
117 @Override
118 public String getToolTipText(MouseEvent e) {
119 java.awt.Point p = e.getPoint();
120 int index = columnModel.getColumnIndexAtX(p.x);
121 int realIndex = columnModel.getColumn(index).getModelIndex();
122 return columnToolTips[realIndex];
123 }
124 };
125 }
126 };
127
128 userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
129
130 userTable.getColumnModel().getColumn(0).setMaxWidth(1);
131 userTable.getColumnModel().getColumn(1).setMaxWidth(1);
132 userTable.getColumnModel().getColumn(3).setMaxWidth(1);
133 userTable.getColumnModel().getColumn(4).setMaxWidth(1);
134
135 userTable.getColumnModel().getColumn(0).setResizable(false);
136 userTable.getColumnModel().getColumn(1).setResizable(false);
137 userTable.getColumnModel().getColumn(3).setResizable(false);
138 userTable.getColumnModel().getColumn(4).setResizable(false);
139
140 userTable.setDefaultRenderer(Boolean.class, new BooleanRenderer());
141 userTable.setDefaultRenderer(String.class, new StringRenderer());
142
143 addButton = new SideButton(new AbstractAction() {
144 {
145 putValue(NAME, tr("Add"));
146 putValue(SHORT_DESCRIPTION, tr("Add filter."));
147 putValue(SMALL_ICON, ImageProvider.get("dialogs","add"));
148 }
149 @Override
150 public void actionPerformed(ActionEvent e) {
151 Filter filter = (Filter)SearchAction.showSearchDialog(new Filter());
152 if(filter != null){
153 filterModel.addFilter(filter);
154 }
155 }});
156 editButton = new SideButton(new AbstractAction() {
157 {
158 putValue(NAME, tr("Edit"));
159 putValue(SHORT_DESCRIPTION, tr("Edit filter."));
160 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
161 }
162 @Override
163 public void actionPerformed(ActionEvent e) {
164 int index = userTable.getSelectionModel().getMinSelectionIndex();
165 if(index < 0) return;
166 Filter f = filterModel.getFilter(index);
167 Filter filter = (Filter)SearchAction.showSearchDialog(f);
168 if(filter != null){
169 filterModel.setFilter(index, filter);
170 }
171 }
172 });
173 deleteButton = new SideButton(new AbstractAction() {
174 {
175 putValue(NAME, tr("Delete"));
176 putValue(SHORT_DESCRIPTION, tr("Delete filter."));
177 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
178 }
179 @Override
180 public void actionPerformed(ActionEvent e) {
181 int index = userTable.getSelectionModel().getMinSelectionIndex();
182 if(index < 0) return;
183 filterModel.removeFilter(index);
184 }
185 });
186 upButton = new SideButton(new AbstractAction() {
187 {
188 putValue(NAME, tr("Up"));
189 putValue(SHORT_DESCRIPTION, tr("Move filter up."));
190 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
191 }
192 @Override
193 public void actionPerformed(ActionEvent e) {
194 int index = userTable.getSelectionModel().getMinSelectionIndex();
195 if(index < 0) return;
196 filterModel.moveUpFilter(index);
197 userTable.getSelectionModel().setSelectionInterval(index-1, index-1);
198 }
199
200 });
201 downButton = new SideButton(new AbstractAction() {
202 {
203 putValue(NAME, tr("Down"));
204 putValue(SHORT_DESCRIPTION, tr("Move filter down."));
205 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
206 }
207 @Override
208 public void actionPerformed(ActionEvent e) {
209 int index = userTable.getSelectionModel().getMinSelectionIndex();
210 if(index < 0) return;
211 filterModel.moveDownFilter(index);
212 userTable.getSelectionModel().setSelectionInterval(index+1, index+1);
213 }
214 });
215
216 // Toggle filter "enabled" on Enter
217 InputMapUtils.addEnterAction(userTable, new AbstractAction() {
218 public void actionPerformed(ActionEvent e) {
219 int index = userTable.getSelectedRow();
220 if (index<0) return;
221 Filter filter = filterModel.getFilter(index);
222 filterModel.setValueAt(!filter.enable, index, FilterTableModel.COL_ENABLED);
223 }
224 });
225
226 // Toggle filter "hiding" on Spacebar
227 InputMapUtils.addSpacebarAction(userTable, new AbstractAction() {
228 public void actionPerformed(ActionEvent e) {
229 int index = userTable.getSelectedRow();
230 if (index<0) return;
231 Filter filter = filterModel.getFilter(index);
232 filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
233 }
234 });
235
236 createLayout(userTable, true, Arrays.asList(new SideButton[] {
237 addButton, editButton, deleteButton, upButton, downButton
238 }));
239 }
240
241 @Override
242 public void destroy() {
243 MultikeyActionsHandler.getInstance().removeAction(enableFilterAction);
244 MultikeyActionsHandler.getInstance().removeAction(hidingFilterAction);
245 super.destroy();
246 }
247
248 static class StringRenderer extends DefaultTableCellRenderer {
249 @Override
250 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
251 FilterTableModel model = (FilterTableModel)table.getModel();
252 Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
253 cell.setEnabled(model.isCellEnabled(row, column));
254 return cell;
255 }
256 }
257
258 static class BooleanRenderer extends JCheckBox implements TableCellRenderer {
259 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
260 FilterTableModel model = (FilterTableModel)table.getModel();
261 setSelected(value != null && (Boolean)value);
262 setEnabled(model.isCellEnabled(row, column));
263 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
264 return this;
265 }
266 }
267
268 public void updateDialogHeader() {
269 SwingUtilities.invokeLater(new Runnable() {
270 public void run() {
271 setTitle(tr("Filter Hidden:{0} Disabled:{1}", filterModel.disabledAndHiddenCount, filterModel.disabledCount));
272 }
273 });
274 }
275
276 public void drawOSDText(Graphics2D g) {
277 filterModel.drawOSDText(g);
278 }
279
280 /**
281 *
282 * @param primitive
283 * @return List of primitives whose filtering can be affected by change in primitive
284 */
285 private Collection<OsmPrimitive> getAffectedPrimitives(Collection<? extends OsmPrimitive> primitives) {
286 // Filters can use nested parent/child expression so complete tree is necessary
287 Set<OsmPrimitive> result = new HashSet<OsmPrimitive>();
288 Stack<OsmPrimitive> stack = new Stack<OsmPrimitive>();
289 stack.addAll(primitives);
290
291 while (!stack.isEmpty()) {
292 OsmPrimitive p = stack.pop();
293
294 if (result.contains(p)) {
295 continue;
296 }
297
298 result.add(p);
299
300 if (p instanceof Way) {
301 for (OsmPrimitive n: ((Way)p).getNodes()) {
302 stack.push(n);
303 }
304 } else if (p instanceof Relation) {
305 for (RelationMember rm: ((Relation)p).getMembers()) {
306 stack.push(rm.getMember());
307 }
308 }
309
310 for (OsmPrimitive ref: p.getReferrers()) {
311 stack.push(ref);
312 }
313 }
314
315 return result;
316 }
317
318 public void dataChanged(DataChangedEvent event) {
319 filterModel.executeFilters();
320 }
321
322 public void nodeMoved(NodeMovedEvent event) {
323 // Do nothing
324 }
325
326 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
327 filterModel.executeFilters();
328 }
329
330 public void primitivesAdded(PrimitivesAddedEvent event) {
331 filterModel.executeFilters(event.getPrimitives());
332 }
333
334 public void primitivesRemoved(PrimitivesRemovedEvent event) {
335 filterModel.executeFilters();
336 }
337
338 public void relationMembersChanged(RelationMembersChangedEvent event) {
339 filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
340 }
341
342 public void tagsChanged(TagsChangedEvent event) {
343 filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
344 }
345
346 public void wayNodesChanged(WayNodesChangedEvent event) {
347 filterModel.executeFilters(getAffectedPrimitives(event.getPrimitives()));
348 }
349
350 abstract class AbstractFilterAction extends AbstractAction implements MultikeyShortcutAction {
351
352 protected Filter lastFilter;
353
354 @Override
355 public void actionPerformed(ActionEvent e) {
356 throw new UnsupportedOperationException();
357 }
358
359 @Override
360 public List<MultikeyInfo> getMultikeyCombinations() {
361 List<MultikeyInfo> result = new ArrayList<MultikeyShortcutAction.MultikeyInfo>();
362
363 for (int i=0; i<filterModel.getRowCount(); i++) {
364 Filter filter = filterModel.getFilter(i);
365 MultikeyInfo info = new MultikeyInfo(i, filter.text);
366 result.add(info);
367 }
368
369 return result;
370 }
371
372 protected boolean isLastFilterValid() {
373 return lastFilter != null && filterModel.getFilters().contains(lastFilter);
374 }
375
376 @Override
377 public MultikeyInfo getLastMultikeyAction() {
378 if (isLastFilterValid())
379 return new MultikeyInfo(-1, lastFilter.text);
380 else
381 return null;
382 }
383
384 }
385
386 private class EnableFilterAction extends AbstractFilterAction {
387
388 EnableFilterAction() {
389 putValue(SHORT_DESCRIPTION, tr("Enable filter"));
390 ENABLE_FILTER_SHORTCUT.setAccelerator(this);
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 HIDING_FILTER_SHORTCUT.setAccelerator(this);
415 }
416
417 @Override
418 public Shortcut getMultikeyShortcut() {
419 return HIDING_FILTER_SHORTCUT;
420 }
421
422 @Override
423 public void executeMultikeyAction(int index, boolean repeatLastAction) {
424 if (index >= 0 && index < filterModel.getRowCount()) {
425 Filter filter = filterModel.getFilter(index);
426 filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
427 lastFilter = filter;
428 } else if (repeatLastAction && isLastFilterValid()) {
429 filterModel.setValueAt(!lastFilter.hiding, filterModel.getFilters().indexOf(lastFilter), FilterTableModel.COL_HIDING);
430 }
431 }
432
433 }
434 }