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

Last change on this file since 3920 was 3920, checked in by jttt, 13 years ago

Paste in relation dialog - paste after last member when nothing is selecting

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