001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.properties; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.Container; 008import java.awt.Font; 009import java.awt.GridBagLayout; 010import java.awt.Point; 011import java.awt.event.ActionEvent; 012import java.awt.event.InputEvent; 013import java.awt.event.KeyEvent; 014import java.awt.event.MouseAdapter; 015import java.awt.event.MouseEvent; 016import java.net.HttpURLConnection; 017import java.net.URI; 018import java.net.URLEncoder; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.EnumSet; 025import java.util.HashMap; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Set; 031import java.util.TreeMap; 032import java.util.TreeSet; 033 034import javax.swing.AbstractAction; 035import javax.swing.JComponent; 036import javax.swing.JLabel; 037import javax.swing.JPanel; 038import javax.swing.JPopupMenu; 039import javax.swing.JScrollPane; 040import javax.swing.JTable; 041import javax.swing.KeyStroke; 042import javax.swing.ListSelectionModel; 043import javax.swing.event.ListSelectionEvent; 044import javax.swing.event.ListSelectionListener; 045import javax.swing.table.DefaultTableCellRenderer; 046import javax.swing.table.DefaultTableModel; 047import javax.swing.table.TableColumnModel; 048import javax.swing.table.TableModel; 049 050import org.openstreetmap.josm.Main; 051import org.openstreetmap.josm.actions.JosmAction; 052import org.openstreetmap.josm.actions.relation.DownloadMembersAction; 053import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 054import org.openstreetmap.josm.actions.relation.SelectInRelationListAction; 055import org.openstreetmap.josm.actions.relation.SelectMembersAction; 056import org.openstreetmap.josm.actions.relation.SelectRelationAction; 057import org.openstreetmap.josm.actions.search.SearchAction.SearchMode; 058import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting; 059import org.openstreetmap.josm.command.ChangeCommand; 060import org.openstreetmap.josm.command.ChangePropertyCommand; 061import org.openstreetmap.josm.command.Command; 062import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 063import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 064import org.openstreetmap.josm.data.SelectionChangedListener; 065import org.openstreetmap.josm.data.osm.IRelation; 066import org.openstreetmap.josm.data.osm.Node; 067import org.openstreetmap.josm.data.osm.OsmPrimitive; 068import org.openstreetmap.josm.data.osm.Relation; 069import org.openstreetmap.josm.data.osm.RelationMember; 070import org.openstreetmap.josm.data.osm.Tag; 071import org.openstreetmap.josm.data.osm.Way; 072import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 073import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; 074import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 075import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 076import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 077import org.openstreetmap.josm.gui.DefaultNameFormatter; 078import org.openstreetmap.josm.gui.ExtendedDialog; 079import org.openstreetmap.josm.gui.MapView; 080import org.openstreetmap.josm.gui.PopupMenuHandler; 081import org.openstreetmap.josm.gui.SideButton; 082import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 083import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel.PresetHandler; 084import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 085import org.openstreetmap.josm.gui.layer.OsmDataLayer; 086import org.openstreetmap.josm.gui.tagging.TaggingPreset; 087import org.openstreetmap.josm.gui.tagging.TaggingPresetType; 088import org.openstreetmap.josm.gui.util.GuiHelper; 089import org.openstreetmap.josm.gui.util.HighlightHelper; 090import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 091import org.openstreetmap.josm.tools.GBC; 092import org.openstreetmap.josm.tools.ImageProvider; 093import org.openstreetmap.josm.tools.InputMapUtils; 094import org.openstreetmap.josm.tools.LanguageInfo; 095import org.openstreetmap.josm.tools.OpenBrowser; 096import org.openstreetmap.josm.tools.Shortcut; 097import org.openstreetmap.josm.tools.Utils; 098 099/** 100 * This dialog displays the tags of the current selected primitives. 101 * 102 * If no object is selected, the dialog list is empty. 103 * If only one is selected, all tags of this object are selected. 104 * If more than one object are selected, the sum of all tags are displayed. If the 105 * different objects share the same tag, the shared value is displayed. If they have 106 * different values, all of them are put in a combo box and the string "<different>" 107 * is displayed in italic. 108 * 109 * Below the list, the user can click on an add, modify and delete tag button to 110 * edit the table selection value. 111 * 112 * The command is applied to all selected entries. 113 * 114 * @author imi 115 */ 116public class PropertiesDialog extends ToggleDialog implements SelectionChangedListener, MapView.EditLayerChangeListener, DataSetListenerAdapter.Listener, PreferenceChangedListener { 117 118 /** 119 * hook for roadsigns plugin to display a small button in the upper right corner of this dialog 120 */ 121 public static final JPanel pluginHook = new JPanel(); 122 123 /** 124 * The tag data of selected objects. 125 */ 126 private final DefaultTableModel tagData = new ReadOnlyTableModel(); 127 128 /** 129 * The membership data of selected objects. 130 */ 131 private final DefaultTableModel membershipData = new ReadOnlyTableModel(); 132 133 /** 134 * The tags table. 135 */ 136 private final JTable tagTable = new JTable(tagData); 137 138 /** 139 * The membership table. 140 */ 141 private final JTable membershipTable = new JTable(membershipData); 142 143 // Popup menus 144 private final JPopupMenu tagMenu = new JPopupMenu(); 145 private final JPopupMenu membershipMenu = new JPopupMenu(); 146 147 // Popup menu handlers 148 private final PopupMenuHandler tagMenuHandler = new PopupMenuHandler(tagMenu); 149 private final PopupMenuHandler membershipMenuHandler = new PopupMenuHandler(membershipMenu); 150 151 private final Map<String, Map<String, Integer>> valueCount = new TreeMap<String, Map<String, Integer>>(); 152 /** 153 * This sub-object is responsible for all adding and editing of tags 154 */ 155 private final TagEditHelper editHelper = new TagEditHelper(tagData, valueCount); 156 157 private final DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this); 158 private final HelpAction helpAction = new HelpAction(); 159 private final PasteValueAction pasteValueAction = new PasteValueAction(); 160 private final CopyValueAction copyValueAction = new CopyValueAction(); 161 private final CopyKeyValueAction copyKeyValueAction = new CopyKeyValueAction(); 162 private final CopyAllKeyValueAction copyAllKeyValueAction = new CopyAllKeyValueAction(); 163 private final SearchAction searchActionSame = new SearchAction(true); 164 private final SearchAction searchActionAny = new SearchAction(false); 165 private final AddAction addAction = new AddAction(); 166 private final EditAction editAction = new EditAction(); 167 private final DeleteAction deleteAction = new DeleteAction(); 168 private final JosmAction[] josmActions = new JosmAction[]{addAction, editAction, deleteAction}; 169 170 // relation actions 171 private final SelectInRelationListAction setRelationSelectionAction = new SelectInRelationListAction(); 172 private final SelectRelationAction selectRelationAction = new SelectRelationAction(false); 173 private final SelectRelationAction addRelationToSelectionAction = new SelectRelationAction(true); 174 175 private final DownloadMembersAction downloadMembersAction = new DownloadMembersAction(); 176 private final DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction(); 177 178 private final SelectMembersAction selectMembersAction = new SelectMembersAction(false); 179 private final SelectMembersAction addMembersToSelectionAction = new SelectMembersAction(true); 180 181 private final HighlightHelper highlightHelper= new HighlightHelper(); 182 183 /** 184 * The Add button (needed to be able to disable it) 185 */ 186 private final SideButton btnAdd = new SideButton(addAction); 187 /** 188 * The Edit button (needed to be able to disable it) 189 */ 190 private final SideButton btnEdit = new SideButton(editAction); 191 /** 192 * The Delete button (needed to be able to disable it) 193 */ 194 private final SideButton btnDel = new SideButton(deleteAction); 195 /** 196 * Matching preset display class 197 */ 198 private final PresetListPanel presets = new PresetListPanel(); 199 200 /** 201 * Text to display when nothing selected. 202 */ 203 private final JLabel selectSth = new JLabel("<html><p>" 204 + tr("Select objects for which to change tags.") + "</p></html>"); 205 206 private PresetHandler presetHandler = new PresetHandler() { 207 @Override public void updateTags(List<Tag> tags) { 208 Command command = TaggingPreset.createCommand(getSelection(), tags); 209 if (command != null) Main.main.undoRedo.add(command); 210 } 211 212 @Override public Collection<OsmPrimitive> getSelection() { 213 if (Main.main == null) return null; 214 if (Main.main.getCurrentDataSet() == null) return null; 215 return Main.main.getCurrentDataSet().getSelected(); 216 } 217 }; 218 219 // <editor-fold defaultstate="collapsed" desc="Dialog construction and helper methods"> 220 221 /** 222 * Create a new PropertiesDialog 223 */ 224 public PropertiesDialog() { 225 super(tr("Tags/Memberships"), "propertiesdialog", tr("Tags for selected objects."), 226 Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Tags/Memberships")), KeyEvent.VK_P, 227 Shortcut.ALT_SHIFT), 150, true); 228 229 setupTagsMenu(); 230 buildTagsTable(); 231 232 setupMembershipMenu(); 233 buildMembershipTable(); 234 235 // combine both tables and wrap them in a scrollPane 236 JPanel bothTables = new JPanel(); 237 boolean top = Main.pref.getBoolean("properties.presets.top", true); 238 bothTables.setLayout(new GridBagLayout()); 239 if(top) { 240 bothTables.add(presets, GBC.std().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2).anchor(GBC.NORTHWEST)); 241 double epsilon = Double.MIN_VALUE; // need to set a weight or else anchor value is ignored 242 bothTables.add(pluginHook, GBC.eol().insets(0,1,1,1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon)); 243 } 244 bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10)); 245 bothTables.add(tagTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL)); 246 bothTables.add(tagTable, GBC.eol().fill(GBC.BOTH)); 247 bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL)); 248 bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH)); 249 if(!top) { 250 bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2)); 251 } 252 253 setupKeyboardShortcuts(); 254 255 // Let the action know when selection in the tables change 256 tagTable.getSelectionModel().addListSelectionListener(editAction); 257 membershipTable.getSelectionModel().addListSelectionListener(editAction); 258 tagTable.getSelectionModel().addListSelectionListener(deleteAction); 259 membershipTable.getSelectionModel().addListSelectionListener(deleteAction); 260 261 262 JScrollPane scrollPane = (JScrollPane) createLayout(bothTables, true, Arrays.asList(new SideButton[] { 263 this.btnAdd, this.btnEdit, this.btnDel 264 })); 265 266 MouseClickWatch mouseClickWatch = new MouseClickWatch(); 267 tagTable.addMouseListener(mouseClickWatch); 268 membershipTable.addMouseListener(mouseClickWatch); 269 scrollPane.addMouseListener(mouseClickWatch); 270 271 selectSth.setPreferredSize(scrollPane.getSize()); 272 presets.setSize(scrollPane.getSize()); 273 274 editHelper.loadTagsIfNeeded(); 275 276 Main.pref.addPreferenceChangeListener(this); 277 } 278 279 private void buildTagsTable() { 280 // setting up the tags table 281 282 tagData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")}); 283 tagTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 284 tagTable.getTableHeader().setReorderingAllowed(false); 285 286 PropertiesCellRenderer cellRenderer = new PropertiesCellRenderer(); 287 tagTable.getColumnModel().getColumn(0).setCellRenderer(cellRenderer); 288 tagTable.getColumnModel().getColumn(1).setCellRenderer(cellRenderer); 289 } 290 291 private void buildMembershipTable() { 292 membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role"),tr("Position")}); 293 membershipTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 294 295 TableColumnModel mod = membershipTable.getColumnModel(); 296 membershipTable.getTableHeader().setReorderingAllowed(false); 297 mod.getColumn(0).setCellRenderer(new DefaultTableCellRenderer() { 298 @Override public Component getTableCellRendererComponent(JTable table, Object value, 299 boolean isSelected, boolean hasFocus, int row, int column) { 300 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); 301 if (value == null) 302 return this; 303 if (c instanceof JLabel) { 304 JLabel label = (JLabel)c; 305 Relation r = (Relation)value; 306 label.setText(r.getDisplayName(DefaultNameFormatter.getInstance())); 307 if (r.isDisabledAndHidden()) { 308 label.setFont(label.getFont().deriveFont(Font.ITALIC)); 309 } 310 } 311 return c; 312 } 313 }); 314 315 mod.getColumn(1).setCellRenderer(new DefaultTableCellRenderer() { 316 @Override public Component getTableCellRendererComponent(JTable table, Object value, 317 boolean isSelected, boolean hasFocus, int row, int column) { 318 if (value == null) 319 return this; 320 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); 321 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden(); 322 if (c instanceof JLabel) { 323 JLabel label = (JLabel)c; 324 MemberInfo col = (MemberInfo) value; 325 326 String text = null; 327 for (RelationMember r : col.role) { 328 if (text == null) { 329 text = r.getRole(); 330 } 331 else if (!text.equals(r.getRole())) { 332 text = tr("<different>"); 333 break; 334 } 335 } 336 337 label.setText(text); 338 if (isDisabledAndHidden) { 339 label.setFont(label.getFont().deriveFont(Font.ITALIC)); 340 } 341 } 342 return c; 343 } 344 }); 345 346 mod.getColumn(2).setCellRenderer(new DefaultTableCellRenderer() { 347 @Override public Component getTableCellRendererComponent(JTable table, Object value, 348 boolean isSelected, boolean hasFocus, int row, int column) { 349 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); 350 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden(); 351 if (c instanceof JLabel) { 352 JLabel label = (JLabel)c; 353 label.setText(((MemberInfo) table.getValueAt(row, 1)).getPositionString()); 354 if (isDisabledAndHidden) { 355 label.setFont(label.getFont().deriveFont(Font.ITALIC)); 356 } 357 } 358 return c; 359 } 360 }); 361 mod.getColumn(2).setPreferredWidth(20); 362 mod.getColumn(1).setPreferredWidth(40); 363 mod.getColumn(0).setPreferredWidth(200); 364 } 365 366 /** 367 * creates the popup menu @field membershipMenu and its launcher on membership table 368 */ 369 private void setupMembershipMenu() { 370 // setting up the membership table 371 membershipMenuHandler.addAction(setRelationSelectionAction); 372 membershipMenuHandler.addAction(selectRelationAction); 373 membershipMenuHandler.addAction(addRelationToSelectionAction); 374 membershipMenuHandler.addAction(selectMembersAction); 375 membershipMenuHandler.addAction(addMembersToSelectionAction); 376 membershipMenu.addSeparator(); 377 membershipMenuHandler.addAction(downloadMembersAction); 378 membershipMenuHandler.addAction(downloadSelectedIncompleteMembersAction); 379 membershipMenu.addSeparator(); 380 membershipMenu.add(helpAction); 381 382 membershipTable.addMouseListener(new PopupMenuLauncher(membershipMenu) { 383 @Override 384 protected int checkTableSelection(JTable table, Point p) { 385 int row = super.checkTableSelection(table, p); 386 List<Relation> rels = new ArrayList<Relation>(); 387 for (int i: table.getSelectedRows()) { 388 rels.add((Relation) table.getValueAt(i, 0)); 389 } 390 membershipMenuHandler.setPrimitives(rels); 391 return row; 392 } 393 394 @Override 395 public void mouseClicked(MouseEvent e) { 396 //update highlights 397 if (Main.isDisplayingMapView()) { 398 int row = membershipTable.rowAtPoint(e.getPoint()); 399 if (row>=0) { 400 if (highlightHelper.highlightOnly((Relation) membershipTable.getValueAt(row, 0))) { 401 Main.map.mapView.repaint(); 402 } 403 } 404 } 405 super.mouseClicked(e); 406 } 407 408 @Override 409 public void mouseExited(MouseEvent me) { 410 highlightHelper.clear(); 411 } 412 }); 413 } 414 415 /** 416 * creates the popup menu @field tagMenu and its launcher on tag table 417 */ 418 private void setupTagsMenu() { 419 tagMenu.add(pasteValueAction); 420 tagMenu.add(copyValueAction); 421 tagMenu.add(copyKeyValueAction); 422 tagMenu.add(copyAllKeyValueAction); 423 tagMenu.addSeparator(); 424 tagMenu.add(searchActionAny); 425 tagMenu.add(searchActionSame); 426 tagMenu.addSeparator(); 427 tagMenu.add(helpAction); 428 tagTable.addMouseListener(new PopupMenuLauncher(tagMenu)); 429 } 430 431 /** 432 * Assignas all needed keys like Enter and Spacebar to most important actions 433 */ 434 private void setupKeyboardShortcuts() { 435 436 // ENTER = editAction, open "edit" dialog 437 tagTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 438 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter"); 439 tagTable.getActionMap().put("onTableEnter",editAction); 440 membershipTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 441 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter"); 442 membershipTable.getActionMap().put("onTableEnter",editAction); 443 444 // INSERT button = addAction, open "add tag" dialog 445 tagTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 446 .put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0),"onTableInsert"); 447 tagTable.getActionMap().put("onTableInsert",addAction); 448 449 // unassign some standard shortcuts for JTable to allow upload / download 450 InputMapUtils.unassignCtrlShiftUpDown(tagTable, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 451 452 // unassign some standard shortcuts for correct copy-pasting, fix #8508 453 tagTable.setTransferHandler(null); 454 455 tagTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 456 .put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),"onCopy"); 457 tagTable.getActionMap().put("onCopy",copyKeyValueAction); 458 459 // allow using enter to add tags for all look&feel configurations 460 InputMapUtils.enableEnter(this.btnAdd); 461 462 // DEL button = deleteAction 463 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 464 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete" 465 ); 466 getActionMap().put("delete", deleteAction); 467 468 // F1 button = custom help action 469 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 470 KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "onHelp"); 471 getActionMap().put("onHelp", helpAction); 472 } 473 474 /** 475 * This simply fires up an {@link RelationEditor} for the relation shown; everything else 476 * is the editor's business. 477 * 478 * @param row 479 */ 480 private void editMembership(int row) { 481 Relation relation = (Relation)membershipData.getValueAt(row, 0); 482 Main.map.relationListDialog.selectRelation(relation); 483 RelationEditor.getEditor( 484 Main.main.getEditLayer(), 485 relation, 486 ((MemberInfo) membershipData.getValueAt(row, 1)).role).setVisible(true); 487 } 488 489 private int findRow(TableModel model, Object value) { 490 for (int i=0; i<model.getRowCount(); i++) { 491 if (model.getValueAt(i, 0).equals(value)) 492 return i; 493 } 494 return -1; 495 } 496 497 /** 498 * Update selection status, call @{link #selectionChanged} function. 499 */ 500 private void updateSelection() { 501 if (Main.main.getCurrentDataSet() == null) { 502 selectionChanged(Collections.<OsmPrimitive>emptyList()); 503 } else { 504 selectionChanged(Main.main.getCurrentDataSet().getSelected()); 505 } 506 } 507 508 // </editor-fold> 509 510 // <editor-fold defaultstate="collapsed" desc="Event listeners methods"> 511 512 @Override 513 public void showNotify() { 514 DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED); 515 SelectionEventManager.getInstance().addSelectionListener(this, FireMode.IN_EDT_CONSOLIDATED); 516 MapView.addEditLayerChangeListener(this); 517 for (JosmAction action : josmActions) { 518 Main.registerActionShortcut(action); 519 } 520 updateSelection(); 521 } 522 523 @Override 524 public void hideNotify() { 525 DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter); 526 SelectionEventManager.getInstance().removeSelectionListener(this); 527 MapView.removeEditLayerChangeListener(this); 528 for (JosmAction action : josmActions) { 529 Main.unregisterActionShortcut(action); 530 } 531 } 532 533 @Override 534 public void setVisible(boolean b) { 535 super.setVisible(b); 536 if (b && Main.main.getCurrentDataSet() != null) { 537 selectionChanged(Main.main.getCurrentDataSet().getSelected()); 538 } 539 } 540 541 @Override 542 public void destroy() { 543 super.destroy(); 544 Main.pref.removePreferenceChangeListener(this); 545 for (JosmAction action : josmActions) { 546 action.destroy(); 547 } 548 Container parent = pluginHook.getParent(); 549 if (parent != null) { 550 parent.remove(pluginHook); 551 } 552 } 553 554 @Override 555 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 556 if (!isVisible()) 557 return; 558 if (tagTable == null) 559 return; // selection changed may be received in base class constructor before init 560 if (tagTable.getCellEditor() != null) { 561 tagTable.getCellEditor().cancelCellEditing(); 562 } 563 564 String selectedTag; 565 Relation selectedRelation = null; 566 selectedTag = editHelper.getChangedKey(); // select last added or last edited key by default 567 if (selectedTag == null && tagTable.getSelectedRowCount() == 1) { 568 selectedTag = (String)tagData.getValueAt(tagTable.getSelectedRow(), 0); 569 } 570 if (membershipTable.getSelectedRowCount() == 1) { 571 selectedRelation = (Relation)membershipData.getValueAt(membershipTable.getSelectedRow(), 0); 572 } 573 574 // re-load tag data 575 tagData.setRowCount(0); 576 577 final boolean displayDiscardableKeys = Main.pref.getBoolean("display.discardable-keys", false); 578 final Map<String, Integer> keyCount = new HashMap<String, Integer>(); 579 final Map<String, String> tags = new HashMap<String, String>(); 580 valueCount.clear(); 581 EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class); 582 for (OsmPrimitive osm : newSelection) { 583 types.add(TaggingPresetType.forPrimitive(osm)); 584 for (String key : osm.keySet()) { 585 if (displayDiscardableKeys || !OsmPrimitive.getDiscardableKeys().contains(key)) { 586 String value = osm.get(key); 587 keyCount.put(key, keyCount.containsKey(key) ? keyCount.get(key) + 1 : 1); 588 if (valueCount.containsKey(key)) { 589 Map<String, Integer> v = valueCount.get(key); 590 v.put(value, v.containsKey(value) ? v.get(value) + 1 : 1); 591 } else { 592 TreeMap<String, Integer> v = new TreeMap<String, Integer>(); 593 v.put(value, 1); 594 valueCount.put(key, v); 595 } 596 } 597 } 598 } 599 for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) { 600 int count = 0; 601 for (Entry<String, Integer> e1 : e.getValue().entrySet()) { 602 count += e1.getValue(); 603 } 604 if (count < newSelection.size()) { 605 e.getValue().put("", newSelection.size() - count); 606 } 607 tagData.addRow(new Object[]{e.getKey(), e.getValue()}); 608 tags.put(e.getKey(), e.getValue().size() == 1 609 ? e.getValue().keySet().iterator().next() : tr("<different>")); 610 } 611 612 membershipData.setRowCount(0); 613 614 Map<Relation, MemberInfo> roles = new HashMap<Relation, MemberInfo>(); 615 for (OsmPrimitive primitive: newSelection) { 616 for (OsmPrimitive ref: primitive.getReferrers()) { 617 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) { 618 Relation r = (Relation) ref; 619 MemberInfo mi = roles.get(r); 620 if(mi == null) { 621 mi = new MemberInfo(); 622 } 623 roles.put(r, mi); 624 int i = 1; 625 for (RelationMember m : r.getMembers()) { 626 if (m.getMember() == primitive) { 627 mi.add(m, i); 628 } 629 ++i; 630 } 631 } 632 } 633 } 634 635 List<Relation> sortedRelations = new ArrayList<Relation>(roles.keySet()); 636 Collections.sort(sortedRelations, new Comparator<Relation>() { 637 @Override public int compare(Relation o1, Relation o2) { 638 int comp = Boolean.valueOf(o1.isDisabledAndHidden()).compareTo(o2.isDisabledAndHidden()); 639 if (comp == 0) { 640 comp = o1.getDisplayName(DefaultNameFormatter.getInstance()).compareTo(o2.getDisplayName(DefaultNameFormatter.getInstance())); 641 } 642 return comp; 643 }} 644 ); 645 646 for (Relation r: sortedRelations) { 647 membershipData.addRow(new Object[]{r, roles.get(r)}); 648 } 649 650 presets.updatePresets(types, tags, presetHandler); 651 652 membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0); 653 membershipTable.setVisible(membershipData.getRowCount() > 0); 654 655 boolean hasSelection = !newSelection.isEmpty(); 656 boolean hasTags = hasSelection && tagData.getRowCount() > 0; 657 boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0; 658 btnAdd.setEnabled(hasSelection); 659 btnEdit.setEnabled(hasTags || hasMemberships); 660 btnDel.setEnabled(hasTags || hasMemberships); 661 tagTable.setVisible(hasTags); 662 tagTable.getTableHeader().setVisible(hasTags); 663 selectSth.setVisible(!hasSelection); 664 pluginHook.setVisible(hasSelection); 665 666 int selectedIndex; 667 if (selectedTag != null && (selectedIndex = findRow(tagData, selectedTag)) != -1) { 668 tagTable.changeSelection(selectedIndex, 0, false, false); 669 } else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) { 670 membershipTable.changeSelection(selectedIndex, 0, false, false); 671 } else if(hasTags) { 672 tagTable.changeSelection(0, 0, false, false); 673 } else if(hasMemberships) { 674 membershipTable.changeSelection(0, 0, false, false); 675 } 676 677 if(tagData.getRowCount() != 0 || membershipData.getRowCount() != 0) { 678 setTitle(tr("Tags: {0} / Memberships: {1}", 679 tagData.getRowCount(), membershipData.getRowCount())); 680 } else { 681 setTitle(tr("Tags / Memberships")); 682 } 683 } 684 685 /* ---------------------------------------------------------------------------------- */ 686 /* EditLayerChangeListener */ 687 /* ---------------------------------------------------------------------------------- */ 688 @Override 689 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 690 if (newLayer == null) editHelper.saveTagsIfNeeded(); 691 // it is time to save history of tags 692 GuiHelper.runInEDT(new Runnable() { 693 @Override public void run() { 694 updateSelection(); 695 } 696 }); 697 } 698 699 @Override 700 public void processDatasetEvent(AbstractDatasetChangedEvent event) { 701 updateSelection(); 702 } 703 704 // </editor-fold> 705 706 // <editor-fold defaultstate="collapsed" desc="Methods that are called by plugins to extend fuctionality "> 707 708 /** 709 * Replies the tag popup menu handler. 710 * @return The tag popup menu handler 711 */ 712 public PopupMenuHandler getPropertyPopupMenuHandler() { 713 return tagMenuHandler; 714 } 715 716 @SuppressWarnings("unchecked") 717 public Tag getSelectedProperty() { 718 int row = tagTable.getSelectedRow(); 719 if (row == -1) return null; 720 TreeMap<String, Integer> map = (TreeMap<String, Integer>) tagData.getValueAt(row, 1); 721 return new Tag( 722 tagData.getValueAt(row, 0).toString(), 723 map.size() > 1 ? "" : map.keySet().iterator().next()); 724 } 725 726 /** 727 * Replies the membership popup menu handler. 728 * @return The membership popup menu handler 729 */ 730 public PopupMenuHandler getMembershipPopupMenuHandler() { 731 return membershipMenuHandler; 732 } 733 734 public IRelation getSelectedMembershipRelation() { 735 int row = membershipTable.getSelectedRow(); 736 return row > -1 ? (IRelation) membershipData.getValueAt(row, 0) : null; 737 } 738 739 // </editor-fold> 740 741 /** 742 * Class that watches for mouse clicks 743 * @author imi 744 */ 745 public class MouseClickWatch extends MouseAdapter { 746 @Override public void mouseClicked(MouseEvent e) { 747 if (e.getClickCount() < 2) 748 { 749 // single click, clear selection in other table not clicked in 750 if (e.getSource() == tagTable) { 751 membershipTable.clearSelection(); 752 } else if (e.getSource() == membershipTable) { 753 tagTable.clearSelection(); 754 } 755 } 756 // double click, edit or add tag 757 else if (e.getSource() == tagTable) 758 { 759 int row = tagTable.rowAtPoint(e.getPoint()); 760 if (row > -1) { 761 boolean focusOnKey = (tagTable.columnAtPoint(e.getPoint()) == 0); 762 editHelper.editTag(row, focusOnKey); 763 } else { 764 editHelper.addTag(); 765 btnAdd.requestFocusInWindow(); 766 } 767 } else if (e.getSource() == membershipTable) { 768 int row = membershipTable.rowAtPoint(e.getPoint()); 769 if (row > -1) { 770 editMembership(row); 771 } 772 } 773 else 774 { 775 editHelper.addTag(); 776 btnAdd.requestFocusInWindow(); 777 } 778 } 779 @Override public void mousePressed(MouseEvent e) { 780 if (e.getSource() == tagTable) { 781 membershipTable.clearSelection(); 782 } else if (e.getSource() == membershipTable) { 783 tagTable.clearSelection(); 784 } 785 } 786 787 } 788 789 static class MemberInfo { 790 List<RelationMember> role = new ArrayList<RelationMember>(); 791 List<Integer> position = new ArrayList<Integer>(); 792 private String positionString = null; 793 void add(RelationMember r, Integer p) { 794 role.add(r); 795 position.add(p); 796 } 797 String getPositionString() { 798 if (positionString == null) { 799 Collections.sort(position); 800 positionString = String.valueOf(position.get(0)); 801 int cnt = 0; 802 int last = position.get(0); 803 for (int i = 1; i < position.size(); ++i) { 804 int cur = position.get(i); 805 if (cur == last + 1) { 806 ++cnt; 807 } else if (cnt == 0) { 808 positionString += "," + cur; 809 } else { 810 positionString += "-" + last; 811 positionString += "," + cur; 812 cnt = 0; 813 } 814 last = cur; 815 } 816 if (cnt >= 1) { 817 positionString += "-" + last; 818 } 819 } 820 if (positionString.length() > 20) { 821 positionString = positionString.substring(0, 17) + "..."; 822 } 823 return positionString; 824 } 825 } 826 827 /** 828 * Class that allows fast creation of read-only table model with String columns 829 */ 830 public static class ReadOnlyTableModel extends DefaultTableModel { 831 @Override public boolean isCellEditable(int row, int column) { 832 return false; 833 } 834 @Override public Class<?> getColumnClass(int columnIndex) { 835 return String.class; 836 } 837 } 838 839 /** 840 * Action handling delete button press in properties dialog. 841 */ 842 class DeleteAction extends JosmAction implements ListSelectionListener { 843 844 public DeleteAction() { 845 super(tr("Delete"), "dialogs/delete", tr("Delete the selected key in all objects"), 846 Shortcut.registerShortcut("properties:delete", tr("Delete Tags"), KeyEvent.VK_D, 847 Shortcut.ALT_CTRL_SHIFT), false); 848 updateEnabledState(); 849 } 850 851 protected void deleteTags(int[] rows){ 852 // convert list of rows to HashMap (and find gap for nextKey) 853 HashMap<String, String> tags = new HashMap<String, String>(rows.length); 854 int nextKeyIndex = rows[0]; 855 for (int row : rows) { 856 String key = tagData.getValueAt(row, 0).toString(); 857 if (row == nextKeyIndex + 1) { 858 nextKeyIndex = row; // no gap yet 859 } 860 tags.put(key, null); 861 } 862 863 // find key to select after deleting other tags 864 String nextKey = null; 865 int rowCount = tagData.getRowCount(); 866 if (rowCount > rows.length) { 867 if (nextKeyIndex == rows[rows.length-1]) { 868 // no gap found, pick next or previous key in list 869 nextKeyIndex = (nextKeyIndex + 1 < rowCount ? nextKeyIndex + 1 : rows[0] - 1); 870 } else { 871 // gap found 872 nextKeyIndex++; 873 } 874 nextKey = (String)tagData.getValueAt(nextKeyIndex, 0); 875 } 876 877 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 878 Main.main.undoRedo.add(new ChangePropertyCommand(sel, tags)); 879 880 membershipTable.clearSelection(); 881 if (nextKey != null) { 882 tagTable.changeSelection(findRow(tagData, nextKey), 0, false, false); 883 } 884 } 885 886 protected void deleteFromRelation(int row) { 887 Relation cur = (Relation)membershipData.getValueAt(row, 0); 888 889 Relation nextRelation = null; 890 int rowCount = membershipTable.getRowCount(); 891 if (rowCount > 1) { 892 nextRelation = (Relation)membershipData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0); 893 } 894 895 ExtendedDialog ed = new ExtendedDialog(Main.parent, 896 tr("Change relation"), 897 new String[] {tr("Delete from relation"), tr("Cancel")}); 898 ed.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"}); 899 ed.setContent(tr("Really delete selection from relation {0}?", cur.getDisplayName(DefaultNameFormatter.getInstance()))); 900 ed.toggleEnable("delete_from_relation"); 901 ed.showDialog(); 902 903 if(ed.getValue() != 1) 904 return; 905 906 Relation rel = new Relation(cur); 907 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 908 for (OsmPrimitive primitive: sel) { 909 rel.removeMembersFor(primitive); 910 } 911 Main.main.undoRedo.add(new ChangeCommand(cur, rel)); 912 913 tagTable.clearSelection(); 914 if (nextRelation != null) { 915 membershipTable.changeSelection(findRow(membershipData, nextRelation), 0, false, false); 916 } 917 } 918 919 @Override 920 public void actionPerformed(ActionEvent e) { 921 if (tagTable.getSelectedRowCount() > 0) { 922 int[] rows = tagTable.getSelectedRows(); 923 deleteTags(rows); 924 } else if (membershipTable.getSelectedRowCount() > 0) { 925 int[] rows = membershipTable.getSelectedRows(); 926 // delete from last relation to convserve row numbers in the table 927 for (int i=rows.length-1; i>=0; i--) { 928 deleteFromRelation(rows[i]); 929 } 930 } 931 } 932 933 @Override 934 protected void updateEnabledState() { 935 setEnabled( 936 (tagTable != null && tagTable.getSelectedRowCount() >= 1) 937 || (membershipTable != null && membershipTable.getSelectedRowCount() > 0) 938 ); 939 } 940 941 @Override 942 public void valueChanged(ListSelectionEvent e) { 943 updateEnabledState(); 944 } 945 } 946 947 /** 948 * Action handling add button press in properties dialog. 949 */ 950 class AddAction extends JosmAction { 951 public AddAction() { 952 super(tr("Add"), "dialogs/add", tr("Add a new key/value pair to all objects"), 953 Shortcut.registerShortcut("properties:add", tr("Add Tag"), KeyEvent.VK_A, 954 Shortcut.ALT), false); 955 } 956 957 @Override 958 public void actionPerformed(ActionEvent e) { 959 editHelper.addTag(); 960 btnAdd.requestFocusInWindow(); 961 } 962 } 963 964 /** 965 * Action handling edit button press in properties dialog. 966 */ 967 class EditAction extends JosmAction implements ListSelectionListener { 968 public EditAction() { 969 super(tr("Edit"), "dialogs/edit", tr("Edit the value of the selected key for all objects"), 970 Shortcut.registerShortcut("properties:edit", tr("Edit Tags"), KeyEvent.VK_S, 971 Shortcut.ALT), false); 972 updateEnabledState(); 973 } 974 975 @Override 976 public void actionPerformed(ActionEvent e) { 977 if (!isEnabled()) 978 return; 979 if (tagTable.getSelectedRowCount() == 1) { 980 int row = tagTable.getSelectedRow(); 981 editHelper.editTag(row, false); 982 } else if (membershipTable.getSelectedRowCount() == 1) { 983 int row = membershipTable.getSelectedRow(); 984 editMembership(row); 985 } 986 } 987 988 @Override 989 protected void updateEnabledState() { 990 setEnabled( 991 (tagTable != null && tagTable.getSelectedRowCount() == 1) 992 ^ (membershipTable != null && membershipTable.getSelectedRowCount() == 1) 993 ); 994 } 995 996 @Override 997 public void valueChanged(ListSelectionEvent e) { 998 updateEnabledState(); 999 } 1000 } 1001 1002 class HelpAction extends AbstractAction { 1003 public HelpAction() { 1004 putValue(NAME, tr("Go to OSM wiki for tag help (F1)")); 1005 putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help for selected object")); 1006 putValue(SMALL_ICON, ImageProvider.get("dialogs", "search")); 1007 } 1008 1009 @Override 1010 public void actionPerformed(ActionEvent e) { 1011 try { 1012 String base = Main.pref.get("url.openstreetmap-wiki", "http://wiki.openstreetmap.org/wiki/"); 1013 String lang = LanguageInfo.getWikiLanguagePrefix(); 1014 final List<URI> uris = new ArrayList<URI>(); 1015 int row; 1016 if (tagTable.getSelectedRowCount() == 1) { 1017 row = tagTable.getSelectedRow(); 1018 String key = URLEncoder.encode(tagData.getValueAt(row, 0).toString(), "UTF-8"); 1019 @SuppressWarnings("unchecked") 1020 Map<String, Integer> m = (Map<String, Integer>) tagData.getValueAt(row, 1); 1021 String val = URLEncoder.encode(m.entrySet().iterator().next().getKey(), "UTF-8"); 1022 1023 uris.add(new URI(String.format("%s%sTag:%s=%s", base, lang, key, val))); 1024 uris.add(new URI(String.format("%sTag:%s=%s", base, key, val))); 1025 uris.add(new URI(String.format("%s%sKey:%s", base, lang, key))); 1026 uris.add(new URI(String.format("%sKey:%s", base, key))); 1027 uris.add(new URI(String.format("%s%sMap_Features", base, lang))); 1028 uris.add(new URI(String.format("%sMap_Features", base))); 1029 } else if (membershipTable.getSelectedRowCount() == 1) { 1030 row = membershipTable.getSelectedRow(); 1031 String type = URLEncoder.encode( 1032 ((Relation)membershipData.getValueAt(row, 0)).get("type"), "UTF-8" 1033 ); 1034 1035 if (type != null && !type.isEmpty()) { 1036 uris.add(new URI(String.format("%s%sRelation:%s", base, lang, type))); 1037 uris.add(new URI(String.format("%sRelation:%s", base, type))); 1038 } 1039 1040 uris.add(new URI(String.format("%s%sRelations", base, lang))); 1041 uris.add(new URI(String.format("%sRelations", base))); 1042 } else { 1043 // give the generic help page, if more than one element is selected 1044 uris.add(new URI(String.format("%s%sMap_Features", base, lang))); 1045 uris.add(new URI(String.format("%sMap_Features", base))); 1046 } 1047 1048 Main.worker.execute(new Runnable(){ 1049 @Override public void run() { 1050 try { 1051 // find a page that actually exists in the wiki 1052 HttpURLConnection conn; 1053 for (URI u : uris) { 1054 conn = Utils.openHttpConnection(u.toURL()); 1055 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000); 1056 1057 if (conn.getResponseCode() != 200) { 1058 Main.info("{0} does not exist", u); 1059 conn.disconnect(); 1060 } else { 1061 int osize = conn.getContentLength(); 1062 if (osize > -1) { 1063 conn.disconnect(); 1064 1065 conn = Utils.openHttpConnection(new URI(u.toString() 1066 .replace("=", "%3D") /* do not URLencode whole string! */ 1067 .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=") 1068 ).toURL()); 1069 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000); 1070 } 1071 1072 /* redirect pages have different content length, but retrieving a "nonredirect" 1073 * page using index.php and the direct-link method gives slightly different 1074 * content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better) 1075 */ 1076 if (conn.getContentLength() != -1 && osize > -1 && Math.abs(conn.getContentLength() - osize) > 200) { 1077 Main.info("{0} is a mediawiki redirect", u); 1078 conn.disconnect(); 1079 } else { 1080 Main.info("browsing to {0}", u); 1081 conn.disconnect(); 1082 1083 OpenBrowser.displayUrl(u.toString()); 1084 break; 1085 } 1086 } 1087 } 1088 } catch (Exception e) { 1089 e.printStackTrace(); 1090 } 1091 } 1092 }); 1093 } catch (Exception e1) { 1094 e1.printStackTrace(); 1095 } 1096 } 1097 } 1098 1099 class PasteValueAction extends AbstractAction { 1100 public PasteValueAction() { 1101 putValue(NAME, tr("Paste Value")); 1102 putValue(SHORT_DESCRIPTION, tr("Paste the value of the selected tag from clipboard")); 1103 } 1104 1105 @Override 1106 public void actionPerformed(ActionEvent ae) { 1107 if (tagTable.getSelectedRowCount() != 1) 1108 return; 1109 String key = tagData.getValueAt(tagTable.getSelectedRow(), 0).toString(); 1110 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 1111 String clipboard = Utils.getClipboardContent(); 1112 if (sel.isEmpty() || clipboard == null) 1113 return; 1114 Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, Utils.strip(clipboard))); 1115 } 1116 } 1117 1118 abstract class AbstractCopyAction extends AbstractAction { 1119 1120 protected abstract Collection<String> getString(OsmPrimitive p, String key); 1121 1122 @Override 1123 public void actionPerformed(ActionEvent ae) { 1124 int[] rows = tagTable.getSelectedRows(); 1125 Set<String> values = new TreeSet<String>(); 1126 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 1127 if (rows.length == 0 || sel.isEmpty()) return; 1128 1129 for (int row: rows) { 1130 String key = tagData.getValueAt(row, 0).toString(); 1131 if (sel.isEmpty()) 1132 return; 1133 for (OsmPrimitive p : sel) { 1134 Collection<String> s = getString(p,key); 1135 if (s != null) { 1136 values.addAll(s); 1137 } 1138 } 1139 } 1140 if (!values.isEmpty()) { 1141 Utils.copyToClipboard(Utils.join("\n", values)); 1142 } 1143 } 1144 } 1145 1146 class CopyValueAction extends AbstractCopyAction { 1147 1148 public CopyValueAction() { 1149 putValue(NAME, tr("Copy Value")); 1150 putValue(SHORT_DESCRIPTION, tr("Copy the value of the selected tag to clipboard")); 1151 } 1152 1153 @Override 1154 protected Collection<String> getString(OsmPrimitive p, String key) { 1155 String v = p.get(key); 1156 return v == null ? null : Collections.singleton(v); 1157 } 1158 } 1159 1160 class CopyKeyValueAction extends AbstractCopyAction { 1161 1162 public CopyKeyValueAction() { 1163 putValue(NAME, tr("Copy Key/Value")); 1164 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the selected tag to clipboard")); 1165 } 1166 1167 @Override 1168 protected Collection<String> getString(OsmPrimitive p, String key) { 1169 String v = p.get(key); 1170 return v == null ? null : Collections.singleton(new Tag(key, v).toString()); 1171 } 1172 } 1173 1174 class CopyAllKeyValueAction extends AbstractCopyAction { 1175 1176 public CopyAllKeyValueAction() { 1177 putValue(NAME, tr("Copy all Keys/Values")); 1178 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the all tags to clipboard")); 1179 } 1180 1181 @Override 1182 protected Collection<String> getString(OsmPrimitive p, String key) { 1183 List<String> r = new LinkedList<String>(); 1184 for (Entry<String, String> kv : p.getKeys().entrySet()) { 1185 r.add(new Tag(kv.getKey(), kv.getValue()).toString()); 1186 } 1187 return r; 1188 } 1189 } 1190 1191 class SearchAction extends AbstractAction { 1192 final boolean sameType; 1193 1194 public SearchAction(boolean sameType) { 1195 this.sameType = sameType; 1196 if (sameType) { 1197 putValue(NAME, tr("Search Key/Value/Type")); 1198 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag, restrict to type (i.e., node/way/relation)")); 1199 } else { 1200 putValue(NAME, tr("Search Key/Value")); 1201 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag")); 1202 } 1203 } 1204 1205 @Override 1206 public void actionPerformed(ActionEvent e) { 1207 if (tagTable.getSelectedRowCount() != 1) 1208 return; 1209 String key = tagData.getValueAt(tagTable.getSelectedRow(), 0).toString(); 1210 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 1211 if (sel.isEmpty()) 1212 return; 1213 String sep = ""; 1214 StringBuilder s = new StringBuilder(); 1215 for (OsmPrimitive p : sel) { 1216 String val = p.get(key); 1217 if (val == null) { 1218 continue; 1219 } 1220 String t = ""; 1221 if (!sameType) { 1222 t = ""; 1223 } else if (p instanceof Node) { 1224 t = "type:node "; 1225 } else if (p instanceof Way) { 1226 t = "type:way "; 1227 } else if (p instanceof Relation) { 1228 t = "type:relation "; 1229 } 1230 s.append(sep).append("(").append(t).append("\"").append( 1231 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(key)).append("\"=\"").append( 1232 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(val)).append("\")"); 1233 sep = " OR "; 1234 } 1235 1236 SearchSetting ss = new SearchSetting(s.toString(), SearchMode.replace, true, false, false); 1237 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(ss); 1238 } 1239 } 1240 1241 @Override 1242 public void preferenceChanged(PreferenceChangeEvent e) { 1243 if ("display.discardable-keys".equals(e.getKey()) && Main.main.getCurrentDataSet() != null) { 1244 // Re-load data when display preference change 1245 selectionChanged(Main.main.getCurrentDataSet().getSelected()); 1246 } 1247 } 1248}