source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java@ 8985

Last change on this file since 8985 was 8985, checked in by Don-vip, 8 years ago

checkstyle

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