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

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

fix remaining checkstyle issues

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