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

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

fix #16388 - fix sonar warning

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