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

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

package refactoring of conflict commands => move all of them to new package command.conflict (some plugins have to be updated)

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