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

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

fix many checkstyle violations

  • Property svn:eol-style set to native
File size: 70.8 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
689 private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut) {
690 int mods = shortcut.getModifiers();
691 int code = shortcut.getKeyCode();
692 if (code!=KeyEvent.VK_INSERT && (mods == 0 || mods == InputEvent.SHIFT_DOWN_MASK)) {
693 Main.info(tr("Sorry, shortcut \"{0}\" can not be enabled in Relation editor dialog"), shortcut);
694 return;
695 }
696 getRootPane().getActionMap().put(actionName, action);
697 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
698 // Assign also to JTables because they have their own Copy&Paste implementation (which is disabled in this case but eats key shortcuts anyway)
699 memberTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
700 memberTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
701 memberTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
702 selectionTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
703 selectionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
704 selectionTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
705 }
706
707 static class AddAbortException extends Exception {
708 }
709
710 static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
711 String msg = tr("<html>This relation already has one or more members referring to<br>"
712 + "the object ''{0}''<br>"
713 + "<br>"
714 + "Do you really want to add another relation member?</html>",
715 primitive.getDisplayName(DefaultNameFormatter.getInstance())
716 );
717 int ret = ConditionalOptionPaneUtil.showOptionDialog(
718 "add_primitive_to_relation",
719 Main.parent,
720 msg,
721 tr("Multiple members referring to same object."),
722 JOptionPane.YES_NO_CANCEL_OPTION,
723 JOptionPane.WARNING_MESSAGE,
724 null,
725 null
726 );
727 switch(ret) {
728 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
729 case JOptionPane.YES_OPTION:
730 return true;
731 case JOptionPane.NO_OPTION:
732 case JOptionPane.CLOSED_OPTION:
733 return false;
734 case JOptionPane.CANCEL_OPTION:
735 throw new AddAbortException();
736 }
737 // should not happen
738 return false;
739 }
740
741 static void warnOfCircularReferences(OsmPrimitive primitive) {
742 String msg = tr("<html>You are trying to add a relation to itself.<br>"
743 + "<br>"
744 + "This creates circular references and is therefore discouraged.<br>"
745 + "Skipping relation ''{0}''.</html>",
746 primitive.getDisplayName(DefaultNameFormatter.getInstance()));
747 JOptionPane.showMessageDialog(
748 Main.parent,
749 msg,
750 tr("Warning"),
751 JOptionPane.WARNING_MESSAGE);
752 }
753
754 /**
755 * Adds primitives to a given relation.
756 * @param orig The relation to modify
757 * @param primitivesToAdd The primitives to add as relation members
758 * @return The resulting command
759 * @throws IllegalArgumentException if orig is null
760 */
761 public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) {
762 CheckParameterUtil.ensureParameterNotNull(orig, "orig");
763 try {
764 final Collection<TaggingPreset> presets = TaggingPreset.getMatchingPresets(
765 EnumSet.of(TaggingPresetType.RELATION), orig.getKeys(), false);
766 Relation relation = new Relation(orig);
767 boolean modified = false;
768 for (OsmPrimitive p : primitivesToAdd) {
769 if (p instanceof Relation && orig.equals(p)) {
770 warnOfCircularReferences(p);
771 continue;
772 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p))
773 && !confirmAddingPrimitive(p)) {
774 continue;
775 }
776 final Set<String> roles = findSuggestedRoles(presets, p);
777 relation.addMember(new RelationMember(roles.size() == 1 ? roles.iterator().next() : "", p));
778 modified = true;
779 }
780 return modified ? new ChangeCommand(orig, relation) : null;
781 } catch (AddAbortException ign) {
782 return null;
783 }
784 }
785
786 protected static Set<String> findSuggestedRoles(final Collection<TaggingPreset> presets, OsmPrimitive p) {
787 final Set<String> roles = new HashSet<>();
788 for (TaggingPreset preset : presets) {
789 String role = preset.suggestRoleForOsmPrimitive(p);
790 if (role != null && !role.isEmpty()) {
791 roles.add(role);
792 }
793 }
794 return roles;
795 }
796
797 abstract class AddFromSelectionAction extends AbstractAction {
798 protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
799 return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
800 }
801
802 protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
803 if (primitives == null || primitives.isEmpty())
804 return primitives;
805 List<OsmPrimitive> ret = new ArrayList<>();
806 ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
807 for (OsmPrimitive primitive : primitives) {
808 if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) {
809 warnOfCircularReferences(primitive);
810 continue;
811 }
812 if (isPotentialDuplicate(primitive)) {
813 if (confirmAddingPrimitive(primitive)) {
814 ret.add(primitive);
815 }
816 continue;
817 } else {
818 ret.add(primitive);
819 }
820 }
821 ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
822 return ret;
823 }
824 }
825
826 class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener {
827 public AddSelectedAtStartAction() {
828 putValue(SHORT_DESCRIPTION,
829 tr("Add all objects selected in the current dataset before the first member"));
830 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
831 refreshEnabled();
832 }
833
834 protected void refreshEnabled() {
835 setEnabled(selectionTableModel.getRowCount() > 0);
836 }
837
838 @Override
839 public void actionPerformed(ActionEvent e) {
840 try {
841 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
842 memberTableModel.addMembersAtBeginning(toAdd);
843 } catch(AddAbortException ex) {
844 // do nothing
845 }
846 }
847
848 @Override
849 public void tableChanged(TableModelEvent e) {
850 refreshEnabled();
851 }
852 }
853
854 class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener {
855 public AddSelectedAtEndAction() {
856 putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
857 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
858 refreshEnabled();
859 }
860
861 protected void refreshEnabled() {
862 setEnabled(selectionTableModel.getRowCount() > 0);
863 }
864
865 @Override
866 public void actionPerformed(ActionEvent e) {
867 try {
868 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
869 memberTableModel.addMembersAtEnd(toAdd);
870 } catch(AddAbortException ex) {
871 // do nothing
872 }
873 }
874
875 @Override
876 public void tableChanged(TableModelEvent e) {
877 refreshEnabled();
878 }
879 }
880
881 class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
882 public AddSelectedBeforeSelection() {
883 putValue(SHORT_DESCRIPTION,
884 tr("Add all objects selected in the current dataset before the first selected member"));
885 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
886 refreshEnabled();
887 }
888
889 protected void refreshEnabled() {
890 setEnabled(selectionTableModel.getRowCount() > 0
891 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
892 }
893
894 @Override
895 public void actionPerformed(ActionEvent e) {
896 try {
897 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
898 memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel
899 .getSelectionModel().getMinSelectionIndex());
900 } catch(AddAbortException ex) {
901 // do nothing
902 }
903
904 }
905
906 @Override
907 public void tableChanged(TableModelEvent e) {
908 refreshEnabled();
909 }
910
911 @Override
912 public void valueChanged(ListSelectionEvent e) {
913 refreshEnabled();
914 }
915 }
916
917 class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
918 public AddSelectedAfterSelection() {
919 putValue(SHORT_DESCRIPTION,
920 tr("Add all objects selected in the current dataset after the last selected member"));
921 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
922 refreshEnabled();
923 }
924
925 protected void refreshEnabled() {
926 setEnabled(selectionTableModel.getRowCount() > 0
927 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
928 }
929
930 @Override
931 public void actionPerformed(ActionEvent e) {
932 try {
933 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
934 memberTableModel.addMembersAfterIdx(toAdd, memberTableModel
935 .getSelectionModel().getMaxSelectionIndex());
936 } catch(AddAbortException ex) {
937 // do nothing
938 }
939 }
940
941 @Override
942 public void tableChanged(TableModelEvent e) {
943 refreshEnabled();
944 }
945
946 @Override
947 public void valueChanged(ListSelectionEvent e) {
948 refreshEnabled();
949 }
950 }
951
952 class RemoveSelectedAction extends AbstractAction implements TableModelListener {
953 public RemoveSelectedAction() {
954 putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects"));
955 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
956 updateEnabledState();
957 }
958
959 protected void updateEnabledState() {
960 DataSet ds = getLayer().data;
961 if (ds == null || ds.getSelected().isEmpty()) {
962 setEnabled(false);
963 return;
964 }
965 // only enable the action if we have members referring to the
966 // selected primitives
967 //
968 setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
969 }
970
971 @Override
972 public void actionPerformed(ActionEvent e) {
973 memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
974 }
975
976 @Override
977 public void tableChanged(TableModelEvent e) {
978 updateEnabledState();
979 }
980 }
981
982 /**
983 * Selects members in the relation editor which refer to primitives in the current
984 * selection of the context layer.
985 *
986 */
987 class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener {
988 public SelectedMembersForSelectionAction() {
989 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
990 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
991 updateEnabledState();
992 }
993
994 protected void updateEnabledState() {
995 boolean enabled = selectionTableModel.getRowCount() > 0
996 && !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty();
997
998 if (enabled) {
999 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",
1000 memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size()));
1001 } else {
1002 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
1003 }
1004 setEnabled(enabled);
1005 }
1006
1007 @Override
1008 public void actionPerformed(ActionEvent e) {
1009 memberTableModel.selectMembersReferringTo(getLayer().data.getSelected());
1010 }
1011
1012 @Override
1013 public void tableChanged(TableModelEvent e) {
1014 updateEnabledState();
1015 }
1016 }
1017
1018 /**
1019 * Selects primitives in the layer this editor belongs to. The selected primitives are
1020 * equal to the set of primitives the currently selected relation members refer to.
1021 *
1022 */
1023 class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener {
1024 public SelectPrimitivesForSelectedMembersAction() {
1025 putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members"));
1026 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
1027 updateEnabledState();
1028 }
1029
1030 protected void updateEnabledState() {
1031 setEnabled(memberTable.getSelectedRowCount() > 0);
1032 }
1033
1034 @Override
1035 public void actionPerformed(ActionEvent e) {
1036 getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives());
1037 }
1038
1039 @Override
1040 public void valueChanged(ListSelectionEvent e) {
1041 updateEnabledState();
1042 }
1043 }
1044
1045 class SortAction extends AbstractAction implements TableModelListener {
1046 public SortAction() {
1047 String tooltip = tr("Sort the relation members");
1048 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
1049 putValue(NAME, tr("Sort"));
1050 Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"),
1051 KeyEvent.VK_END, Shortcut.ALT);
1052 sc.setAccelerator(this);
1053 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1054 updateEnabledState();
1055 }
1056
1057 @Override
1058 public void actionPerformed(ActionEvent e) {
1059 memberTableModel.sort();
1060 }
1061
1062 protected void updateEnabledState() {
1063 setEnabled(memberTableModel.getRowCount() > 0);
1064 }
1065
1066 @Override
1067 public void tableChanged(TableModelEvent e) {
1068 updateEnabledState();
1069 }
1070 }
1071
1072 class SortBelowAction extends AbstractAction implements TableModelListener, ListSelectionListener {
1073 public SortBelowAction() {
1074 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort_below"));
1075 putValue(NAME, tr("Sort below"));
1076 putValue(SHORT_DESCRIPTION, tr("Sort the selected relation members and all members below"));
1077 updateEnabledState();
1078 }
1079
1080 @Override
1081 public void actionPerformed(ActionEvent e) {
1082 memberTableModel.sortBelow();
1083 }
1084
1085 protected void updateEnabledState() {
1086 setEnabled(memberTableModel.getRowCount() > 0 && !memberTableModel.getSelectionModel().isSelectionEmpty());
1087 }
1088
1089 @Override
1090 public void tableChanged(TableModelEvent e) {
1091 updateEnabledState();
1092 }
1093
1094 @Override
1095 public void valueChanged(ListSelectionEvent e) {
1096 updateEnabledState();
1097 }
1098 }
1099
1100 class ReverseAction extends AbstractAction implements TableModelListener {
1101 public ReverseAction() {
1102 putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
1103 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse"));
1104 putValue(NAME, tr("Reverse"));
1105 // Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"),
1106 // KeyEvent.VK_END, Shortcut.ALT)
1107 updateEnabledState();
1108 }
1109
1110 @Override
1111 public void actionPerformed(ActionEvent e) {
1112 memberTableModel.reverse();
1113 }
1114
1115 protected void updateEnabledState() {
1116 setEnabled(memberTableModel.getRowCount() > 0);
1117 }
1118
1119 @Override
1120 public void tableChanged(TableModelEvent e) {
1121 updateEnabledState();
1122 }
1123 }
1124
1125 class MoveUpAction extends AbstractAction implements ListSelectionListener {
1126 public MoveUpAction() {
1127 String tooltip = tr("Move the currently selected members up");
1128 putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
1129 Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"),
1130 KeyEvent.VK_UP, Shortcut.ALT);
1131 sc.setAccelerator(this);
1132 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1133 setEnabled(false);
1134 }
1135
1136 @Override
1137 public void actionPerformed(ActionEvent e) {
1138 memberTableModel.moveUp(memberTable.getSelectedRows());
1139 }
1140
1141 @Override
1142 public void valueChanged(ListSelectionEvent e) {
1143 setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
1144 }
1145 }
1146
1147 class MoveDownAction extends AbstractAction implements ListSelectionListener {
1148 public MoveDownAction() {
1149 String tooltip = tr("Move the currently selected members down");
1150 putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
1151 Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"),
1152 KeyEvent.VK_DOWN, Shortcut.ALT);
1153 sc.setAccelerator(this);
1154 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1155 setEnabled(false);
1156 }
1157
1158 @Override
1159 public void actionPerformed(ActionEvent e) {
1160 memberTableModel.moveDown(memberTable.getSelectedRows());
1161 }
1162
1163 @Override
1164 public void valueChanged(ListSelectionEvent e) {
1165 setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
1166 }
1167 }
1168
1169 class RemoveAction extends AbstractAction implements ListSelectionListener {
1170 public RemoveAction() {
1171 String tooltip = tr("Remove the currently selected members from this relation");
1172 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1173 putValue(NAME, tr("Remove"));
1174 Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"),
1175 KeyEvent.VK_DELETE, Shortcut.ALT);
1176 sc.setAccelerator(this);
1177 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1178 setEnabled(false);
1179 }
1180
1181 @Override
1182 public void actionPerformed(ActionEvent e) {
1183 memberTableModel.remove(memberTable.getSelectedRows());
1184 }
1185
1186 @Override
1187 public void valueChanged(ListSelectionEvent e) {
1188 setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
1189 }
1190 }
1191
1192 class DeleteCurrentRelationAction extends AbstractAction implements PropertyChangeListener{
1193 public DeleteCurrentRelationAction() {
1194 putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
1195 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1196 putValue(NAME, tr("Delete"));
1197 updateEnabledState();
1198 }
1199
1200 public void run() {
1201 Relation toDelete = getRelation();
1202 if (toDelete == null)
1203 return;
1204 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
1205 getLayer(),
1206 toDelete
1207 );
1208 }
1209
1210 @Override
1211 public void actionPerformed(ActionEvent e) {
1212 run();
1213 }
1214
1215 protected void updateEnabledState() {
1216 setEnabled(getRelationSnapshot() != null);
1217 }
1218
1219 @Override
1220 public void propertyChange(PropertyChangeEvent evt) {
1221 if (evt.getPropertyName().equals(RELATION_SNAPSHOT_PROP)) {
1222 updateEnabledState();
1223 }
1224 }
1225 }
1226
1227 abstract class SavingAction extends AbstractAction {
1228 /**
1229 * apply updates to a new relation
1230 */
1231 protected void applyNewRelation() {
1232 final Relation newRelation = new Relation();
1233 tagEditorPanel.getModel().applyToPrimitive(newRelation);
1234 memberTableModel.applyToRelation(newRelation);
1235 List<RelationMember> newMembers = new ArrayList<>();
1236 for (RelationMember rm: newRelation.getMembers()) {
1237 if (!rm.getMember().isDeleted()) {
1238 newMembers.add(rm);
1239 }
1240 }
1241 if (newRelation.getMembersCount() != newMembers.size()) {
1242 newRelation.setMembers(newMembers);
1243 String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" +
1244 "was open. They have been removed from the relation members list.");
1245 JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE);
1246 }
1247 // If the user wanted to create a new relation, but hasn't added any members or
1248 // tags, don't add an empty relation
1249 if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys())
1250 return;
1251 Main.main.undoRedo.add(new AddCommand(getLayer(),newRelation));
1252
1253 // make sure everybody is notified about the changes
1254 //
1255 getLayer().data.fireSelectionChanged();
1256 GenericRelationEditor.this.setRelation(newRelation);
1257 RelationDialogManager.getRelationDialogManager().updateContext(
1258 getLayer(),
1259 getRelation(),
1260 GenericRelationEditor.this
1261 );
1262 SwingUtilities.invokeLater(new Runnable() {
1263 @Override
1264 public void run() {
1265 // Relation list gets update in EDT so selecting my be postponed to following EDT run
1266 Main.map.relationListDialog.selectRelation(newRelation);
1267 }
1268 });
1269 }
1270
1271 /**
1272 * Apply the updates for an existing relation which has been changed
1273 * outside of the relation editor.
1274 *
1275 */
1276 protected void applyExistingConflictingRelation() {
1277 Relation editedRelation = new Relation(getRelation());
1278 tagEditorPanel.getModel().applyToPrimitive(editedRelation);
1279 memberTableModel.applyToRelation(editedRelation);
1280 Conflict<Relation> conflict = new Conflict<>(getRelation(), editedRelation);
1281 Main.main.undoRedo.add(new ConflictAddCommand(getLayer(),conflict));
1282 }
1283
1284 /**
1285 * Apply the updates for an existing relation which has not been changed
1286 * outside of the relation editor.
1287 *
1288 */
1289 protected void applyExistingNonConflictingRelation() {
1290 Relation editedRelation = new Relation(getRelation());
1291 tagEditorPanel.getModel().applyToPrimitive(editedRelation);
1292 memberTableModel.applyToRelation(editedRelation);
1293 Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation));
1294 getLayer().data.fireSelectionChanged();
1295 // this will refresh the snapshot and update the dialog title
1296 //
1297 setRelation(getRelation());
1298 }
1299
1300 protected boolean confirmClosingBecauseOfDirtyState() {
1301 ButtonSpec[] options = new ButtonSpec[] {
1302 new ButtonSpec(
1303 tr("Yes, create a conflict and close"),
1304 ImageProvider.get("ok"),
1305 tr("Click to create a conflict and close this relation editor") ,
1306 null /* no specific help topic */
1307 ),
1308 new ButtonSpec(
1309 tr("No, continue editing"),
1310 ImageProvider.get("cancel"),
1311 tr("Click to return to the relation editor and to resume relation editing") ,
1312 null /* no specific help topic */
1313 )
1314 };
1315
1316 int ret = HelpAwareOptionPane.showOptionDialog(
1317 Main.parent,
1318 tr("<html>This relation has been changed outside of the editor.<br>"
1319 + "You cannot apply your changes and continue editing.<br>"
1320 + "<br>"
1321 + "Do you want to create a conflict and close the editor?</html>"),
1322 tr("Conflict in data"),
1323 JOptionPane.WARNING_MESSAGE,
1324 null,
1325 options,
1326 options[0], // OK is default
1327 "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
1328 );
1329 return ret == 0;
1330 }
1331
1332 protected void warnDoubleConflict() {
1333 JOptionPane.showMessageDialog(
1334 Main.parent,
1335 tr("<html>Layer ''{0}'' already has a conflict for object<br>"
1336 + "''{1}''.<br>"
1337 + "Please resolve this conflict first, then try again.</html>",
1338 getLayer().getName(),
1339 getRelation().getDisplayName(DefaultNameFormatter.getInstance())
1340 ),
1341 tr("Double conflict"),
1342 JOptionPane.WARNING_MESSAGE
1343 );
1344 }
1345 }
1346
1347 class ApplyAction extends SavingAction {
1348 public ApplyAction() {
1349 putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
1350 putValue(SMALL_ICON, ImageProvider.get("save"));
1351 putValue(NAME, tr("Apply"));
1352 setEnabled(true);
1353 }
1354
1355 public void run() {
1356 if (getRelation() == null) {
1357 applyNewRelation();
1358 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1359 || tagEditorPanel.getModel().isDirty()) {
1360 if (isDirtyRelation()) {
1361 if (confirmClosingBecauseOfDirtyState()) {
1362 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1363 warnDoubleConflict();
1364 return;
1365 }
1366 applyExistingConflictingRelation();
1367 setVisible(false);
1368 }
1369 } else {
1370 applyExistingNonConflictingRelation();
1371 }
1372 }
1373 }
1374
1375 @Override
1376 public void actionPerformed(ActionEvent e) {
1377 run();
1378 }
1379 }
1380
1381 class OKAction extends SavingAction {
1382 public OKAction() {
1383 putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
1384 putValue(SMALL_ICON, ImageProvider.get("ok"));
1385 putValue(NAME, tr("OK"));
1386 setEnabled(true);
1387 }
1388
1389 public void run() {
1390 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
1391 memberTable.stopHighlighting();
1392 if (getRelation() == null) {
1393 applyNewRelation();
1394 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1395 || tagEditorPanel.getModel().isDirty()) {
1396 if (isDirtyRelation()) {
1397 if (confirmClosingBecauseOfDirtyState()) {
1398 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1399 warnDoubleConflict();
1400 return;
1401 }
1402 applyExistingConflictingRelation();
1403 } else
1404 return;
1405 } else {
1406 applyExistingNonConflictingRelation();
1407 }
1408 }
1409 setVisible(false);
1410 }
1411
1412 @Override
1413 public void actionPerformed(ActionEvent e) {
1414 run();
1415 }
1416 }
1417
1418 class CancelAction extends SavingAction {
1419 public CancelAction() {
1420 putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
1421 putValue(SMALL_ICON, ImageProvider.get("cancel"));
1422 putValue(NAME, tr("Cancel"));
1423
1424 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
1425 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
1426 getRootPane().getActionMap().put("ESCAPE", this);
1427 setEnabled(true);
1428 }
1429
1430 @Override
1431 public void actionPerformed(ActionEvent e) {
1432 memberTable.stopHighlighting();
1433 TagEditorModel tagModel = tagEditorPanel.getModel();
1434 Relation snapshot = getRelationSnapshot();
1435 if ((!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty())
1436 && !(snapshot == null && tagModel.getTags().isEmpty())) {
1437 //give the user a chance to save the changes
1438 int ret = confirmClosingByCancel();
1439 if (ret == 0) { //Yes, save the changes
1440 //copied from OKAction.run()
1441 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
1442 if (getRelation() == null) {
1443 applyNewRelation();
1444 } else if (!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty()) {
1445 if (isDirtyRelation()) {
1446 if (confirmClosingBecauseOfDirtyState()) {
1447 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1448 warnDoubleConflict();
1449 return;
1450 }
1451 applyExistingConflictingRelation();
1452 } else
1453 return;
1454 } else {
1455 applyExistingNonConflictingRelation();
1456 }
1457 }
1458 } else if (ret == 2) //Cancel, continue editing
1459 return;
1460 //in case of "No, discard", there is no extra action to be performed here.
1461 }
1462 setVisible(false);
1463 }
1464
1465 protected int confirmClosingByCancel() {
1466 ButtonSpec[] options = new ButtonSpec[] {
1467 new ButtonSpec(
1468 tr("Yes, save the changes and close"),
1469 ImageProvider.get("ok"),
1470 tr("Click to save the changes and close this relation editor") ,
1471 null /* no specific help topic */
1472 ),
1473 new ButtonSpec(
1474 tr("No, discard the changes and close"),
1475 ImageProvider.get("cancel"),
1476 tr("Click to discard the changes and close this relation editor") ,
1477 null /* no specific help topic */
1478 ),
1479 new ButtonSpec(
1480 tr("Cancel, continue editing"),
1481 ImageProvider.get("cancel"),
1482 tr("Click to return to the relation editor and to resume relation editing") ,
1483 null /* no specific help topic */
1484 )
1485 };
1486
1487 return HelpAwareOptionPane.showOptionDialog(
1488 Main.parent,
1489 tr("<html>The relation has been changed.<br>"
1490 + "<br>"
1491 + "Do you want to save your changes?</html>"),
1492 tr("Unsaved changes"),
1493 JOptionPane.WARNING_MESSAGE,
1494 null,
1495 options,
1496 options[0], // OK is default,
1497 "/Dialog/RelationEditor#DiscardChanges"
1498 );
1499 }
1500 }
1501
1502 class AddTagAction extends AbstractAction {
1503 public AddTagAction() {
1504 putValue(SHORT_DESCRIPTION, tr("Add an empty tag"));
1505 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1506 setEnabled(true);
1507 }
1508
1509 @Override
1510 public void actionPerformed(ActionEvent e) {
1511 tagEditorPanel.getModel().appendNewTag();
1512 }
1513 }
1514
1515 class DownloadIncompleteMembersAction extends AbstractAction implements TableModelListener {
1516 public DownloadIncompleteMembersAction() {
1517 String tooltip = tr("Download all incomplete members");
1518 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete"));
1519 putValue(NAME, tr("Download Members"));
1520 Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
1521 KeyEvent.VK_HOME, Shortcut.ALT);
1522 sc.setAccelerator(this);
1523 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
1524 updateEnabledState();
1525 }
1526
1527 @Override
1528 public void actionPerformed(ActionEvent e) {
1529 if (!isEnabled())
1530 return;
1531 Main.worker.submit(new DownloadRelationMemberTask(
1532 getRelation(),
1533 memberTableModel.getIncompleteMemberPrimitives(),
1534 getLayer(),
1535 GenericRelationEditor.this)
1536 );
1537 }
1538
1539 protected void updateEnabledState() {
1540 setEnabled(memberTableModel.hasIncompleteMembers() && !Main.isOffline(OnlineResource.OSM_API));
1541 }
1542
1543 @Override
1544 public void tableChanged(TableModelEvent e) {
1545 updateEnabledState();
1546 }
1547 }
1548
1549 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener, TableModelListener{
1550 public DownloadSelectedIncompleteMembersAction() {
1551 putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members"));
1552 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
1553 putValue(NAME, tr("Download Members"));
1554 // Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
1555 // KeyEvent.VK_K, Shortcut.ALT)
1556 updateEnabledState();
1557 }
1558
1559 @Override
1560 public void actionPerformed(ActionEvent e) {
1561 if (!isEnabled())
1562 return;
1563 Main.worker.submit(new DownloadRelationMemberTask(
1564 getRelation(),
1565 memberTableModel.getSelectedIncompleteMemberPrimitives(),
1566 getLayer(),
1567 GenericRelationEditor.this)
1568 );
1569 }
1570
1571 protected void updateEnabledState() {
1572 setEnabled(memberTableModel.hasIncompleteSelectedMembers() && !Main.isOffline(OnlineResource.OSM_API));
1573 }
1574
1575 @Override
1576 public void valueChanged(ListSelectionEvent e) {
1577 updateEnabledState();
1578 }
1579
1580 @Override
1581 public void tableChanged(TableModelEvent e) {
1582 updateEnabledState();
1583 }
1584 }
1585
1586 class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener {
1587 public SetRoleAction() {
1588 putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
1589 putValue(SMALL_ICON, ImageProvider.get("apply"));
1590 putValue(NAME, tr("Apply Role"));
1591 refreshEnabled();
1592 }
1593
1594 protected void refreshEnabled() {
1595 setEnabled(memberTable.getSelectedRowCount() > 0);
1596 }
1597
1598 protected boolean isEmptyRole() {
1599 return tfRole.getText() == null || tfRole.getText().trim().isEmpty();
1600 }
1601
1602 protected boolean confirmSettingEmptyRole(int onNumMembers) {
1603 String message = "<html>"
1604 + trn("You are setting an empty role on {0} object.",
1605 "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers)
1606 + "<br>"
1607 + tr("This is equal to deleting the roles of these objects.") +
1608 "<br>"
1609 + tr("Do you really want to apply the new role?") + "</html>";
1610 String[] options = new String[] {
1611 tr("Yes, apply it"),
1612 tr("No, do not apply")
1613 };
1614 int ret = ConditionalOptionPaneUtil.showOptionDialog(
1615 "relation_editor.confirm_applying_empty_role",
1616 Main.parent,
1617 message,
1618 tr("Confirm empty role"),
1619 JOptionPane.YES_NO_OPTION,
1620 JOptionPane.WARNING_MESSAGE,
1621 options,
1622 options[0]
1623 );
1624 switch(ret) {
1625 case JOptionPane.YES_OPTION:
1626 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
1627 return true;
1628 default:
1629 return false;
1630 }
1631 }
1632
1633 @Override
1634 public void actionPerformed(ActionEvent e) {
1635 if (isEmptyRole()) {
1636 if (!confirmSettingEmptyRole(memberTable.getSelectedRowCount()))
1637 return;
1638 }
1639 memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
1640 }
1641
1642 @Override
1643 public void valueChanged(ListSelectionEvent e) {
1644 refreshEnabled();
1645 }
1646
1647 @Override
1648 public void changedUpdate(DocumentEvent e) {
1649 refreshEnabled();
1650 }
1651
1652 @Override
1653 public void insertUpdate(DocumentEvent e) {
1654 refreshEnabled();
1655 }
1656
1657 @Override
1658 public void removeUpdate(DocumentEvent e) {
1659 refreshEnabled();
1660 }
1661 }
1662
1663 /**
1664 * Creates a new relation with a copy of the current editor state.
1665 */
1666 class DuplicateRelationAction extends AbstractAction {
1667 public DuplicateRelationAction() {
1668 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
1669 // FIXME provide an icon
1670 putValue(SMALL_ICON, ImageProvider.get("duplicate"));
1671 putValue(NAME, tr("Duplicate"));
1672 setEnabled(true);
1673 }
1674
1675 @Override
1676 public void actionPerformed(ActionEvent e) {
1677 Relation copy = new Relation();
1678 tagEditorPanel.getModel().applyToPrimitive(copy);
1679 memberTableModel.applyToRelation(copy);
1680 RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers());
1681 editor.setVisible(true);
1682 }
1683 }
1684
1685 /**
1686 * Action for editing the currently selected relation.
1687 */
1688 class EditAction extends AbstractAction implements ListSelectionListener {
1689 public EditAction() {
1690 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
1691 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1692 refreshEnabled();
1693 }
1694
1695 protected void refreshEnabled() {
1696 setEnabled(memberTable.getSelectedRowCount() == 1
1697 && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
1698 }
1699
1700 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
1701 Collection<RelationMember> members = new HashSet<>();
1702 Collection<OsmPrimitive> selection = getLayer().data.getSelected();
1703 for (RelationMember member: r.getMembers()) {
1704 if (selection.contains(member.getMember())) {
1705 members.add(member);
1706 }
1707 }
1708 return members;
1709 }
1710
1711 public void run() {
1712 int idx = memberTable.getSelectedRow();
1713 if (idx < 0)
1714 return;
1715 OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
1716 if (!(primitive instanceof Relation))
1717 return;
1718 Relation r = (Relation) primitive;
1719 if (r.isIncomplete())
1720 return;
1721
1722 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r));
1723 editor.setVisible(true);
1724 }
1725
1726 @Override
1727 public void actionPerformed(ActionEvent e) {
1728 if (!isEnabled())
1729 return;
1730 run();
1731 }
1732
1733 @Override
1734 public void valueChanged(ListSelectionEvent e) {
1735 refreshEnabled();
1736 }
1737 }
1738
1739 class PasteMembersAction extends AddFromSelectionAction {
1740
1741 @Override
1742 public void actionPerformed(ActionEvent e) {
1743 try {
1744 List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
1745 DataSet ds = getLayer().data;
1746 List<OsmPrimitive> toAdd = new ArrayList<>();
1747 boolean hasNewInOtherLayer = false;
1748
1749 for (PrimitiveData primitive: primitives) {
1750 OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
1751 if (primitiveInDs != null) {
1752 toAdd.add(primitiveInDs);
1753 } else if (!primitive.isNew()) {
1754 OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
1755 ds.addPrimitive(p);
1756 toAdd.add(p);
1757 } else {
1758 hasNewInOtherLayer = true;
1759 break;
1760 }
1761 }
1762
1763 if (hasNewInOtherLayer) {
1764 JOptionPane.showMessageDialog(Main.parent,
1765 tr("Members from paste buffer cannot be added because they are not included in current layer"));
1766 return;
1767 }
1768
1769 toAdd = filterConfirmedPrimitives(toAdd);
1770 int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
1771 if (index == -1) {
1772 index = memberTableModel.getRowCount() - 1;
1773 }
1774 memberTableModel.addMembersAfterIdx(toAdd, index);
1775
1776 tfRole.requestFocusInWindow();
1777
1778 } catch (AddAbortException ex) {
1779 // Do nothing
1780 }
1781 }
1782 }
1783
1784 class CopyMembersAction extends AbstractAction {
1785 @Override
1786 public void actionPerformed(ActionEvent e) {
1787 Set<OsmPrimitive> primitives = new HashSet<>();
1788 for (RelationMember rm: memberTableModel.getSelectedMembers()) {
1789 primitives.add(rm.getMember());
1790 }
1791 if (!primitives.isEmpty()) {
1792 CopyAction.copy(getLayer(), primitives);
1793 }
1794 }
1795 }
1796
1797 class MemberTableDblClickAdapter extends MouseAdapter {
1798 @Override
1799 public void mouseClicked(MouseEvent e) {
1800 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
1801 new EditAction().run();
1802 }
1803 }
1804 }
1805}
Note: See TracBrowser for help on using the repository browser.