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