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, marktr("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, marktr("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, marktr("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, marktr("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, marktr("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 }