source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java@ 8513

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

checkstyle: blocks

  • Property svn:eol-style set to native
File size: 71.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.BorderLayout;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.FocusAdapter;
15import java.awt.event.FocusEvent;
16import java.awt.event.InputEvent;
17import java.awt.event.KeyEvent;
18import java.awt.event.MouseAdapter;
19import java.awt.event.MouseEvent;
20import java.awt.event.WindowAdapter;
21import java.awt.event.WindowEvent;
22import java.beans.PropertyChangeEvent;
23import java.beans.PropertyChangeListener;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.EnumSet;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Set;
31
32import javax.swing.AbstractAction;
33import javax.swing.BorderFactory;
34import javax.swing.InputMap;
35import javax.swing.JButton;
36import javax.swing.JComponent;
37import javax.swing.JLabel;
38import javax.swing.JMenu;
39import javax.swing.JMenuItem;
40import javax.swing.JOptionPane;
41import javax.swing.JPanel;
42import javax.swing.JScrollPane;
43import javax.swing.JSplitPane;
44import javax.swing.JTabbedPane;
45import javax.swing.JToolBar;
46import javax.swing.KeyStroke;
47import javax.swing.SwingUtilities;
48import javax.swing.event.ChangeEvent;
49import javax.swing.event.ChangeListener;
50import javax.swing.event.DocumentEvent;
51import javax.swing.event.DocumentListener;
52import javax.swing.event.ListSelectionEvent;
53import javax.swing.event.ListSelectionListener;
54import javax.swing.event.TableModelEvent;
55import javax.swing.event.TableModelListener;
56
57import org.openstreetmap.josm.Main;
58import org.openstreetmap.josm.actions.CopyAction;
59import org.openstreetmap.josm.actions.ExpertToggleAction;
60import org.openstreetmap.josm.actions.JosmAction;
61import org.openstreetmap.josm.command.AddCommand;
62import org.openstreetmap.josm.command.ChangeCommand;
63import org.openstreetmap.josm.command.Command;
64import org.openstreetmap.josm.command.conflict.ConflictAddCommand;
65import org.openstreetmap.josm.data.conflict.Conflict;
66import org.openstreetmap.josm.data.osm.DataSet;
67import org.openstreetmap.josm.data.osm.OsmPrimitive;
68import org.openstreetmap.josm.data.osm.PrimitiveData;
69import org.openstreetmap.josm.data.osm.Relation;
70import org.openstreetmap.josm.data.osm.RelationMember;
71import org.openstreetmap.josm.data.osm.Tag;
72import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
73import org.openstreetmap.josm.gui.DefaultNameFormatter;
74import org.openstreetmap.josm.gui.HelpAwareOptionPane;
75import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
76import org.openstreetmap.josm.gui.MainMenu;
77import org.openstreetmap.josm.gui.SideButton;
78import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
79import org.openstreetmap.josm.gui.help.HelpUtil;
80import org.openstreetmap.josm.gui.layer.OsmDataLayer;
81import org.openstreetmap.josm.gui.tagging.PresetHandler;
82import org.openstreetmap.josm.gui.tagging.TagEditorModel;
83import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
84import org.openstreetmap.josm.gui.tagging.TaggingPreset;
85import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
86import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
87import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
88import org.openstreetmap.josm.io.OnlineResource;
89import org.openstreetmap.josm.tools.CheckParameterUtil;
90import org.openstreetmap.josm.tools.ImageProvider;
91import org.openstreetmap.josm.tools.Shortcut;
92import org.openstreetmap.josm.tools.WindowGeometry;
93
94/**
95 * This dialog is for editing relations.
96 * @since 343
97 */
98public class GenericRelationEditor extends RelationEditor {
99 /** the tag table and its model */
100 private TagEditorPanel tagEditorPanel;
101 private ReferringRelationsBrowser referrerBrowser;
102 private ReferringRelationsBrowserModel referrerModel;
103
104 /** the member table */
105 private MemberTable memberTable;
106 private MemberTableModel memberTableModel;
107
108 /** the model for the selection table */
109 private SelectionTable selectionTable;
110 private SelectionTableModel selectionTableModel;
111
112 private AutoCompletingTextField tfRole;
113
114 /** the menu item in the windows menu. Required to properly
115 * hide on dialog close.
116 */
117 private JMenuItem windowMenuItem;
118 /**
119 * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.SortBelowAction}.
120 */
121 private JButton sortBelowButton;
122
123 /**
124 * Creates a new relation editor for the given relation. The relation will be saved if the user
125 * selects "ok" in the editor.
126 *
127 * If no relation is given, will create an editor for a new relation.
128 *
129 * @param layer the {@link OsmDataLayer} the new or edited relation belongs to
130 * @param relation relation to edit, or null to create a new one.
131 * @param selectedMembers a collection of members which shall be selected initially
132 */
133 public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
134 super(layer, relation, selectedMembers);
135
136 setRememberWindowGeometry(getClass().getName() + ".geometry",
137 WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650)));
138
139 final PresetHandler presetHandler = new PresetHandler() {
140
141 @Override
142 public void updateTags(List<Tag> tags) {
143 tagEditorPanel.getModel().updateTags(tags);
144 }
145
146 @Override
147 public Collection<OsmPrimitive> getSelection() {
148 Relation relation = new Relation();
149 tagEditorPanel.getModel().applyToPrimitive(relation);
150 return Collections.<OsmPrimitive>singletonList(relation);
151 }
152 };
153
154 // init the various models
155 //
156 memberTableModel = new MemberTableModel(getLayer(), presetHandler);
157 memberTableModel.register();
158 selectionTableModel = new SelectionTableModel(getLayer());
159 selectionTableModel.register();
160 referrerModel = new ReferringRelationsBrowserModel(relation);
161
162 tagEditorPanel = new TagEditorPanel(presetHandler);
163
164 // populate the models
165 //
166 if (relation != null) {
167 tagEditorPanel.getModel().initFromPrimitive(relation);
168 this.memberTableModel.populate(relation);
169 if (!getLayer().data.getRelations().contains(relation)) {
170 // treat it as a new relation if it doesn't exist in the
171 // data set yet.
172 setRelation(null);
173 }
174 } else {
175 tagEditorPanel.getModel().clear();
176 this.memberTableModel.populate(null);
177 }
178 tagEditorPanel.getModel().ensureOneTag();
179
180 JSplitPane pane = buildSplitPane(relation);
181 pane.setPreferredSize(new Dimension(100, 100));
182
183 JPanel pnl = new JPanel();
184 pnl.setLayout(new BorderLayout());
185 pnl.add(pane, BorderLayout.CENTER);
186 pnl.setBorder(BorderFactory.createRaisedBevelBorder());
187
188 getContentPane().setLayout(new BorderLayout());
189 JTabbedPane tabbedPane = new JTabbedPane();
190 tabbedPane.add(tr("Tags and Members"), pnl);
191 referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel);
192 tabbedPane.add(tr("Parent Relations"), referrerBrowser);
193 tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation));
194 tabbedPane.addChangeListener(
195 new ChangeListener() {
196 @Override
197 public void stateChanged(ChangeEvent e) {
198 JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource();
199 int index = sourceTabbedPane.getSelectedIndex();
200 String title = sourceTabbedPane.getTitleAt(index);
201 if (title.equals(tr("Parent Relations"))) {
202 referrerBrowser.init();
203 }
204 }
205 }
206 );
207
208 getContentPane().add(buildToolBar(), BorderLayout.NORTH);
209 getContentPane().add(tabbedPane, BorderLayout.CENTER);
210 getContentPane().add(buildOkCancelButtonPanel(), BorderLayout.SOUTH);
211
212 setSize(findMaxDialogSize());
213
214 addWindowListener(
215 new WindowAdapter() {
216 @Override
217 public void windowOpened(WindowEvent e) {
218 cleanSelfReferences();
219 }
220 }
221 );
222 registerCopyPasteAction(tagEditorPanel.getPasteAction(),
223 "PASTE_TAGS",
224 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke());
225 registerCopyPasteAction(new PasteMembersAction(), "PASTE_MEMBERS", Shortcut.getPasteKeyStroke());
226 registerCopyPasteAction(new CopyMembersAction(), "COPY_MEMBERS", Shortcut.getCopyKeyStroke());
227
228 tagEditorPanel.setNextFocusComponent(memberTable);
229 selectionTable.setFocusable(false);
230 memberTableModel.setSelectedMembers(selectedMembers);
231 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/RelationEditor"));
232 }
233
234 /**
235 * Creates the toolbar
236 *
237 * @return the toolbar
238 */
239 protected JToolBar buildToolBar() {
240 JToolBar tb = new JToolBar();
241 tb.setFloatable(false);
242 tb.add(new ApplyAction());
243 tb.add(new DuplicateRelationAction());
244 DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction();
245 addPropertyChangeListener(deleteAction);
246 tb.add(deleteAction);
247 return tb;
248 }
249
250 /**
251 * builds the panel with the OK and the Cancel button
252 *
253 * @return the panel with the OK and the Cancel button
254 */
255 protected JPanel buildOkCancelButtonPanel() {
256 JPanel pnl = new JPanel();
257 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
258
259 pnl.add(new SideButton(new OKAction()));
260 pnl.add(new SideButton(new CancelAction()));
261 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
262 return pnl;
263 }
264
265 /**
266 * builds the panel with the tag editor
267 *
268 * @return the panel with the tag editor
269 */
270 protected JPanel buildTagEditorPanel() {
271 JPanel pnl = new JPanel();
272 pnl.setLayout(new GridBagLayout());
273
274 GridBagConstraints gc = new GridBagConstraints();
275 gc.gridx = 0;
276 gc.gridy = 0;
277 gc.gridheight = 1;
278 gc.gridwidth = 1;
279 gc.fill = GridBagConstraints.HORIZONTAL;
280 gc.anchor = GridBagConstraints.FIRST_LINE_START;
281 gc.weightx = 1.0;
282 gc.weighty = 0.0;
283 pnl.add(new JLabel(tr("Tags")), gc);
284
285 gc.gridx = 0;
286 gc.gridy = 1;
287 gc.fill = GridBagConstraints.BOTH;
288 gc.anchor = GridBagConstraints.CENTER;
289 gc.weightx = 1.0;
290 gc.weighty = 1.0;
291 pnl.add(tagEditorPanel, gc);
292 return pnl;
293 }
294
295 /**
296 * builds the panel for the relation member editor
297 *
298 * @return the panel for the relation member editor
299 */
300 protected JPanel buildMemberEditorPanel() {
301 final JPanel pnl = new JPanel(new GridBagLayout());
302 // setting up the member table
303 memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
304 memberTable.addMouseListener(new MemberTableDblClickAdapter());
305 memberTableModel.addMemberModelListener(memberTable);
306
307 final JScrollPane scrollPane = new JScrollPane(memberTable);
308
309 GridBagConstraints gc = new GridBagConstraints();
310 gc.gridx = 0;
311 gc.gridy = 0;
312 gc.gridwidth = 2;
313 gc.fill = GridBagConstraints.HORIZONTAL;
314 gc.anchor = GridBagConstraints.FIRST_LINE_START;
315 gc.weightx = 1.0;
316 gc.weighty = 0.0;
317 pnl.add(new JLabel(tr("Members")), gc);
318
319 gc.gridx = 0;
320 gc.gridy = 1;
321 gc.gridheight = 2;
322 gc.gridwidth = 1;
323 gc.fill = GridBagConstraints.VERTICAL;
324 gc.anchor = GridBagConstraints.NORTHWEST;
325 gc.weightx = 0.0;
326 gc.weighty = 1.0;
327 pnl.add(buildLeftButtonPanel(), gc);
328
329 gc.gridx = 1;
330 gc.gridy = 1;
331 gc.gridheight = 1;
332 gc.fill = GridBagConstraints.BOTH;
333 gc.anchor = GridBagConstraints.CENTER;
334 gc.weightx = 0.6;
335 gc.weighty = 1.0;
336 pnl.add(scrollPane, gc);
337
338 // --- role editing
339 JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
340 p3.add(new JLabel(tr("Apply Role:")));
341 tfRole = new AutoCompletingTextField(10);
342 tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
343 tfRole.addFocusListener(new FocusAdapter() {
344 @Override
345 public void focusGained(FocusEvent e) {
346 tfRole.selectAll();
347 }
348 });
349 tfRole.setAutoCompletionList(new AutoCompletionList());
350 tfRole.addFocusListener(
351 new FocusAdapter() {
352 @Override
353 public void focusGained(FocusEvent e) {
354 AutoCompletionList list = tfRole.getAutoCompletionList();
355 if (list != null) {
356 list.clear();
357 getLayer().data.getAutoCompletionManager().populateWithMemberRoles(list, getRelation());
358 }
359 }
360 }
361 );
362 tfRole.setText(Main.pref.get("relation.editor.generic.lastrole", ""));
363 p3.add(tfRole);
364 SetRoleAction setRoleAction = new SetRoleAction();
365 memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
366 tfRole.getDocument().addDocumentListener(setRoleAction);
367 tfRole.addActionListener(setRoleAction);
368 memberTableModel.getSelectionModel().addListSelectionListener(
369 new ListSelectionListener() {
370 @Override
371 public void valueChanged(ListSelectionEvent e) {
372 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0);
373 }
374 }
375 );
376 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0);
377 SideButton btnApply = new SideButton(setRoleAction);
378 btnApply.setPreferredSize(new Dimension(20, 20));
379 btnApply.setText("");
380 p3.add(btnApply);
381
382 gc.gridx = 1;
383 gc.gridy = 2;
384 gc.fill = GridBagConstraints.HORIZONTAL;
385 gc.anchor = GridBagConstraints.LAST_LINE_START;
386 gc.weightx = 1.0;
387 gc.weighty = 0.0;
388 pnl.add(p3, gc);
389
390 JPanel pnl2 = new JPanel();
391 pnl2.setLayout(new GridBagLayout());
392
393 gc.gridx = 0;
394 gc.gridy = 0;
395 gc.gridheight = 1;
396 gc.gridwidth = 3;
397 gc.fill = GridBagConstraints.HORIZONTAL;
398 gc.anchor = GridBagConstraints.FIRST_LINE_START;
399 gc.weightx = 1.0;
400 gc.weighty = 0.0;
401 pnl2.add(new JLabel(tr("Selection")), gc);
402
403 gc.gridx = 0;
404 gc.gridy = 1;
405 gc.gridheight = 1;
406 gc.gridwidth = 1;
407 gc.fill = GridBagConstraints.VERTICAL;
408 gc.anchor = GridBagConstraints.NORTHWEST;
409 gc.weightx = 0.0;
410 gc.weighty = 1.0;
411 pnl2.add(buildSelectionControlButtonPanel(), gc);
412
413 gc.gridx = 1;
414 gc.gridy = 1;
415 gc.weightx = 1.0;
416 gc.weighty = 1.0;
417 gc.fill = GridBagConstraints.BOTH;
418 pnl2.add(buildSelectionTablePanel(), gc);
419
420 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
421 splitPane.setLeftComponent(pnl);
422 splitPane.setRightComponent(pnl2);
423 splitPane.setOneTouchExpandable(false);
424 addWindowListener(new WindowAdapter() {
425 @Override
426 public void windowOpened(WindowEvent e) {
427 // has to be called when the window is visible, otherwise
428 // no effect
429 splitPane.setDividerLocation(0.6);
430 }
431 });
432
433 JPanel pnl3 = new JPanel();
434 pnl3.setLayout(new BorderLayout());
435 pnl3.add(splitPane, BorderLayout.CENTER);
436
437 return pnl3;
438 }
439
440 /**
441 * builds the panel with the table displaying the currently selected primitives
442 *
443 * @return panel with current selection
444 */
445 protected JPanel buildSelectionTablePanel() {
446 JPanel pnl = new JPanel(new BorderLayout());
447 MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
448 selectionTable = new SelectionTable(selectionTableModel, new SelectionTableColumnModel(memberTableModel));
449 selectionTable.setMemberTableModel(memberTableModel);
450 selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
451 pnl.add(new JScrollPane(selectionTable), BorderLayout.CENTER);
452 return pnl;
453 }
454
455 /**
456 * builds the {@link JSplitPane} which divides the editor in an upper and a lower half
457 *
458 * @return the split panel
459 */
460 protected JSplitPane buildSplitPane(Relation relation) {
461 final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
462 pane.setTopComponent(buildTagEditorPanel());
463 pane.setBottomComponent(buildMemberEditorPanel());
464 pane.setOneTouchExpandable(true);
465 addWindowListener(new WindowAdapter() {
466 @Override
467 public void windowOpened(WindowEvent e) {
468 // has to be called when the window is visible, otherwise
469 // no effect
470 pane.setDividerLocation(0.3);
471 }
472 });
473 return pane;
474 }
475
476 /**
477 * build the panel with the buttons on the left
478 *
479 * @return left button panel
480 */
481 protected JToolBar buildLeftButtonPanel() {
482 JToolBar tb = new JToolBar();
483 tb.setOrientation(JToolBar.VERTICAL);
484 tb.setFloatable(false);
485
486 // -- move up action
487 MoveUpAction moveUpAction = new MoveUpAction();
488 memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction);
489 tb.add(moveUpAction);
490 memberTable.getActionMap().put("moveUp", moveUpAction);
491
492 // -- move down action
493 MoveDownAction moveDownAction = new MoveDownAction();
494 memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction);
495 tb.add(moveDownAction);
496 memberTable.getActionMap().put("moveDown", moveDownAction);
497
498 tb.addSeparator();
499
500 // -- edit action
501 EditAction editAction = new EditAction();
502 memberTableModel.getSelectionModel().addListSelectionListener(editAction);
503 tb.add(editAction);
504
505 // -- delete action
506 RemoveAction removeSelectedAction = new RemoveAction();
507 memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction);
508 tb.add(removeSelectedAction);
509 memberTable.getActionMap().put("removeSelected", removeSelectedAction);
510
511 tb.addSeparator();
512 // -- sort action
513 SortAction sortAction = new SortAction();
514 memberTableModel.addTableModelListener(sortAction);
515 tb.add(sortAction);
516 final SortBelowAction sortBelowAction = new SortBelowAction();
517 memberTableModel.addTableModelListener(sortBelowAction);
518 memberTableModel.getSelectionModel().addListSelectionListener(sortBelowAction);
519 sortBelowButton = tb.add(sortBelowAction);
520
521 // -- reverse action
522 ReverseAction reverseAction = new ReverseAction();
523 memberTableModel.addTableModelListener(reverseAction);
524 tb.add(reverseAction);
525
526 tb.addSeparator();
527
528 // -- download action
529 DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction();
530 memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction);
531 tb.add(downloadIncompleteMembersAction);
532 memberTable.getActionMap().put("downloadIncomplete", downloadIncompleteMembersAction);
533
534 // -- download selected action
535 DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction();
536 memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction);
537 memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction);
538 tb.add(downloadSelectedIncompleteMembersAction);
539
540 InputMap inputMap = memberTable.getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
541 inputMap.put((KeyStroke) removeSelectedAction.getValue(AbstractAction.ACCELERATOR_KEY), "removeSelected");
542 inputMap.put((KeyStroke) moveUpAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveUp");
543 inputMap.put((KeyStroke) moveDownAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveDown");
544 inputMap.put((KeyStroke) downloadIncompleteMembersAction.getValue(AbstractAction.ACCELERATOR_KEY), "downloadIncomplete");
545
546 return tb;
547 }
548
549 /**
550 * build the panel with the buttons for adding or removing the current selection
551 *
552 * @return control buttons panel for selection/members
553 */
554 protected JToolBar buildSelectionControlButtonPanel() {
555 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
556 tb.setFloatable(false);
557
558 // -- add at start action
559 AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction();
560 selectionTableModel.addTableModelListener(addSelectionAction);
561 tb.add(addSelectionAction);
562
563 // -- add before selected action
564 AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection();
565 selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction);
566 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction);
567 tb.add(addSelectedBeforeSelectionAction);
568
569 // -- add after selected action
570 AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection();
571 selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction);
572 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction);
573 tb.add(addSelectedAfterSelectionAction);
574
575 // -- add at end action
576 AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction();
577 selectionTableModel.addTableModelListener(addSelectedAtEndAction);
578 tb.add(addSelectedAtEndAction);
579
580 tb.addSeparator();
581
582 // -- select members action
583 SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction();
584 selectionTableModel.addTableModelListener(selectMembersForSelectionAction);
585 memberTableModel.addTableModelListener(selectMembersForSelectionAction);
586 tb.add(selectMembersForSelectionAction);
587
588 // -- select action
589 SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction();
590 memberTable.getSelectionModel().addListSelectionListener(selectAction);
591 tb.add(selectAction);
592
593 tb.addSeparator();
594
595 // -- remove selected action
596 RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction();
597 selectionTableModel.addTableModelListener(removeSelectedAction);
598 tb.add(removeSelectedAction);
599
600 return tb;
601 }
602
603 @Override
604 protected Dimension findMaxDialogSize() {
605 return new Dimension(700, 650);
606 }
607
608 @Override
609 public void setVisible(boolean visible) {
610 if (visible) {
611 tagEditorPanel.initAutoCompletion(getLayer());
612 }
613 super.setVisible(visible);
614 if (visible) {
615 sortBelowButton.setVisible(ExpertToggleAction.isExpert());
616 RelationDialogManager.getRelationDialogManager().positionOnScreen(this);
617 if (windowMenuItem == null) {
618 addToWindowMenu();
619 }
620 tagEditorPanel.requestFocusInWindow();
621 } else {
622 // make sure all registered listeners are unregistered
623 //
624 memberTable.stopHighlighting();
625 selectionTableModel.unregister();
626 memberTableModel.unregister();
627 memberTable.unlinkAsListener();
628 if (windowMenuItem != null) {
629 Main.main.menu.windowMenu.remove(windowMenuItem);
630 windowMenuItem = null;
631 }
632 dispose();
633 }
634 }
635
636 /** adds current relation editor to the windows menu (in the "volatile" group) o*/
637 protected void addToWindowMenu() {
638 String name = getRelation() == null ? tr("New Relation") : getRelation().getLocalName();
639 final String tt = tr("Focus Relation Editor with relation ''{0}'' in layer ''{1}''",
640 name, getLayer().getName());
641 name = tr("Relation Editor: {0}", name == null ? getRelation().getId() : name);
642 final JMenu wm = Main.main.menu.windowMenu;
643 final JosmAction focusAction = new JosmAction(name, "dialogs/relationlist", tt, null, false, false) {
644 @Override
645 public void actionPerformed(ActionEvent e) {
646 final RelationEditor r = (RelationEditor) getValue("relationEditor");
647 r.setVisible(true);
648 }
649 };
650 focusAction.putValue("relationEditor", this);
651 windowMenuItem = MainMenu.add(wm, focusAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE);
652 }
653
654 /**
655 * checks whether the current relation has members referring to itself. If so,
656 * warns the users and provides an option for removing these members.
657 *
658 */
659 protected void cleanSelfReferences() {
660 List<OsmPrimitive> toCheck = new ArrayList<>();
661 toCheck.add(getRelation());
662 if (memberTableModel.hasMembersReferringTo(toCheck)) {
663 int ret = ConditionalOptionPaneUtil.showOptionDialog(
664 "clean_relation_self_references",
665 Main.parent,
666 tr("<html>There is at least one member in this relation referring<br>"
667 + "to the relation itself.<br>"
668 + "This creates circular dependencies and is discouraged.<br>"
669 + "How do you want to proceed with circular dependencies?</html>"),
670 tr("Warning"),
671 JOptionPane.YES_NO_OPTION,
672 JOptionPane.WARNING_MESSAGE,
673 new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")},
674 tr("Remove them, clean up relation")
675 );
676 switch(ret) {
677 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
678 case JOptionPane.CLOSED_OPTION:
679 case JOptionPane.NO_OPTION:
680 return;
681 case JOptionPane.YES_OPTION:
682 memberTableModel.removeMembersReferringTo(toCheck);
683 break;
684 }
685 }
686 }
687
688 private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut) {
689 int mods = shortcut.getModifiers();
690 int code = shortcut.getKeyCode();
691 if (code != KeyEvent.VK_INSERT && (mods == 0 || mods == InputEvent.SHIFT_DOWN_MASK)) {
692 Main.info(tr("Sorry, shortcut \"{0}\" can not be enabled in Relation editor dialog"), shortcut);
693 return;
694 }
695 getRootPane().getActionMap().put(actionName, action);
696 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
697 // Assign also to JTables because they have their own Copy&Paste implementation (which is disabled in this case but eats key shortcuts anyway)
698 memberTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
699 memberTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
700 memberTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
701 selectionTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
702 selectionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
703 selectionTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
704 }
705
706 static class AddAbortException extends Exception {
707 }
708
709 static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
710 String msg = tr("<html>This relation already has one or more members referring to<br>"
711 + "the object ''{0}''<br>"
712 + "<br>"
713 + "Do you really want to add another relation member?</html>",
714 primitive.getDisplayName(DefaultNameFormatter.getInstance())
715 );
716 int ret = ConditionalOptionPaneUtil.showOptionDialog(
717 "add_primitive_to_relation",
718 Main.parent,
719 msg,
720 tr("Multiple members referring to same object."),
721 JOptionPane.YES_NO_CANCEL_OPTION,
722 JOptionPane.WARNING_MESSAGE,
723 null,
724 null
725 );
726 switch(ret) {
727 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
728 case JOptionPane.YES_OPTION:
729 return true;
730 case JOptionPane.NO_OPTION:
731 case JOptionPane.CLOSED_OPTION:
732 return false;
733 case JOptionPane.CANCEL_OPTION:
734 throw new AddAbortException();
735 }
736 // should not happen
737 return false;
738 }
739
740 static void warnOfCircularReferences(OsmPrimitive primitive) {
741 String msg = tr("<html>You are trying to add a relation to itself.<br>"
742 + "<br>"
743 + "This creates circular references and is therefore discouraged.<br>"
744 + "Skipping relation ''{0}''.</html>",
745 primitive.getDisplayName(DefaultNameFormatter.getInstance()));
746 JOptionPane.showMessageDialog(
747 Main.parent,
748 msg,
749 tr("Warning"),
750 JOptionPane.WARNING_MESSAGE);
751 }
752
753 /**
754 * Adds primitives to a given relation.
755 * @param orig The relation to modify
756 * @param primitivesToAdd The primitives to add as relation members
757 * @return The resulting command
758 * @throws IllegalArgumentException if orig is null
759 */
760 public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) {
761 CheckParameterUtil.ensureParameterNotNull(orig, "orig");
762 try {
763 final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(
764 EnumSet.of(TaggingPresetType.RELATION), orig.getKeys(), false);
765 Relation relation = new Relation(orig);
766 boolean modified = false;
767 for (OsmPrimitive p : primitivesToAdd) {
768 if (p instanceof Relation && orig.equals(p)) {
769 warnOfCircularReferences(p);
770 continue;
771 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p))
772 && !confirmAddingPrimitive(p)) {
773 continue;
774 }
775 final Set<String> roles = findSuggestedRoles(presets, p);
776 relation.addMember(new RelationMember(roles.size() == 1 ? roles.iterator().next() : "", p));
777 modified = true;
778 }
779 return modified ? new ChangeCommand(orig, relation) : null;
780 } catch (AddAbortException ign) {
781 return null;
782 }
783 }
784
785 protected static Set<String> findSuggestedRoles(final Collection<TaggingPreset> presets, OsmPrimitive p) {
786 final Set<String> roles = new HashSet<>();
787 for (TaggingPreset preset : presets) {
788 String role = preset.suggestRoleForOsmPrimitive(p);
789 if (role != null && !role.isEmpty()) {
790 roles.add(role);
791 }
792 }
793 return roles;
794 }
795
796 abstract class AddFromSelectionAction extends AbstractAction {
797 protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
798 return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
799 }
800
801 protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
802 if (primitives == null || primitives.isEmpty())
803 return primitives;
804 List<OsmPrimitive> ret = new ArrayList<>();
805 ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
806 for (OsmPrimitive primitive : primitives) {
807 if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) {
808 warnOfCircularReferences(primitive);
809 continue;
810 }
811 if (isPotentialDuplicate(primitive)) {
812 if (confirmAddingPrimitive(primitive)) {
813 ret.add(primitive);
814 }
815 continue;
816 } else {
817 ret.add(primitive);
818 }
819 }
820 ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
821 return ret;
822 }
823 }
824
825 class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener {
826 public AddSelectedAtStartAction() {
827 putValue(SHORT_DESCRIPTION,
828 tr("Add all objects selected in the current dataset before the first member"));
829 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
830 refreshEnabled();
831 }
832
833 protected void refreshEnabled() {
834 setEnabled(selectionTableModel.getRowCount() > 0);
835 }
836
837 @Override
838 public void actionPerformed(ActionEvent e) {
839 try {
840 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
841 memberTableModel.addMembersAtBeginning(toAdd);
842 } catch (AddAbortException ex) {
843 // do nothing
844 if (Main.isTraceEnabled()) {
845 Main.trace(ex.getMessage());
846 }
847 }
848 }
849
850 @Override
851 public void tableChanged(TableModelEvent e) {
852 refreshEnabled();
853 }
854 }
855
856 class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener {
857 public AddSelectedAtEndAction() {
858 putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
859 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
860 refreshEnabled();
861 }
862
863 protected void refreshEnabled() {
864 setEnabled(selectionTableModel.getRowCount() > 0);
865 }
866
867 @Override
868 public void actionPerformed(ActionEvent e) {
869 try {
870 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
871 memberTableModel.addMembersAtEnd(toAdd);
872 } catch (AddAbortException ex) {
873 // do nothing
874 if (Main.isTraceEnabled()) {
875 Main.trace(ex.getMessage());
876 }
877 }
878 }
879
880 @Override
881 public void tableChanged(TableModelEvent e) {
882 refreshEnabled();
883 }
884 }
885
886 class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
887 /**
888 * Constructs a new {@code AddSelectedBeforeSelection}.
889 */
890 public AddSelectedBeforeSelection() {
891 putValue(SHORT_DESCRIPTION,
892 tr("Add all objects selected in the current dataset before the first selected member"));
893 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
894 refreshEnabled();
895 }
896
897 protected void refreshEnabled() {
898 setEnabled(selectionTableModel.getRowCount() > 0
899 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
900 }
901
902 @Override
903 public void actionPerformed(ActionEvent e) {
904 try {
905 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
906 memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel
907 .getSelectionModel().getMinSelectionIndex());
908 } catch (AddAbortException ex) {
909 // do nothing
910 if (Main.isTraceEnabled()) {
911 Main.trace(ex.getMessage());
912 }
913 }
914 }
915
916 @Override
917 public void tableChanged(TableModelEvent e) {
918 refreshEnabled();
919 }
920
921 @Override
922 public void valueChanged(ListSelectionEvent e) {
923 refreshEnabled();
924 }
925 }
926
927 class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
928 public AddSelectedAfterSelection() {
929 putValue(SHORT_DESCRIPTION,
930 tr("Add all objects selected in the current dataset after the last selected member"));
931 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
932 refreshEnabled();
933 }
934
935 protected void refreshEnabled() {
936 setEnabled(selectionTableModel.getRowCount() > 0
937 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
938 }
939
940 @Override
941 public void actionPerformed(ActionEvent e) {
942 try {
943 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
944 memberTableModel.addMembersAfterIdx(toAdd, memberTableModel
945 .getSelectionModel().getMaxSelectionIndex());
946 } catch (AddAbortException ex) {
947 // do nothing
948 if (Main.isTraceEnabled()) {
949 Main.trace(ex.getMessage());
950 }
951 }
952 }
953
954 @Override
955 public void tableChanged(TableModelEvent e) {
956 refreshEnabled();
957 }
958
959 @Override
960 public void valueChanged(ListSelectionEvent e) {
961 refreshEnabled();
962 }
963 }
964
965 class RemoveSelectedAction extends AbstractAction implements TableModelListener {
966 /**
967 * Constructs a new {@code RemoveSelectedAction}.
968 */
969 public RemoveSelectedAction() {
970 putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects"));
971 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
972 updateEnabledState();
973 }
974
975 protected void updateEnabledState() {
976 DataSet ds = getLayer().data;
977 if (ds == null || ds.getSelected().isEmpty()) {
978 setEnabled(false);
979 return;
980 }
981 // only enable the action if we have members referring to the
982 // selected primitives
983 //
984 setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
985 }
986
987 @Override
988 public void actionPerformed(ActionEvent e) {
989 memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
990 }
991
992 @Override
993 public void tableChanged(TableModelEvent e) {
994 updateEnabledState();
995 }
996 }
997
998 /**
999 * Selects members in the relation editor which refer to primitives in the current
1000 * selection of the context layer.
1001 *
1002 */
1003 class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener {
1004 public SelectedMembersForSelectionAction() {
1005 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
1006 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
1007 updateEnabledState();
1008 }
1009
1010 protected void updateEnabledState() {
1011 boolean enabled = selectionTableModel.getRowCount() > 0
1012 && !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty();
1013
1014 if (enabled) {
1015 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",
1016 memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size()));
1017 } else {
1018 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
1019 }
1020 setEnabled(enabled);
1021 }
1022
1023 @Override
1024 public void actionPerformed(ActionEvent e) {
1025 memberTableModel.selectMembersReferringTo(getLayer().data.getSelected());
1026 }
1027
1028 @Override
1029 public void tableChanged(TableModelEvent e) {
1030 updateEnabledState();
1031 }
1032 }
1033
1034 /**
1035 * Selects primitives in the layer this editor belongs to. The selected primitives are
1036 * equal to the set of primitives the currently selected relation members refer to.
1037 *
1038 */
1039 class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener {
1040 public SelectPrimitivesForSelectedMembersAction() {
1041 putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members"));
1042 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
1043 updateEnabledState();
1044 }
1045
1046 protected void updateEnabledState() {
1047 setEnabled(memberTable.getSelectedRowCount() > 0);
1048 }
1049
1050 @Override
1051 public void actionPerformed(ActionEvent e) {
1052 getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives());
1053 }
1054
1055 @Override
1056 public void valueChanged(ListSelectionEvent e) {
1057 updateEnabledState();
1058 }
1059 }
1060
1061 class SortAction extends AbstractAction implements TableModelListener {
1062 public SortAction() {
1063 String tooltip = tr("Sort the relation members");
1064 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
1065 putValue(NAME, tr("Sort"));
1066 Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"),
1067 KeyEvent.VK_END, Shortcut.ALT);
1068 sc.setAccelerator(this);
1069 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1070 updateEnabledState();
1071 }
1072
1073 @Override
1074 public void actionPerformed(ActionEvent e) {
1075 memberTableModel.sort();
1076 }
1077
1078 protected void updateEnabledState() {
1079 setEnabled(memberTableModel.getRowCount() > 0);
1080 }
1081
1082 @Override
1083 public void tableChanged(TableModelEvent e) {
1084 updateEnabledState();
1085 }
1086 }
1087
1088 class SortBelowAction extends AbstractAction implements TableModelListener, ListSelectionListener {
1089 public SortBelowAction() {
1090 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort_below"));
1091 putValue(NAME, tr("Sort below"));
1092 putValue(SHORT_DESCRIPTION, tr("Sort the selected relation members and all members below"));
1093 updateEnabledState();
1094 }
1095
1096 @Override
1097 public void actionPerformed(ActionEvent e) {
1098 memberTableModel.sortBelow();
1099 }
1100
1101 protected void updateEnabledState() {
1102 setEnabled(memberTableModel.getRowCount() > 0 && !memberTableModel.getSelectionModel().isSelectionEmpty());
1103 }
1104
1105 @Override
1106 public void tableChanged(TableModelEvent e) {
1107 updateEnabledState();
1108 }
1109
1110 @Override
1111 public void valueChanged(ListSelectionEvent e) {
1112 updateEnabledState();
1113 }
1114 }
1115
1116 class ReverseAction extends AbstractAction implements TableModelListener {
1117 public ReverseAction() {
1118 putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
1119 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse"));
1120 putValue(NAME, tr("Reverse"));
1121 // Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"),
1122 // KeyEvent.VK_END, Shortcut.ALT)
1123 updateEnabledState();
1124 }
1125
1126 @Override
1127 public void actionPerformed(ActionEvent e) {
1128 memberTableModel.reverse();
1129 }
1130
1131 protected void updateEnabledState() {
1132 setEnabled(memberTableModel.getRowCount() > 0);
1133 }
1134
1135 @Override
1136 public void tableChanged(TableModelEvent e) {
1137 updateEnabledState();
1138 }
1139 }
1140
1141 class MoveUpAction extends AbstractAction implements ListSelectionListener {
1142 public MoveUpAction() {
1143 String tooltip = tr("Move the currently selected members up");
1144 putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
1145 Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"),
1146 KeyEvent.VK_UP, Shortcut.ALT);
1147 sc.setAccelerator(this);
1148 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1149 setEnabled(false);
1150 }
1151
1152 @Override
1153 public void actionPerformed(ActionEvent e) {
1154 memberTableModel.moveUp(memberTable.getSelectedRows());
1155 }
1156
1157 @Override
1158 public void valueChanged(ListSelectionEvent e) {
1159 setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
1160 }
1161 }
1162
1163 class MoveDownAction extends AbstractAction implements ListSelectionListener {
1164 public MoveDownAction() {
1165 String tooltip = tr("Move the currently selected members down");
1166 putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
1167 Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"),
1168 KeyEvent.VK_DOWN, Shortcut.ALT);
1169 sc.setAccelerator(this);
1170 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1171 setEnabled(false);
1172 }
1173
1174 @Override
1175 public void actionPerformed(ActionEvent e) {
1176 memberTableModel.moveDown(memberTable.getSelectedRows());
1177 }
1178
1179 @Override
1180 public void valueChanged(ListSelectionEvent e) {
1181 setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
1182 }
1183 }
1184
1185 class RemoveAction extends AbstractAction implements ListSelectionListener {
1186 public RemoveAction() {
1187 String tooltip = tr("Remove the currently selected members from this relation");
1188 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1189 putValue(NAME, tr("Remove"));
1190 Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"),
1191 KeyEvent.VK_DELETE, Shortcut.ALT);
1192 sc.setAccelerator(this);
1193 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1194 setEnabled(false);
1195 }
1196
1197 @Override
1198 public void actionPerformed(ActionEvent e) {
1199 memberTableModel.remove(memberTable.getSelectedRows());
1200 }
1201
1202 @Override
1203 public void valueChanged(ListSelectionEvent e) {
1204 setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
1205 }
1206 }
1207
1208 class DeleteCurrentRelationAction extends AbstractAction implements PropertyChangeListener{
1209 public DeleteCurrentRelationAction() {
1210 putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
1211 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1212 putValue(NAME, tr("Delete"));
1213 updateEnabledState();
1214 }
1215
1216 public void run() {
1217 Relation toDelete = getRelation();
1218 if (toDelete == null)
1219 return;
1220 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
1221 getLayer(),
1222 toDelete
1223 );
1224 }
1225
1226 @Override
1227 public void actionPerformed(ActionEvent e) {
1228 run();
1229 }
1230
1231 protected void updateEnabledState() {
1232 setEnabled(getRelationSnapshot() != null);
1233 }
1234
1235 @Override
1236 public void propertyChange(PropertyChangeEvent evt) {
1237 if (evt.getPropertyName().equals(RELATION_SNAPSHOT_PROP)) {
1238 updateEnabledState();
1239 }
1240 }
1241 }
1242
1243 abstract class SavingAction extends AbstractAction {
1244 /**
1245 * apply updates to a new relation
1246 */
1247 protected void applyNewRelation() {
1248 final Relation newRelation = new Relation();
1249 tagEditorPanel.getModel().applyToPrimitive(newRelation);
1250 memberTableModel.applyToRelation(newRelation);
1251 List<RelationMember> newMembers = new ArrayList<>();
1252 for (RelationMember rm: newRelation.getMembers()) {
1253 if (!rm.getMember().isDeleted()) {
1254 newMembers.add(rm);
1255 }
1256 }
1257 if (newRelation.getMembersCount() != newMembers.size()) {
1258 newRelation.setMembers(newMembers);
1259 String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" +
1260 "was open. They have been removed from the relation members list.");
1261 JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE);
1262 }
1263 // If the user wanted to create a new relation, but hasn't added any members or
1264 // tags, don't add an empty relation
1265 if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys())
1266 return;
1267 Main.main.undoRedo.add(new AddCommand(getLayer(), newRelation));
1268
1269 // make sure everybody is notified about the changes
1270 //
1271 getLayer().data.fireSelectionChanged();
1272 GenericRelationEditor.this.setRelation(newRelation);
1273 RelationDialogManager.getRelationDialogManager().updateContext(
1274 getLayer(),
1275 getRelation(),
1276 GenericRelationEditor.this
1277 );
1278 SwingUtilities.invokeLater(new Runnable() {
1279 @Override
1280 public void run() {
1281 // Relation list gets update in EDT so selecting my be postponed to following EDT run
1282 Main.map.relationListDialog.selectRelation(newRelation);
1283 }
1284 });
1285 }
1286
1287 /**
1288 * Apply the updates for an existing relation which has been changed
1289 * outside of the relation editor.
1290 *
1291 */
1292 protected void applyExistingConflictingRelation() {
1293 Relation editedRelation = new Relation(getRelation());
1294 tagEditorPanel.getModel().applyToPrimitive(editedRelation);
1295 memberTableModel.applyToRelation(editedRelation);
1296 Conflict<Relation> conflict = new Conflict<>(getRelation(), editedRelation);
1297 Main.main.undoRedo.add(new ConflictAddCommand(getLayer(), conflict));
1298 }
1299
1300 /**
1301 * Apply the updates for an existing relation which has not been changed
1302 * outside of the relation editor.
1303 *
1304 */
1305 protected void applyExistingNonConflictingRelation() {
1306 Relation editedRelation = new Relation(getRelation());
1307 tagEditorPanel.getModel().applyToPrimitive(editedRelation);
1308 memberTableModel.applyToRelation(editedRelation);
1309 Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation));
1310 getLayer().data.fireSelectionChanged();
1311 // this will refresh the snapshot and update the dialog title
1312 //
1313 setRelation(getRelation());
1314 }
1315
1316 protected boolean confirmClosingBecauseOfDirtyState() {
1317 ButtonSpec[] options = new ButtonSpec[] {
1318 new ButtonSpec(
1319 tr("Yes, create a conflict and close"),
1320 ImageProvider.get("ok"),
1321 tr("Click to create a conflict and close this relation editor") ,
1322 null /* no specific help topic */
1323 ),
1324 new ButtonSpec(
1325 tr("No, continue editing"),
1326 ImageProvider.get("cancel"),
1327 tr("Click to return to the relation editor and to resume relation editing") ,
1328 null /* no specific help topic */
1329 )
1330 };
1331
1332 int ret = HelpAwareOptionPane.showOptionDialog(
1333 Main.parent,
1334 tr("<html>This relation has been changed outside of the editor.<br>"
1335 + "You cannot apply your changes and continue editing.<br>"
1336 + "<br>"
1337 + "Do you want to create a conflict and close the editor?</html>"),
1338 tr("Conflict in data"),
1339 JOptionPane.WARNING_MESSAGE,
1340 null,
1341 options,
1342 options[0], // OK is default
1343 "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
1344 );
1345 return ret == 0;
1346 }
1347
1348 protected void warnDoubleConflict() {
1349 JOptionPane.showMessageDialog(
1350 Main.parent,
1351 tr("<html>Layer ''{0}'' already has a conflict for object<br>"
1352 + "''{1}''.<br>"
1353 + "Please resolve this conflict first, then try again.</html>",
1354 getLayer().getName(),
1355 getRelation().getDisplayName(DefaultNameFormatter.getInstance())
1356 ),
1357 tr("Double conflict"),
1358 JOptionPane.WARNING_MESSAGE
1359 );
1360 }
1361 }
1362
1363 class ApplyAction extends SavingAction {
1364 public ApplyAction() {
1365 putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
1366 putValue(SMALL_ICON, ImageProvider.get("save"));
1367 putValue(NAME, tr("Apply"));
1368 setEnabled(true);
1369 }
1370
1371 public void run() {
1372 if (getRelation() == null) {
1373 applyNewRelation();
1374 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1375 || tagEditorPanel.getModel().isDirty()) {
1376 if (isDirtyRelation()) {
1377 if (confirmClosingBecauseOfDirtyState()) {
1378 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1379 warnDoubleConflict();
1380 return;
1381 }
1382 applyExistingConflictingRelation();
1383 setVisible(false);
1384 }
1385 } else {
1386 applyExistingNonConflictingRelation();
1387 }
1388 }
1389 }
1390
1391 @Override
1392 public void actionPerformed(ActionEvent e) {
1393 run();
1394 }
1395 }
1396
1397 class OKAction extends SavingAction {
1398 public OKAction() {
1399 putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
1400 putValue(SMALL_ICON, ImageProvider.get("ok"));
1401 putValue(NAME, tr("OK"));
1402 setEnabled(true);
1403 }
1404
1405 public void run() {
1406 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
1407 memberTable.stopHighlighting();
1408 if (getRelation() == null) {
1409 applyNewRelation();
1410 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1411 || tagEditorPanel.getModel().isDirty()) {
1412 if (isDirtyRelation()) {
1413 if (confirmClosingBecauseOfDirtyState()) {
1414 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1415 warnDoubleConflict();
1416 return;
1417 }
1418 applyExistingConflictingRelation();
1419 } else
1420 return;
1421 } else {
1422 applyExistingNonConflictingRelation();
1423 }
1424 }
1425 setVisible(false);
1426 }
1427
1428 @Override
1429 public void actionPerformed(ActionEvent e) {
1430 run();
1431 }
1432 }
1433
1434 class CancelAction extends SavingAction {
1435 public CancelAction() {
1436 putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
1437 putValue(SMALL_ICON, ImageProvider.get("cancel"));
1438 putValue(NAME, tr("Cancel"));
1439
1440 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
1441 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
1442 getRootPane().getActionMap().put("ESCAPE", this);
1443 setEnabled(true);
1444 }
1445
1446 @Override
1447 public void actionPerformed(ActionEvent e) {
1448 memberTable.stopHighlighting();
1449 TagEditorModel tagModel = tagEditorPanel.getModel();
1450 Relation snapshot = getRelationSnapshot();
1451 if ((!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty())
1452 && !(snapshot == null && tagModel.getTags().isEmpty())) {
1453 //give the user a chance to save the changes
1454 int ret = confirmClosingByCancel();
1455 if (ret == 0) { //Yes, save the changes
1456 //copied from OKAction.run()
1457 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
1458 if (getRelation() == null) {
1459 applyNewRelation();
1460 } else if (!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty()) {
1461 if (isDirtyRelation()) {
1462 if (confirmClosingBecauseOfDirtyState()) {
1463 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1464 warnDoubleConflict();
1465 return;
1466 }
1467 applyExistingConflictingRelation();
1468 } else
1469 return;
1470 } else {
1471 applyExistingNonConflictingRelation();
1472 }
1473 }
1474 } else if (ret == 2) //Cancel, continue editing
1475 return;
1476 //in case of "No, discard", there is no extra action to be performed here.
1477 }
1478 setVisible(false);
1479 }
1480
1481 protected int confirmClosingByCancel() {
1482 ButtonSpec[] options = new ButtonSpec[] {
1483 new ButtonSpec(
1484 tr("Yes, save the changes and close"),
1485 ImageProvider.get("ok"),
1486 tr("Click to save the changes and close this relation editor") ,
1487 null /* no specific help topic */
1488 ),
1489 new ButtonSpec(
1490 tr("No, discard the changes and close"),
1491 ImageProvider.get("cancel"),
1492 tr("Click to discard the changes and close this relation editor") ,
1493 null /* no specific help topic */
1494 ),
1495 new ButtonSpec(
1496 tr("Cancel, continue editing"),
1497 ImageProvider.get("cancel"),
1498 tr("Click to return to the relation editor and to resume relation editing") ,
1499 null /* no specific help topic */
1500 )
1501 };
1502
1503 return HelpAwareOptionPane.showOptionDialog(
1504 Main.parent,
1505 tr("<html>The relation has been changed.<br>"
1506 + "<br>"
1507 + "Do you want to save your changes?</html>"),
1508 tr("Unsaved changes"),
1509 JOptionPane.WARNING_MESSAGE,
1510 null,
1511 options,
1512 options[0], // OK is default,
1513 "/Dialog/RelationEditor#DiscardChanges"
1514 );
1515 }
1516 }
1517
1518 class AddTagAction extends AbstractAction {
1519 public AddTagAction() {
1520 putValue(SHORT_DESCRIPTION, tr("Add an empty tag"));
1521 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1522 setEnabled(true);
1523 }
1524
1525 @Override
1526 public void actionPerformed(ActionEvent e) {
1527 tagEditorPanel.getModel().appendNewTag();
1528 }
1529 }
1530
1531 class DownloadIncompleteMembersAction extends AbstractAction implements TableModelListener {
1532 public DownloadIncompleteMembersAction() {
1533 String tooltip = tr("Download all incomplete members");
1534 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete"));
1535 putValue(NAME, tr("Download Members"));
1536 Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
1537 KeyEvent.VK_HOME, Shortcut.ALT);
1538 sc.setAccelerator(this);
1539 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1540 updateEnabledState();
1541 }
1542
1543 @Override
1544 public void actionPerformed(ActionEvent e) {
1545 if (!isEnabled())
1546 return;
1547 Main.worker.submit(new DownloadRelationMemberTask(
1548 getRelation(),
1549 memberTableModel.getIncompleteMemberPrimitives(),
1550 getLayer(),
1551 GenericRelationEditor.this)
1552 );
1553 }
1554
1555 protected void updateEnabledState() {
1556 setEnabled(memberTableModel.hasIncompleteMembers() && !Main.isOffline(OnlineResource.OSM_API));
1557 }
1558
1559 @Override
1560 public void tableChanged(TableModelEvent e) {
1561 updateEnabledState();
1562 }
1563 }
1564
1565 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener, TableModelListener{
1566 public DownloadSelectedIncompleteMembersAction() {
1567 putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members"));
1568 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
1569 putValue(NAME, tr("Download Members"));
1570 // Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
1571 // KeyEvent.VK_K, Shortcut.ALT)
1572 updateEnabledState();
1573 }
1574
1575 @Override
1576 public void actionPerformed(ActionEvent e) {
1577 if (!isEnabled())
1578 return;
1579 Main.worker.submit(new DownloadRelationMemberTask(
1580 getRelation(),
1581 memberTableModel.getSelectedIncompleteMemberPrimitives(),
1582 getLayer(),
1583 GenericRelationEditor.this)
1584 );
1585 }
1586
1587 protected void updateEnabledState() {
1588 setEnabled(memberTableModel.hasIncompleteSelectedMembers() && !Main.isOffline(OnlineResource.OSM_API));
1589 }
1590
1591 @Override
1592 public void valueChanged(ListSelectionEvent e) {
1593 updateEnabledState();
1594 }
1595
1596 @Override
1597 public void tableChanged(TableModelEvent e) {
1598 updateEnabledState();
1599 }
1600 }
1601
1602 class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener {
1603 public SetRoleAction() {
1604 putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
1605 putValue(SMALL_ICON, ImageProvider.get("apply"));
1606 putValue(NAME, tr("Apply Role"));
1607 refreshEnabled();
1608 }
1609
1610 protected void refreshEnabled() {
1611 setEnabled(memberTable.getSelectedRowCount() > 0);
1612 }
1613
1614 protected boolean isEmptyRole() {
1615 return tfRole.getText() == null || tfRole.getText().trim().isEmpty();
1616 }
1617
1618 protected boolean confirmSettingEmptyRole(int onNumMembers) {
1619 String message = "<html>"
1620 + trn("You are setting an empty role on {0} object.",
1621 "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers)
1622 + "<br>"
1623 + tr("This is equal to deleting the roles of these objects.") +
1624 "<br>"
1625 + tr("Do you really want to apply the new role?") + "</html>";
1626 String[] options = new String[] {
1627 tr("Yes, apply it"),
1628 tr("No, do not apply")
1629 };
1630 int ret = ConditionalOptionPaneUtil.showOptionDialog(
1631 "relation_editor.confirm_applying_empty_role",
1632 Main.parent,
1633 message,
1634 tr("Confirm empty role"),
1635 JOptionPane.YES_NO_OPTION,
1636 JOptionPane.WARNING_MESSAGE,
1637 options,
1638 options[0]
1639 );
1640 switch(ret) {
1641 case JOptionPane.YES_OPTION:
1642 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
1643 return true;
1644 default:
1645 return false;
1646 }
1647 }
1648
1649 @Override
1650 public void actionPerformed(ActionEvent e) {
1651 if (isEmptyRole()) {
1652 if (!confirmSettingEmptyRole(memberTable.getSelectedRowCount()))
1653 return;
1654 }
1655 memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
1656 }
1657
1658 @Override
1659 public void valueChanged(ListSelectionEvent e) {
1660 refreshEnabled();
1661 }
1662
1663 @Override
1664 public void changedUpdate(DocumentEvent e) {
1665 refreshEnabled();
1666 }
1667
1668 @Override
1669 public void insertUpdate(DocumentEvent e) {
1670 refreshEnabled();
1671 }
1672
1673 @Override
1674 public void removeUpdate(DocumentEvent e) {
1675 refreshEnabled();
1676 }
1677 }
1678
1679 /**
1680 * Creates a new relation with a copy of the current editor state.
1681 */
1682 class DuplicateRelationAction extends AbstractAction {
1683 public DuplicateRelationAction() {
1684 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
1685 // FIXME provide an icon
1686 putValue(SMALL_ICON, ImageProvider.get("duplicate"));
1687 putValue(NAME, tr("Duplicate"));
1688 setEnabled(true);
1689 }
1690
1691 @Override
1692 public void actionPerformed(ActionEvent e) {
1693 Relation copy = new Relation();
1694 tagEditorPanel.getModel().applyToPrimitive(copy);
1695 memberTableModel.applyToRelation(copy);
1696 RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers());
1697 editor.setVisible(true);
1698 }
1699 }
1700
1701 /**
1702 * Action for editing the currently selected relation.
1703 */
1704 class EditAction extends AbstractAction implements ListSelectionListener {
1705 public EditAction() {
1706 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
1707 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1708 refreshEnabled();
1709 }
1710
1711 protected void refreshEnabled() {
1712 setEnabled(memberTable.getSelectedRowCount() == 1
1713 && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
1714 }
1715
1716 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
1717 Collection<RelationMember> members = new HashSet<>();
1718 Collection<OsmPrimitive> selection = getLayer().data.getSelected();
1719 for (RelationMember member: r.getMembers()) {
1720 if (selection.contains(member.getMember())) {
1721 members.add(member);
1722 }
1723 }
1724 return members;
1725 }
1726
1727 public void run() {
1728 int idx = memberTable.getSelectedRow();
1729 if (idx < 0)
1730 return;
1731 OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
1732 if (!(primitive instanceof Relation))
1733 return;
1734 Relation r = (Relation) primitive;
1735 if (r.isIncomplete())
1736 return;
1737
1738 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r));
1739 editor.setVisible(true);
1740 }
1741
1742 @Override
1743 public void actionPerformed(ActionEvent e) {
1744 if (!isEnabled())
1745 return;
1746 run();
1747 }
1748
1749 @Override
1750 public void valueChanged(ListSelectionEvent e) {
1751 refreshEnabled();
1752 }
1753 }
1754
1755 class PasteMembersAction extends AddFromSelectionAction {
1756
1757 @Override
1758 public void actionPerformed(ActionEvent e) {
1759 try {
1760 List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
1761 DataSet ds = getLayer().data;
1762 List<OsmPrimitive> toAdd = new ArrayList<>();
1763 boolean hasNewInOtherLayer = false;
1764
1765 for (PrimitiveData primitive: primitives) {
1766 OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
1767 if (primitiveInDs != null) {
1768 toAdd.add(primitiveInDs);
1769 } else if (!primitive.isNew()) {
1770 OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
1771 ds.addPrimitive(p);
1772 toAdd.add(p);
1773 } else {
1774 hasNewInOtherLayer = true;
1775 break;
1776 }
1777 }
1778
1779 if (hasNewInOtherLayer) {
1780 JOptionPane.showMessageDialog(Main.parent,
1781 tr("Members from paste buffer cannot be added because they are not included in current layer"));
1782 return;
1783 }
1784
1785 toAdd = filterConfirmedPrimitives(toAdd);
1786 int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
1787 if (index == -1) {
1788 index = memberTableModel.getRowCount() - 1;
1789 }
1790 memberTableModel.addMembersAfterIdx(toAdd, index);
1791
1792 tfRole.requestFocusInWindow();
1793
1794 } catch (AddAbortException ex) {
1795 // Do nothing
1796 if (Main.isTraceEnabled()) {
1797 Main.trace(ex.getMessage());
1798 }
1799 }
1800 }
1801 }
1802
1803 class CopyMembersAction extends AbstractAction {
1804 @Override
1805 public void actionPerformed(ActionEvent e) {
1806 Set<OsmPrimitive> primitives = new HashSet<>();
1807 for (RelationMember rm: memberTableModel.getSelectedMembers()) {
1808 primitives.add(rm.getMember());
1809 }
1810 if (!primitives.isEmpty()) {
1811 CopyAction.copy(getLayer(), primitives);
1812 }
1813 }
1814 }
1815
1816 class MemberTableDblClickAdapter extends MouseAdapter {
1817 @Override
1818 public void mouseClicked(MouseEvent e) {
1819 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
1820 new EditAction().run();
1821 }
1822 }
1823 }
1824}
Note: See TracBrowser for help on using the repository browser.