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

Last change on this file since 14027 was 14027, checked in by michael2402, 6 years ago

See #16388: New mechanism for plugins to register relation editor actions.

  • Property svn:eol-style set to native
File size: 38.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.GraphicsEnvironment;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Window;
14import java.awt.datatransfer.Clipboard;
15import java.awt.datatransfer.FlavorListener;
16import java.awt.event.ActionEvent;
17import java.awt.event.FocusAdapter;
18import java.awt.event.FocusEvent;
19import java.awt.event.InputEvent;
20import java.awt.event.KeyEvent;
21import java.awt.event.MouseAdapter;
22import java.awt.event.MouseEvent;
23import java.awt.event.WindowAdapter;
24import java.awt.event.WindowEvent;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.EnumSet;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Set;
33
34import javax.swing.AbstractAction;
35import javax.swing.BorderFactory;
36import javax.swing.InputMap;
37import javax.swing.JButton;
38import javax.swing.JComponent;
39import javax.swing.JLabel;
40import javax.swing.JMenuItem;
41import javax.swing.JOptionPane;
42import javax.swing.JPanel;
43import javax.swing.JRootPane;
44import javax.swing.JScrollPane;
45import javax.swing.JSplitPane;
46import javax.swing.JTabbedPane;
47import javax.swing.JTable;
48import javax.swing.JToolBar;
49import javax.swing.KeyStroke;
50
51import org.openstreetmap.josm.Main;
52import org.openstreetmap.josm.actions.JosmAction;
53import org.openstreetmap.josm.command.ChangeCommand;
54import org.openstreetmap.josm.command.Command;
55import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
56import org.openstreetmap.josm.data.osm.OsmPrimitive;
57import org.openstreetmap.josm.data.osm.Relation;
58import org.openstreetmap.josm.data.osm.RelationMember;
59import org.openstreetmap.josm.data.osm.Tag;
60import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
61import org.openstreetmap.josm.gui.MainApplication;
62import org.openstreetmap.josm.gui.MainMenu;
63import org.openstreetmap.josm.gui.ScrollViewport;
64import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
65import org.openstreetmap.josm.gui.dialogs.relation.actions.AbstractRelationEditorAction;
66import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
67import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
68import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction;
69import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedBeforeSelection;
70import org.openstreetmap.josm.gui.dialogs.relation.actions.ApplyAction;
71import org.openstreetmap.josm.gui.dialogs.relation.actions.CancelAction;
72import org.openstreetmap.josm.gui.dialogs.relation.actions.CopyMembersAction;
73import org.openstreetmap.josm.gui.dialogs.relation.actions.DeleteCurrentRelationAction;
74import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadIncompleteMembersAction;
75import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadSelectedIncompleteMembersAction;
76import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction;
77import org.openstreetmap.josm.gui.dialogs.relation.actions.EditAction;
78import org.openstreetmap.josm.gui.dialogs.relation.actions.IRelationEditorActionAccess;
79import org.openstreetmap.josm.gui.dialogs.relation.actions.IRelationEditorActionGroup;
80import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveDownAction;
81import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveUpAction;
82import org.openstreetmap.josm.gui.dialogs.relation.actions.OKAction;
83import org.openstreetmap.josm.gui.dialogs.relation.actions.PasteMembersAction;
84import org.openstreetmap.josm.gui.dialogs.relation.actions.RefreshAction;
85import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction;
86import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction;
87import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction;
88import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectAction;
89import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction;
90import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectedMembersForSelectionAction;
91import org.openstreetmap.josm.gui.dialogs.relation.actions.SetRoleAction;
92import org.openstreetmap.josm.gui.dialogs.relation.actions.SortAction;
93import org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction;
94import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
95import org.openstreetmap.josm.gui.help.HelpUtil;
96import org.openstreetmap.josm.gui.layer.OsmDataLayer;
97import org.openstreetmap.josm.gui.tagging.TagEditorModel;
98import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
99import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
100import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
101import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
102import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
103import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
104import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
105import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
106import org.openstreetmap.josm.gui.util.WindowGeometry;
107import org.openstreetmap.josm.spi.preferences.Config;
108import org.openstreetmap.josm.tools.CheckParameterUtil;
109import org.openstreetmap.josm.tools.Logging;
110import org.openstreetmap.josm.tools.Shortcut;
111import org.openstreetmap.josm.tools.Utils;
112
113/**
114 * This dialog is for editing relations.
115 * @since 343
116 */
117public class GenericRelationEditor extends RelationEditor {
118 /** the tag table and its model */
119 private final TagEditorPanel tagEditorPanel;
120 private final ReferringRelationsBrowser referrerBrowser;
121 private final ReferringRelationsBrowserModel referrerModel;
122
123 /** the member table and its model */
124 private final MemberTable memberTable;
125 private final MemberTableModel memberTableModel;
126
127 /** the selection table and its model */
128 private final SelectionTable selectionTable;
129 private final SelectionTableModel selectionTableModel;
130
131 private final AutoCompletingTextField tfRole;
132
133 /**
134 * the menu item in the windows menu. Required to properly hide on dialog close.
135 */
136 private JMenuItem windowMenuItem;
137 /**
138 * The toolbar with the buttons on the left
139 */
140 private final LeftButtonToolbar leftButtonToolbar;
141 /**
142 * Action for performing the {@link RefreshAction}
143 */
144 private final RefreshAction refreshAction;
145 /**
146 * Action for performing the {@link ApplyAction}
147 */
148 private final ApplyAction applyAction;
149 /**
150 * Action for performing the {@link SelectAction}
151 */
152 private final SelectAction selectAction;
153 /**
154 * Action for performing the {@link DuplicateRelationAction}
155 */
156 private final DuplicateRelationAction duplicateAction;
157 /**
158 * Action for performing the {@link DeleteCurrentRelationAction}
159 */
160 private final DeleteCurrentRelationAction deleteAction;
161 /**
162 * Action for performing the {@link OKAction}
163 */
164 private final OKAction okAction;
165 /**
166 * Action for performing the {@link CancelAction}
167 */
168 private final CancelAction cancelAction;
169 /**
170 * A list of listeners that need to be notified on clipboard content changes.
171 */
172 private final ArrayList<FlavorListener> clipboardListeners = new ArrayList<>();
173
174 /**
175 * Creates a new relation editor for the given relation. The relation will be saved if the user
176 * selects "ok" in the editor.
177 *
178 * If no relation is given, will create an editor for a new relation.
179 *
180 * @param layer the {@link OsmDataLayer} the new or edited relation belongs to
181 * @param relation relation to edit, or null to create a new one.
182 * @param selectedMembers a collection of members which shall be selected initially
183 */
184 public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
185 super(layer, relation);
186
187 setRememberWindowGeometry(getClass().getName() + ".geometry",
188 WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650)));
189
190 final TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
191
192 @Override
193 public void updateTags(List<Tag> tags) {
194 tagEditorPanel.getModel().updateTags(tags);
195 }
196
197 @Override
198 public Collection<OsmPrimitive> getSelection() {
199 Relation relation = new Relation();
200 tagEditorPanel.getModel().applyToPrimitive(relation);
201 return Collections.<OsmPrimitive>singletonList(relation);
202 }
203 };
204
205 // init the various models
206 //
207 memberTableModel = new MemberTableModel(relation, getLayer(), presetHandler);
208 memberTableModel.register();
209 selectionTableModel = new SelectionTableModel(getLayer());
210 selectionTableModel.register();
211 referrerModel = new ReferringRelationsBrowserModel(relation);
212
213 tagEditorPanel = new TagEditorPanel(relation, presetHandler);
214 populateModels(relation);
215 tagEditorPanel.getModel().ensureOneTag();
216
217 // setting up the member table
218 memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
219 memberTable.addMouseListener(new MemberTableDblClickAdapter());
220 memberTableModel.addMemberModelListener(memberTable);
221
222 MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
223 selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
224 selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
225
226 leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess());
227 tfRole = buildRoleTextField(this);
228
229 JSplitPane pane = buildSplitPane(
230 buildTagEditorPanel(tagEditorPanel),
231 buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
232 this);
233 pane.setPreferredSize(new Dimension(100, 100));
234
235 JPanel pnl = new JPanel(new BorderLayout());
236 pnl.add(pane, BorderLayout.CENTER);
237 pnl.setBorder(BorderFactory.createRaisedBevelBorder());
238
239 getContentPane().setLayout(new BorderLayout());
240 JTabbedPane tabbedPane = new JTabbedPane();
241 tabbedPane.add(tr("Tags and Members"), pnl);
242 referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel);
243 tabbedPane.add(tr("Parent Relations"), referrerBrowser);
244 tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation));
245 tabbedPane.addChangeListener(e -> {
246 JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource();
247 int index = sourceTabbedPane.getSelectedIndex();
248 String title = sourceTabbedPane.getTitleAt(index);
249 if (title.equals(tr("Parent Relations"))) {
250 referrerBrowser.init();
251 }
252 });
253
254 IRelationEditorActionAccess actionAccess = new RelationEditorActionAccess();
255
256 refreshAction = new RefreshAction(actionAccess);
257 applyAction = new ApplyAction(actionAccess);
258 selectAction = new SelectAction(actionAccess);
259 duplicateAction = new DuplicateRelationAction(actionAccess);
260 deleteAction = new DeleteCurrentRelationAction(actionAccess);
261 addPropertyChangeListener(deleteAction);
262
263 okAction = new OKAction(actionAccess);
264 cancelAction = new CancelAction(actionAccess);
265
266 getContentPane().add(buildToolBar(refreshAction, applyAction, selectAction, duplicateAction, deleteAction), BorderLayout.NORTH);
267 getContentPane().add(tabbedPane, BorderLayout.CENTER);
268 getContentPane().add(buildOkCancelButtonPanel(okAction, cancelAction), BorderLayout.SOUTH);
269
270 setSize(findMaxDialogSize());
271
272 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
273 addWindowListener(
274 new WindowAdapter() {
275 @Override
276 public void windowOpened(WindowEvent e) {
277 cleanSelfReferences(memberTableModel, getRelation());
278 }
279
280 @Override
281 public void windowClosing(WindowEvent e) {
282 cancel();
283 }
284 }
285 );
286 // CHECKSTYLE.OFF: LineLength
287 registerCopyPasteAction(tagEditorPanel.getPasteAction(), "PASTE_TAGS",
288 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke(),
289 getRootPane(), memberTable, selectionTable);
290 // CHECKSTYLE.ON: LineLength
291
292 KeyStroke key = Shortcut.getPasteKeyStroke();
293 if (key != null) {
294 // handle uncommon situation, that user has no keystroke assigned to paste
295 registerCopyPasteAction(new PasteMembersAction(actionAccess) {
296 private static final long serialVersionUID = 1L;
297
298 @Override
299 public void actionPerformed(ActionEvent e) {
300 super.actionPerformed(e);
301 tfRole.requestFocusInWindow();
302 }
303 }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
304 }
305 key = Shortcut.getCopyKeyStroke();
306 if (key != null) {
307 // handle uncommon situation, that user has no keystroke assigned to copy
308 registerCopyPasteAction(new CopyMembersAction(actionAccess),
309 "COPY_MEMBERS", key, getRootPane(), memberTable, selectionTable);
310 }
311 tagEditorPanel.setNextFocusComponent(memberTable);
312 selectionTable.setFocusable(false);
313 memberTableModel.setSelectedMembers(selectedMembers);
314 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/RelationEditor"));
315 }
316
317 @Override
318 public void reloadDataFromRelation() {
319 setRelation(getRelation());
320 populateModels(getRelation());
321 refreshAction.updateEnabledState();
322 }
323
324 private void populateModels(Relation relation) {
325 if (relation != null) {
326 tagEditorPanel.getModel().initFromPrimitive(relation);
327 memberTableModel.populate(relation);
328 if (!getLayer().data.getRelations().contains(relation)) {
329 // treat it as a new relation if it doesn't exist in the data set yet.
330 setRelation(null);
331 }
332 } else {
333 tagEditorPanel.getModel().clear();
334 memberTableModel.populate(null);
335 }
336 }
337
338 /**
339 * Apply changes.
340 * @see ApplyAction
341 */
342 public void apply() {
343 applyAction.actionPerformed(null);
344 }
345
346 /**
347 * Select relation.
348 * @see SelectAction
349 * @since 12933
350 */
351 public void select() {
352 selectAction.actionPerformed(null);
353 }
354
355 /**
356 * Cancel changes.
357 * @see CancelAction
358 */
359 public void cancel() {
360 cancelAction.actionPerformed(null);
361 }
362
363 /**
364 * Creates the toolbar
365 * @param actions relation toolbar actions
366 * @return the toolbar
367 * @since 12933
368 */
369 protected static JToolBar buildToolBar(AbstractRelationEditorAction... actions) {
370 JToolBar tb = new JToolBar();
371 tb.setFloatable(false);
372 for (AbstractRelationEditorAction action : actions) {
373 tb.add(action);
374 }
375 return tb;
376 }
377
378 /**
379 * builds the panel with the OK and the Cancel button
380 * @param okAction OK action
381 * @param cancelAction Cancel action
382 *
383 * @return the panel with the OK and the Cancel button
384 */
385 protected static JPanel buildOkCancelButtonPanel(OKAction okAction, CancelAction cancelAction) {
386 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
387 pnl.add(new JButton(okAction));
388 pnl.add(new JButton(cancelAction));
389 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
390 return pnl;
391 }
392
393 /**
394 * builds the panel with the tag editor
395 * @param tagEditorPanel tag editor panel
396 *
397 * @return the panel with the tag editor
398 */
399 protected static JPanel buildTagEditorPanel(TagEditorPanel tagEditorPanel) {
400 JPanel pnl = new JPanel(new GridBagLayout());
401
402 GridBagConstraints gc = new GridBagConstraints();
403 gc.gridx = 0;
404 gc.gridy = 0;
405 gc.gridheight = 1;
406 gc.gridwidth = 1;
407 gc.fill = GridBagConstraints.HORIZONTAL;
408 gc.anchor = GridBagConstraints.FIRST_LINE_START;
409 gc.weightx = 1.0;
410 gc.weighty = 0.0;
411 pnl.add(new JLabel(tr("Tags")), gc);
412
413 gc.gridx = 0;
414 gc.gridy = 1;
415 gc.fill = GridBagConstraints.BOTH;
416 gc.anchor = GridBagConstraints.CENTER;
417 gc.weightx = 1.0;
418 gc.weighty = 1.0;
419 pnl.add(tagEditorPanel, gc);
420 return pnl;
421 }
422
423 /**
424 * builds the role text field
425 * @param re relation editor
426 * @return the role text field
427 */
428 protected static AutoCompletingTextField buildRoleTextField(final IRelationEditor re) {
429 final AutoCompletingTextField tfRole = new AutoCompletingTextField(10);
430 tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
431 tfRole.addFocusListener(new FocusAdapter() {
432 @Override
433 public void focusGained(FocusEvent e) {
434 tfRole.selectAll();
435 }
436 });
437 tfRole.setAutoCompletionList(new AutoCompletionList());
438 tfRole.addFocusListener(
439 new FocusAdapter() {
440 @Override
441 public void focusGained(FocusEvent e) {
442 AutoCompletionList list = tfRole.getAutoCompletionList();
443 if (list != null) {
444 list.clear();
445 AutoCompletionManager.of(re.getLayer().data).populateWithMemberRoles(list, re.getRelation());
446 }
447 }
448 }
449 );
450 tfRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));
451 return tfRole;
452 }
453
454 /**
455 * builds the panel for the relation member editor
456 * @param leftButtonToolbar left button toolbar
457 * @param editorAccess The relation editor
458 *
459 * @return the panel for the relation member editor
460 */
461 protected static JPanel buildMemberEditorPanel(
462 LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) {
463 final JPanel pnl = new JPanel(new GridBagLayout());
464 final JScrollPane scrollPane = new JScrollPane(editorAccess.getMemberTable());
465
466 GridBagConstraints gc = new GridBagConstraints();
467 gc.gridx = 0;
468 gc.gridy = 0;
469 gc.gridwidth = 2;
470 gc.fill = GridBagConstraints.HORIZONTAL;
471 gc.anchor = GridBagConstraints.FIRST_LINE_START;
472 gc.weightx = 1.0;
473 gc.weighty = 0.0;
474 pnl.add(new JLabel(tr("Members")), gc);
475
476 gc.gridx = 0;
477 gc.gridy = 1;
478 gc.gridheight = 2;
479 gc.gridwidth = 1;
480 gc.fill = GridBagConstraints.VERTICAL;
481 gc.anchor = GridBagConstraints.NORTHWEST;
482 gc.weightx = 0.0;
483 gc.weighty = 1.0;
484 pnl.add(new ScrollViewport(leftButtonToolbar, ScrollViewport.VERTICAL_DIRECTION), gc);
485
486 gc.gridx = 1;
487 gc.gridy = 1;
488 gc.gridheight = 1;
489 gc.fill = GridBagConstraints.BOTH;
490 gc.anchor = GridBagConstraints.CENTER;
491 gc.weightx = 0.6;
492 gc.weighty = 1.0;
493 pnl.add(scrollPane, gc);
494
495 // --- role editing
496 JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
497 p3.add(new JLabel(tr("Apply Role:")));
498 p3.add(editorAccess.getTextFieldRole());
499 SetRoleAction setRoleAction = new SetRoleAction(editorAccess);
500 editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(setRoleAction);
501 editorAccess.getTextFieldRole().getDocument().addDocumentListener(setRoleAction);
502 editorAccess.getTextFieldRole().addActionListener(setRoleAction);
503 editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(
504 e -> editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0)
505 );
506 editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0);
507 JButton btnApply = new JButton(setRoleAction);
508 btnApply.setPreferredSize(new Dimension(20, 20));
509 btnApply.setText("");
510 p3.add(btnApply);
511
512 gc.gridx = 1;
513 gc.gridy = 2;
514 gc.fill = GridBagConstraints.HORIZONTAL;
515 gc.anchor = GridBagConstraints.LAST_LINE_START;
516 gc.weightx = 1.0;
517 gc.weighty = 0.0;
518 pnl.add(p3, gc);
519
520 JPanel pnl2 = new JPanel(new GridBagLayout());
521
522 gc.gridx = 0;
523 gc.gridy = 0;
524 gc.gridheight = 1;
525 gc.gridwidth = 3;
526 gc.fill = GridBagConstraints.HORIZONTAL;
527 gc.anchor = GridBagConstraints.FIRST_LINE_START;
528 gc.weightx = 1.0;
529 gc.weighty = 0.0;
530 pnl2.add(new JLabel(tr("Selection")), gc);
531
532 gc.gridx = 0;
533 gc.gridy = 1;
534 gc.gridheight = 1;
535 gc.gridwidth = 1;
536 gc.fill = GridBagConstraints.VERTICAL;
537 gc.anchor = GridBagConstraints.NORTHWEST;
538 gc.weightx = 0.0;
539 gc.weighty = 1.0;
540 pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(editorAccess),
541 ScrollViewport.VERTICAL_DIRECTION), gc);
542
543 gc.gridx = 1;
544 gc.gridy = 1;
545 gc.weightx = 1.0;
546 gc.weighty = 1.0;
547 gc.fill = GridBagConstraints.BOTH;
548 pnl2.add(buildSelectionTablePanel(editorAccess.getSelectionTable()), gc);
549
550 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
551 splitPane.setLeftComponent(pnl);
552 splitPane.setRightComponent(pnl2);
553 splitPane.setOneTouchExpandable(false);
554 if (editorAccess.getEditor() instanceof Window) {
555 ((Window) editorAccess.getEditor()).addWindowListener(new WindowAdapter() {
556 @Override
557 public void windowOpened(WindowEvent e) {
558 // has to be called when the window is visible, otherwise no effect
559 splitPane.setDividerLocation(0.6);
560 }
561 });
562 }
563
564 JPanel pnl3 = new JPanel(new BorderLayout());
565 pnl3.add(splitPane, BorderLayout.CENTER);
566
567 return pnl3;
568 }
569
570 /**
571 * builds the panel with the table displaying the currently selected primitives
572 * @param selectionTable selection table
573 *
574 * @return panel with current selection
575 */
576 protected static JPanel buildSelectionTablePanel(SelectionTable selectionTable) {
577 JPanel pnl = new JPanel(new BorderLayout());
578 pnl.add(new JScrollPane(selectionTable), BorderLayout.CENTER);
579 return pnl;
580 }
581
582 /**
583 * builds the {@link JSplitPane} which divides the editor in an upper and a lower half
584 * @param top top panel
585 * @param bottom bottom panel
586 * @param re relation editor
587 *
588 * @return the split panel
589 */
590 protected static JSplitPane buildSplitPane(JPanel top, JPanel bottom, IRelationEditor re) {
591 final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
592 pane.setTopComponent(top);
593 pane.setBottomComponent(bottom);
594 pane.setOneTouchExpandable(true);
595 if (re instanceof Window) {
596 ((Window) re).addWindowListener(new WindowAdapter() {
597 @Override
598 public void windowOpened(WindowEvent e) {
599 // has to be called when the window is visible, otherwise no effect
600 pane.setDividerLocation(0.3);
601 }
602 });
603 }
604 return pane;
605 }
606
607 /**
608 * The toolbar with the buttons on the left
609 */
610 static class LeftButtonToolbar extends JToolBar {
611 private static final long serialVersionUID = 1L;
612
613 /**
614 * Constructs a new {@code LeftButtonToolbar}.
615 * @param re relation editor
616 */
617 LeftButtonToolbar(IRelationEditorActionAccess editorAccess) {
618 setOrientation(JToolBar.VERTICAL);
619 setFloatable(false);
620
621 List<IRelationEditorActionGroup> groups = new ArrayList<>();
622 // Move
623 groups.add(buildNativeGroup(10,
624 new MoveUpAction(editorAccess, "moveUp"),
625 new MoveDownAction(editorAccess, "moveDown")
626 ));
627 // Edit
628 groups.add(buildNativeGroup(20,
629 new EditAction(editorAccess),
630 new RemoveAction(editorAccess, "removeSelected")
631 ));
632 // Sort
633 groups.add(buildNativeGroup(30,
634 new SortAction(editorAccess),
635 new SortBelowAction(editorAccess)
636 ));
637 // Reverse
638 groups.add(buildNativeGroup(40,
639 new ReverseAction(editorAccess)
640 ));
641 // Download
642 groups.add(buildNativeGroup(50,
643 new DownloadIncompleteMembersAction(editorAccess, "downloadIncomplete"),
644 new DownloadSelectedIncompleteMembersAction(editorAccess)
645 ));
646 groups.addAll(RelationEditorHooks.getMemberActions());
647
648 IRelationEditorActionGroup.fillToolbar(this, groups, editorAccess);
649
650
651 InputMap inputMap = editorAccess.getMemberTable().getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
652 inputMap.put((KeyStroke) new RemoveAction(editorAccess, "removeSelected").getValue(AbstractAction.ACCELERATOR_KEY), "removeSelected");
653 inputMap.put((KeyStroke) new MoveUpAction(editorAccess, "moveUp").getValue(AbstractAction.ACCELERATOR_KEY), "moveUp");
654 inputMap.put((KeyStroke) new MoveDownAction(editorAccess, "moveDown").getValue(AbstractAction.ACCELERATOR_KEY), "moveDown");
655 inputMap.put((KeyStroke) new DownloadIncompleteMembersAction(
656 editorAccess, "downloadIncomplete").getValue(AbstractAction.ACCELERATOR_KEY), "downloadIncomplete");
657 }
658 }
659
660 /**
661 * build the toolbar with the buttons for adding or removing the current selection
662 * @param memberTable member table
663 * @param memberTableModel member table model
664 * @param selectionTableModel selection table model
665 * @param re relation editor
666 *
667 * @return control buttons panel for selection/members
668 */
669 protected static JToolBar buildSelectionControlButtonToolbar(IRelationEditorActionAccess editorAccess) {
670 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
671 tb.setFloatable(false);
672
673 List<IRelationEditorActionGroup> groups = new ArrayList<>();
674 groups.add(buildNativeGroup(10,
675 new AddSelectedAtStartAction(editorAccess),
676 new AddSelectedBeforeSelection(editorAccess),
677 new AddSelectedAfterSelection(editorAccess),
678 new AddSelectedAtEndAction(editorAccess)
679 ));
680 groups.add(buildNativeGroup(20,
681 new SelectedMembersForSelectionAction(editorAccess),
682 new SelectPrimitivesForSelectedMembersAction(editorAccess)
683 ));
684 groups.add(buildNativeGroup(30,
685 new RemoveSelectedAction(editorAccess)
686 ));
687 groups.addAll(RelationEditorHooks.getSelectActions());
688
689 IRelationEditorActionGroup.fillToolbar(tb, groups, editorAccess);
690 return tb;
691 }
692
693 private static IRelationEditorActionGroup buildNativeGroup(int order, AbstractRelationEditorAction... actions) {
694 return new IRelationEditorActionGroup() {
695 @Override
696 public int order() {
697 return order;
698 }
699
700 @Override
701 public List<AbstractRelationEditorAction> getActions(IRelationEditorActionAccess editorAccess) {
702 return Arrays.asList(actions);
703 }
704 };
705 }
706
707 @Override
708 protected Dimension findMaxDialogSize() {
709 return new Dimension(700, 650);
710 }
711
712 @Override
713 public void setVisible(boolean visible) {
714 if (isVisible() == visible) {
715 return;
716 }
717 if (visible) {
718 tagEditorPanel.initAutoCompletion(getLayer());
719 }
720 super.setVisible(visible);
721 Clipboard clipboard = ClipboardUtils.getClipboard();
722 if (visible) {
723 RelationDialogManager.getRelationDialogManager().positionOnScreen(this);
724 if (windowMenuItem == null) {
725 windowMenuItem = addToWindowMenu(this, getLayer().getName());
726 }
727 tagEditorPanel.requestFocusInWindow();
728 for (FlavorListener listener : clipboardListeners) {
729 clipboard.addFlavorListener(listener);
730 }
731 } else {
732 // make sure all registered listeners are unregistered
733 //
734 memberTable.stopHighlighting();
735 selectionTableModel.unregister();
736 memberTableModel.unregister();
737 memberTable.unregisterListeners();
738 if (windowMenuItem != null) {
739 MainApplication.getMenu().windowMenu.remove(windowMenuItem);
740 windowMenuItem = null;
741 }
742 for (FlavorListener listener : clipboardListeners) {
743 clipboard.removeFlavorListener(listener);
744 }
745 dispose();
746 }
747 }
748
749 /**
750 * Adds current relation editor to the windows menu (in the "volatile" group)
751 * @param re relation editor
752 * @param layerName layer name
753 * @return created menu item
754 */
755 protected static JMenuItem addToWindowMenu(IRelationEditor re, String layerName) {
756 Relation r = re.getRelation();
757 String name = r == null ? tr("New relation") : r.getLocalName();
758 JosmAction focusAction = new JosmAction(
759 tr("Relation Editor: {0}", name == null && r != null ? r.getId() : name),
760 "dialogs/relationlist",
761 tr("Focus Relation Editor with relation ''{0}'' in layer ''{1}''", name, layerName),
762 null, false, false) {
763 private static final long serialVersionUID = 1L;
764
765 @Override
766 public void actionPerformed(ActionEvent e) {
767 ((RelationEditor) getValue("relationEditor")).setVisible(true);
768 }
769 };
770 focusAction.putValue("relationEditor", re);
771 return MainMenu.add(MainApplication.getMenu().windowMenu, focusAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE);
772 }
773
774 /**
775 * checks whether the current relation has members referring to itself. If so,
776 * warns the users and provides an option for removing these members.
777 * @param memberTableModel member table model
778 * @param relation relation
779 */
780 protected static void cleanSelfReferences(MemberTableModel memberTableModel, Relation relation) {
781 List<OsmPrimitive> toCheck = new ArrayList<>();
782 toCheck.add(relation);
783 if (memberTableModel.hasMembersReferringTo(toCheck)) {
784 int ret = ConditionalOptionPaneUtil.showOptionDialog(
785 "clean_relation_self_references",
786 Main.parent,
787 tr("<html>There is at least one member in this relation referring<br>"
788 + "to the relation itself.<br>"
789 + "This creates circular dependencies and is discouraged.<br>"
790 + "How do you want to proceed with circular dependencies?</html>"),
791 tr("Warning"),
792 JOptionPane.YES_NO_OPTION,
793 JOptionPane.WARNING_MESSAGE,
794 new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")},
795 tr("Remove them, clean up relation")
796 );
797 switch(ret) {
798 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
799 case JOptionPane.CLOSED_OPTION:
800 case JOptionPane.NO_OPTION:
801 return;
802 case JOptionPane.YES_OPTION:
803 memberTableModel.removeMembersReferringTo(toCheck);
804 break;
805 default: // Do nothing
806 }
807 }
808 }
809
810 private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
811 JRootPane rootPane, JTable... tables) {
812 if (shortcut == null) {
813 Logging.warn("No shortcut provided for the Paste action in Relation editor dialog");
814 } else {
815 int mods = shortcut.getModifiers();
816 int code = shortcut.getKeyCode();
817 if (code != KeyEvent.VK_INSERT && (mods == 0 || mods == InputEvent.SHIFT_DOWN_MASK)) {
818 Logging.info(tr("Sorry, shortcut \"{0}\" can not be enabled in Relation editor dialog"), shortcut);
819 return;
820 }
821 }
822 rootPane.getActionMap().put(actionName, action);
823 if (shortcut != null) {
824 rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
825 // Assign also to JTables because they have their own Copy&Paste implementation
826 // (which is disabled in this case but eats key shortcuts anyway)
827 for (JTable table : tables) {
828 table.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
829 table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
830 table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
831 }
832 }
833 if (action instanceof FlavorListener) {
834 clipboardListeners.add((FlavorListener) action);
835 }
836 }
837
838 /**
839 * Exception thrown when user aborts add operation.
840 */
841 public static class AddAbortException extends Exception {
842 }
843
844 /**
845 * Asks confirmationbefore adding a primitive.
846 * @param primitive primitive to add
847 * @return {@code true} is user confirms the operation, {@code false} otherwise
848 * @throws AddAbortException if user aborts operation
849 */
850 public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
851 String msg = tr("<html>This relation already has one or more members referring to<br>"
852 + "the object ''{0}''<br>"
853 + "<br>"
854 + "Do you really want to add another relation member?</html>",
855 Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance()))
856 );
857 int ret = ConditionalOptionPaneUtil.showOptionDialog(
858 "add_primitive_to_relation",
859 Main.parent,
860 msg,
861 tr("Multiple members referring to same object."),
862 JOptionPane.YES_NO_CANCEL_OPTION,
863 JOptionPane.WARNING_MESSAGE,
864 null,
865 null
866 );
867 switch(ret) {
868 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
869 case JOptionPane.YES_OPTION:
870 return true;
871 case JOptionPane.NO_OPTION:
872 case JOptionPane.CLOSED_OPTION:
873 return false;
874 case JOptionPane.CANCEL_OPTION:
875 default:
876 throw new AddAbortException();
877 }
878 }
879
880 /**
881 * Warn about circular references.
882 * @param primitive the concerned primitive
883 */
884 public static void warnOfCircularReferences(OsmPrimitive primitive) {
885 String msg = tr("<html>You are trying to add a relation to itself.<br>"
886 + "<br>"
887 + "This creates circular references and is therefore discouraged.<br>"
888 + "Skipping relation ''{0}''.</html>",
889 Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance())));
890 JOptionPane.showMessageDialog(
891 Main.parent,
892 msg,
893 tr("Warning"),
894 JOptionPane.WARNING_MESSAGE);
895 }
896
897 /**
898 * Adds primitives to a given relation.
899 * @param orig The relation to modify
900 * @param primitivesToAdd The primitives to add as relation members
901 * @return The resulting command
902 * @throws IllegalArgumentException if orig is null
903 */
904 public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) {
905 CheckParameterUtil.ensureParameterNotNull(orig, "orig");
906 try {
907 final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(
908 EnumSet.of(TaggingPresetType.forPrimitive(orig)), orig.getKeys(), false);
909 Relation relation = new Relation(orig);
910 boolean modified = false;
911 for (OsmPrimitive p : primitivesToAdd) {
912 if (p instanceof Relation && orig.equals(p)) {
913 if (!GraphicsEnvironment.isHeadless()) {
914 warnOfCircularReferences(p);
915 }
916 continue;
917 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p))
918 && !confirmAddingPrimitive(p)) {
919 continue;
920 }
921 final Set<String> roles = findSuggestedRoles(presets, p);
922 relation.addMember(new RelationMember(roles.size() == 1 ? roles.iterator().next() : "", p));
923 modified = true;
924 }
925 return modified ? new ChangeCommand(orig, relation) : null;
926 } catch (AddAbortException ign) {
927 Logging.trace(ign);
928 return null;
929 }
930 }
931
932 protected static Set<String> findSuggestedRoles(final Collection<TaggingPreset> presets, OsmPrimitive p) {
933 final Set<String> roles = new HashSet<>();
934 for (TaggingPreset preset : presets) {
935 String role = preset.suggestRoleForOsmPrimitive(p);
936 if (role != null && !role.isEmpty()) {
937 roles.add(role);
938 }
939 }
940 return roles;
941 }
942
943 class MemberTableDblClickAdapter extends MouseAdapter {
944 @Override
945 public void mouseClicked(MouseEvent e) {
946 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
947 new EditAction(new RelationEditorActionAccess()).actionPerformed(null);
948 }
949 }
950 }
951
952 private class RelationEditorActionAccess implements IRelationEditorActionAccess {
953
954 @Override
955 public MemberTable getMemberTable() {
956 return memberTable;
957 }
958
959 @Override
960 public MemberTableModel getMemberTableModel() {
961 return memberTableModel;
962 }
963
964 @Override
965 public SelectionTable getSelectionTable() {
966 return selectionTable;
967 }
968
969 @Override
970 public SelectionTableModel getSelectionTableModel() {
971 return selectionTableModel;
972 }
973
974 @Override
975 public IRelationEditor getEditor() {
976 return GenericRelationEditor.this;
977 }
978
979 @Override
980 public TagEditorModel getTagModel() {
981 return tagEditorPanel.getModel();
982 }
983
984 @Override
985 public AutoCompletingTextField getTextFieldRole() {
986 return tfRole;
987 }
988
989 }
990}
Note: See TracBrowser for help on using the repository browser.