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

Last change on this file since 14470 was 14470, checked in by GerdP, 5 years ago

see #17040 Fix various memory leaks
Not sure if this will break Unit tests. Many of them don't work on my PC with a clean copy.

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