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

Last change on this file since 4191 was 4191, checked in by stoecker, 13 years ago

remove old debug stuff

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