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

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

see #8902 - Small performance enhancements / coding style (patch by shinigami):

  • while -> foreach
  • for -> for each

plus:

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