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

Last change on this file since 6038 was 6038, checked in by akks, 11 years ago

fix #8829: relation was often hanging JOSM after 6036 + better highlighting

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