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