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

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

see #15229 - deprecate Main.parent and Main itself

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