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

Last change on this file since 5082 was 5082, checked in by simon04, 12 years ago

fix #5395 - add "Add selection to relation" to popup menu of relation toggle dialog

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