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

Last change on this file since 3561 was 3561, checked in by bastiK, 14 years ago

applied #5502 (patch by cmuelle8) - Relations editor: order of buttons

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