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

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

massive refactoring of GenericRelationEditor. As JDialog cannot be instantiated in headless mode, extract all actions to separate classes in new package gui.dialogs.relation.actions in order to test them with JUnit

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