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

Last change on this file since 5793 was 5793, checked in by akks, 11 years ago

Big refactoring of relation context menu actions, removing duplicate and similar code. Any behavior change is a bug!
see #7846: Harmonize Relation popup menus and actions,
Please check relation-actions in Selection List, Relation List and Properties dialogs.

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