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

Last change on this file since 13486 was 12933, checked in by Don-vip, 7 years ago

fix #15368 - Button that selects the relation from relation editor

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