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

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

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