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