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

Last change on this file since 2040 was 2040, checked in by Gubaer, 15 years ago

improved upload dialog
new: tags for changesets
new: multiple uploads to the same changeset
fixed #3381: simple imput of a changeset source
fixed #2491: Allow arbitrary key-value pairs in changesets
fixed #2436: Allow multiple uploads to one changeset

  • Property svn:eol-style set to native
File size: 58.5 KB
Line 
1package org.openstreetmap.josm.gui.dialogs.relation;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.BorderLayout;
6import java.awt.Dialog;
7import java.awt.Dimension;
8import java.awt.FlowLayout;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.Insets;
12import java.awt.event.ActionEvent;
13import java.awt.event.ComponentAdapter;
14import java.awt.event.ComponentEvent;
15import java.awt.event.FocusAdapter;
16import java.awt.event.FocusEvent;
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.io.IOException;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.List;
29import java.util.logging.Logger;
30
31import javax.swing.AbstractAction;
32import javax.swing.BorderFactory;
33import javax.swing.JButton;
34import javax.swing.JComponent;
35import javax.swing.JLabel;
36import javax.swing.JOptionPane;
37import javax.swing.JPanel;
38import javax.swing.JScrollPane;
39import javax.swing.JSplitPane;
40import javax.swing.JTabbedPane;
41import javax.swing.JTable;
42import javax.swing.KeyStroke;
43import javax.swing.SwingUtilities;
44import javax.swing.event.ChangeEvent;
45import javax.swing.event.ChangeListener;
46import javax.swing.event.DocumentEvent;
47import javax.swing.event.DocumentListener;
48import javax.swing.event.ListSelectionEvent;
49import javax.swing.event.ListSelectionListener;
50import javax.swing.event.TableModelEvent;
51import javax.swing.event.TableModelListener;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.command.AddCommand;
55import org.openstreetmap.josm.command.ChangeCommand;
56import org.openstreetmap.josm.command.ConflictAddCommand;
57import org.openstreetmap.josm.data.conflict.Conflict;
58import org.openstreetmap.josm.data.osm.DataSet;
59import org.openstreetmap.josm.data.osm.DataSource;
60import org.openstreetmap.josm.data.osm.OsmPrimitive;
61import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
62import org.openstreetmap.josm.data.osm.Relation;
63import org.openstreetmap.josm.data.osm.RelationMember;
64import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
65import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
66import org.openstreetmap.josm.gui.DefaultNameFormatter;
67import org.openstreetmap.josm.gui.ExceptionDialogUtil;
68import org.openstreetmap.josm.gui.PleaseWaitRunnable;
69import org.openstreetmap.josm.gui.SideButton;
70import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionCache;
71import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
72import org.openstreetmap.josm.gui.layer.OsmDataLayer;
73import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
74import org.openstreetmap.josm.gui.progress.ProgressMonitor;
75import org.openstreetmap.josm.gui.tagging.AutoCompletingTextField;
76import org.openstreetmap.josm.gui.tagging.TagCellEditor;
77import org.openstreetmap.josm.gui.tagging.TagEditorModel;
78import org.openstreetmap.josm.gui.tagging.TagTable;
79import org.openstreetmap.josm.io.OsmApi;
80import org.openstreetmap.josm.io.OsmServerObjectReader;
81import org.openstreetmap.josm.io.OsmTransferException;
82import org.openstreetmap.josm.tools.ImageProvider;
83import org.openstreetmap.josm.tools.Shortcut;
84import org.xml.sax.SAXException;
85
86/**
87 * This dialog is for editing relations.
88 *
89 */
90public class GenericRelationEditor extends RelationEditor {
91
92 static private final Logger logger = Logger.getLogger(GenericRelationEditor.class.getName());
93 static private final Dimension DEFAULT_EDITOR_DIMENSION = new Dimension(700, 500);
94
95 /** the tag table and its model */
96 private TagEditorModel tagEditorModel;
97 private TagTable tagTable;
98 private AutoCompletionCache acCache;
99 private AutoCompletionList acList;
100 private ReferringRelationsBrowser referrerBrowser;
101 private ReferringRelationsBrowserModel referrerModel;
102
103 /** the member table */
104 private MemberTable memberTable;
105 private MemberTableModel memberTableModel;
106
107 /** the model for the selection table */
108 private SelectionTableModel selectionTableModel;
109
110 private AutoCompletingTextField tfRole;
111
112 /**
113 * Creates a new relation editor for the given relation. The relation will be saved if the user
114 * selects "ok" in the editor.
115 *
116 * If no relation is given, will create an editor for a new relation.
117 *
118 * @param layer the {@see OsmDataLayer} the new or edited relation belongs to
119 * @param relation relation to edit, or null to create a new one.
120 * @param selectedMembers a collection of members which shall be selected initially
121 */
122 public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
123 super(layer, relation, selectedMembers);
124
125
126 // initialize the autocompletion infrastructure
127 //
128 acCache = AutoCompletionCache.getCacheForLayer(getLayer());
129 acCache.initFromJOSMDataset();
130 acList = new AutoCompletionList();
131
132 // init the various models
133 //
134 tagEditorModel = new TagEditorModel();
135 memberTableModel = new MemberTableModel(getLayer());
136 selectionTableModel = new SelectionTableModel(getLayer());
137 referrerModel = new ReferringRelationsBrowserModel(relation);
138
139 // populate the models
140 //
141 if (relation != null) {
142 this.tagEditorModel.initFromPrimitive(relation);
143 this.memberTableModel.populate(relation);
144 if (!getLayer().data.relations.contains(relation)) {
145 // treat it as a new relation if it doesn't exist in the
146 // data set yet.
147 setRelation(null);
148 }
149 } else {
150 tagEditorModel.clear();
151 this.memberTableModel.populate(null);
152 }
153 tagEditorModel.ensureOneTag();
154
155 JSplitPane pane = buildSplitPane();
156 pane.setPreferredSize(new Dimension(100, 100));
157
158 JPanel pnl = new JPanel();
159 pnl.setLayout(new BorderLayout());
160 pnl.add(pane, BorderLayout.CENTER);
161 pnl.setBorder(BorderFactory.createRaisedBevelBorder());
162
163 getContentPane().setLayout(new BorderLayout());
164 JTabbedPane tabbedPane = new JTabbedPane();
165 tabbedPane.add(tr("Tags and Members"), pnl);
166 referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel, this);
167 tabbedPane.add(tr("Parent Relations"), referrerBrowser);
168 tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation));
169 tabbedPane.addChangeListener(
170 new ChangeListener() {
171 public void stateChanged(ChangeEvent e) {
172 JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource();
173 int index = sourceTabbedPane.getSelectedIndex();
174 String title = sourceTabbedPane.getTitleAt(index);
175 if (title.equals(tr("Parent Relations"))) {
176 referrerBrowser.init();
177 }
178 }
179 }
180 );
181
182 getContentPane().add(tabbedPane, BorderLayout.CENTER);
183 getContentPane().add(buildOkCancelButtonPanel(), BorderLayout.SOUTH);
184
185 setSize(findMaxDialogSize());
186
187 addWindowListener(
188 new WindowAdapter() {
189 @Override
190 public void windowOpened(WindowEvent e) {
191 cleanSelfReferences();
192 }
193 }
194 );
195
196 memberTableModel.setSelectedMembers(selectedMembers);
197 }
198
199 /**
200 * builds the panel with the OK and the Cancel button
201 *
202 * @return the panel with the OK and the Cancel button
203 */
204 protected JPanel buildOkCancelButtonPanel() {
205 JPanel pnl = new JPanel();
206 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
207
208 pnl.add(new SideButton(new OKAction()));
209 pnl.add(new SideButton(new CancelAction()));
210
211 return pnl;
212 }
213
214 /**
215 * build the panel with the buttons on the left
216 *
217 * @return
218 */
219 protected JPanel buildTagEditorControlPanel() {
220 JPanel pnl = new JPanel();
221 pnl.setLayout(new GridBagLayout());
222
223 GridBagConstraints gc = new GridBagConstraints();
224 gc.gridx = 0;
225 gc.gridy = 0;
226 gc.gridheight = 1;
227 gc.gridwidth = 1;
228 gc.insets = new Insets(0, 5, 0, 5);
229 gc.fill = GridBagConstraints.HORIZONTAL;
230 gc.anchor = GridBagConstraints.CENTER;
231 gc.weightx = 0.0;
232 gc.weighty = 0.0;
233
234 // -----
235 AddTagAction addTagAction = new AddTagAction();
236 pnl.add(new JButton(addTagAction), gc);
237
238 // -----
239 gc.gridy = 1;
240 DeleteTagAction deleteTagAction = new DeleteTagAction();
241 tagTable.getSelectionModel().addListSelectionListener(deleteTagAction);
242 pnl.add(new JButton(deleteTagAction), gc);
243
244 // ------
245 // just grab the remaining space
246 gc.gridy = 2;
247 gc.weighty = 1.0;
248 gc.fill = GridBagConstraints.BOTH;
249 pnl.add(new JPanel(), gc);
250 return pnl;
251 }
252
253 /**
254 * builds the panel with the tag editor
255 *
256 * @return the panel with the tag editor
257 */
258 protected JPanel buildTagEditorPanel() {
259 JPanel pnl = new JPanel();
260 pnl.setLayout(new GridBagLayout());
261
262 // setting up the tag table
263 //
264 tagTable = new TagTable(tagEditorModel);
265 TagCellEditor editor = ((TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor());
266 editor.setAutoCompletionCache(acCache);
267 editor.setAutoCompletionList(acList);
268 editor = ((TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor());
269 editor.setAutoCompletionCache(acCache);
270 editor.setAutoCompletionList(acList);
271
272 final JScrollPane scrollPane = new JScrollPane(tagTable);
273
274 GridBagConstraints gc = new GridBagConstraints();
275 gc.gridx = 0;
276 gc.gridy = 0;
277 gc.gridheight = 1;
278 gc.gridwidth = 3;
279 gc.fill = GridBagConstraints.HORIZONTAL;
280 gc.anchor = GridBagConstraints.FIRST_LINE_START;
281 gc.weightx = 1.0;
282 gc.weighty = 0.0;
283 pnl.add(new JLabel(tr("Tags")), gc);
284
285 gc.gridx = 0;
286 gc.gridy = 1;
287 gc.gridheight = 1;
288 gc.gridwidth = 1;
289 gc.fill = GridBagConstraints.VERTICAL;
290 gc.anchor = GridBagConstraints.NORTHWEST;
291 gc.weightx = 0.0;
292 gc.weighty = 1.0;
293 pnl.add(buildTagEditorControlPanel(), gc);
294
295 gc.gridx = 1;
296 gc.gridy = 1;
297 gc.fill = GridBagConstraints.BOTH;
298 gc.anchor = GridBagConstraints.CENTER;
299 gc.weightx = 0.8;
300 gc.weighty = 1.0;
301 pnl.add(scrollPane, gc);
302 return pnl;
303 }
304
305 /**
306 * builds the panel for the relation member editor
307 *
308 * @return the panel for the relation member editor
309 */
310 protected JPanel buildMemberEditorPanel() {
311 final JPanel pnl = new JPanel();
312 pnl.setLayout(new GridBagLayout());
313 // setting up the member table
314 memberTable = new MemberTable(getLayer(),memberTableModel);
315 MemberRoleCellEditor editor = ((MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor());
316 editor.setAutoCompletionCache(acCache);
317 editor.setAutoCompletionList(acList);
318
319 memberTable.addMouseListener(new MemberTableDblClickAdapter());
320 memberTableModel.addMemberModelListener(memberTable);
321
322 final JScrollPane scrollPane = new JScrollPane(memberTable);
323 // this adapters ensures that the width of the tag table columns is adjusted
324 // to the width of the scroll pane viewport. Also tried to overwrite
325 // getPreferredViewportSize() in JTable, but did not work.
326 //
327 scrollPane.addComponentListener(new ComponentAdapter() {
328 @Override
329 public void componentResized(ComponentEvent e) {
330 super.componentResized(e);
331 Dimension d = scrollPane.getViewport().getExtentSize();
332 memberTable.adjustColumnWidth(d.width);
333 }
334 });
335
336 GridBagConstraints gc = new GridBagConstraints();
337 gc.gridx = 0;
338 gc.gridy = 0;
339 gc.gridheight = 1;
340 gc.gridwidth = 3;
341 gc.fill = GridBagConstraints.HORIZONTAL;
342 gc.anchor = GridBagConstraints.FIRST_LINE_START;
343 gc.weightx = 1.0;
344 gc.weighty = 0.0;
345 pnl.add(new JLabel(tr("Members")), gc);
346
347 gc.gridx = 0;
348 gc.gridy = 1;
349 gc.gridheight = 1;
350 gc.gridwidth = 1;
351 gc.fill = GridBagConstraints.VERTICAL;
352 gc.anchor = GridBagConstraints.NORTHWEST;
353 gc.weightx = 0.0;
354 gc.weighty = 1.0;
355 pnl.add(buildLeftButtonPanel(), gc);
356
357 gc.gridx = 1;
358 gc.gridy = 1;
359 gc.fill = GridBagConstraints.BOTH;
360 gc.anchor = GridBagConstraints.CENTER;
361 gc.weightx = 0.6;
362 gc.weighty = 1.0;
363 pnl.add(scrollPane, gc);
364
365 JPanel pnl2 = new JPanel();
366 pnl2.setLayout(new GridBagLayout());
367
368 gc.gridx = 0;
369 gc.gridy = 0;
370 gc.gridheight = 1;
371 gc.gridwidth = 3;
372 gc.fill = GridBagConstraints.HORIZONTAL;
373 gc.anchor = GridBagConstraints.FIRST_LINE_START;
374 gc.weightx = 1.0;
375 gc.weighty = 0.0;
376 pnl2.add(new JLabel(tr("Selection")), gc);
377
378 gc.gridx = 0;
379 gc.gridy = 1;
380 gc.gridheight = 1;
381 gc.gridwidth = 1;
382 gc.fill = GridBagConstraints.VERTICAL;
383 gc.anchor = GridBagConstraints.NORTHWEST;
384 gc.weightx = 0.0;
385 gc.weighty = 1.0;
386 pnl2.add(buildSelectionControlButtonPanel(), gc);
387
388 gc.gridx = 1;
389 gc.gridy = 1;
390 gc.weightx = 1.0;
391 gc.weighty = 1.0;
392 gc.fill = GridBagConstraints.BOTH;
393 pnl2.add(buildSelectionTablePanel(), gc);
394
395 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
396 splitPane.setLeftComponent(pnl);
397 splitPane.setRightComponent(pnl2);
398 splitPane.setOneTouchExpandable(false);
399 addWindowListener(new WindowAdapter() {
400 @Override
401 public void windowOpened(WindowEvent e) {
402 // has to be called when the window is visible, otherwise
403 // no effect
404 splitPane.setDividerLocation(0.6);
405 }
406 });
407
408
409 JPanel pnl3 = new JPanel();
410 pnl3.setLayout(new BorderLayout());
411 pnl3.add(splitPane, BorderLayout.CENTER);
412 pnl3.add(buildButtonPanel(), BorderLayout.SOUTH);
413 return pnl3;
414 }
415
416 /**
417 * builds the panel with the table displaying the currently selected primitives
418 *
419 * @return
420 */
421 protected JPanel buildSelectionTablePanel() {
422 JPanel pnl = new JPanel();
423 pnl.setLayout(new BorderLayout());
424 JTable tbl = new JTable(selectionTableModel, new SelectionTableColumnModel(memberTableModel));
425 tbl.setEnabled(false);
426 JScrollPane pane = new JScrollPane(tbl);
427 pnl.add(pane, BorderLayout.CENTER);
428 return pnl;
429 }
430
431 /**
432 * builds the {@see JSplitPane} which divides the editor in an upper and a lower half
433 *
434 * @return the split panel
435 */
436 protected JSplitPane buildSplitPane() {
437 final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
438 pane.setTopComponent(buildTagEditorPanel());
439 pane.setBottomComponent(buildMemberEditorPanel());
440 pane.setOneTouchExpandable(true);
441 addWindowListener(new WindowAdapter() {
442 @Override
443 public void windowOpened(WindowEvent e) {
444 // has to be called when the window is visible, otherwise
445 // no effect
446 pane.setDividerLocation(0.3);
447 }
448 });
449 return pane;
450 }
451
452 /**
453 * build the panel with the buttons on the left
454 *
455 * @return
456 */
457 protected JPanel buildLeftButtonPanel() {
458 JPanel pnl = new JPanel();
459 pnl.setLayout(new GridBagLayout());
460
461 GridBagConstraints gc = new GridBagConstraints();
462 gc.gridx = 0;
463 gc.gridy = 0;
464 gc.gridheight = 1;
465 gc.gridwidth = 1;
466 gc.insets = new Insets(0, 5, 0, 5);
467 gc.fill = GridBagConstraints.HORIZONTAL;
468 gc.anchor = GridBagConstraints.CENTER;
469 gc.weightx = 0.0;
470 gc.weighty = 0.0;
471
472 // -----
473 gc.gridy = 0;
474 MoveUpAction moveUpAction = new MoveUpAction();
475 memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction);
476 pnl.add(new JButton(moveUpAction), gc);
477
478 // -----
479 gc.gridy = 1;
480 MoveDownAction moveDownAction = new MoveDownAction();
481 memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction);
482 pnl.add(new JButton(moveDownAction), gc);
483
484 // -- edit action
485 gc.gridy = 2;
486 EditAction editAction = new EditAction();
487 memberTableModel.getSelectionModel().addListSelectionListener(editAction);
488 pnl.add(new JButton(editAction),gc);
489
490 // ------
491 gc.gridy = 3;
492 RemoveAction removeSelectedAction = new RemoveAction();
493 memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction);
494 pnl.add(new JButton(removeSelectedAction), gc);
495
496
497 // ------
498 gc.gridy = 4;
499 SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction();
500 memberTable.getSelectionModel().addListSelectionListener(selectAction);
501 pnl.add(new JButton(selectAction), gc);
502
503 // ------
504 gc.gridy = 5;
505 SortAction sortAction = new SortAction();
506 pnl.add(new JButton(sortAction), gc);
507
508 // ------
509 // just grab the remaining space
510 gc.gridy = 6;
511 gc.weighty = 1.0;
512 gc.fill = GridBagConstraints.BOTH;
513 pnl.add(new JPanel(), gc);
514 return pnl;
515 }
516
517 /**
518 * build the panel with the buttons for adding or removing the current selection
519 *
520 * @return
521 */
522 protected JPanel buildSelectionControlButtonPanel() {
523 JPanel pnl = new JPanel();
524 pnl.setLayout(new GridBagLayout());
525
526 GridBagConstraints gc = new GridBagConstraints();
527 gc.gridx = 0;
528 gc.gridy = 0;
529 gc.gridheight = 1;
530 gc.gridwidth = 1;
531 gc.insets = new Insets(0, 5, 0, 5);
532 gc.fill = GridBagConstraints.HORIZONTAL;
533 gc.anchor = GridBagConstraints.CENTER;
534 gc.weightx = 0.0;
535 gc.weighty = 0.0;
536 AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction();
537 selectionTableModel.addTableModelListener(addSelectedAtEndAction);
538 pnl.add(new JButton(addSelectedAtEndAction), gc);
539
540 // -----
541 gc.gridy = 1;
542 SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction();
543 selectionTableModel.addTableModelListener(selectMembersForSelectionAction);
544 memberTableModel.addTableModelListener(selectMembersForSelectionAction);
545 pnl.add(new JButton(selectMembersForSelectionAction), gc);
546
547 // -----
548 gc.gridy = 2;
549 RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction();
550 selectionTableModel.addTableModelListener(removeSelectedAction);
551 pnl.add(new JButton(removeSelectedAction), gc);
552
553 // ------
554 // just grab the remaining space
555 gc.gridy = 3;
556 gc.weighty = 1.0;
557 gc.fill = GridBagConstraints.BOTH;
558 pnl.add(new JPanel(), gc);
559
560 // -----
561 gc.gridy = 4;
562 gc.weighty = 0.0;
563 AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction();
564 selectionTableModel.addTableModelListener(addSelectionAction);
565 pnl.add(new JButton(addSelectionAction), gc);
566
567 // -----
568 gc.gridy = 5;
569 AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection();
570 selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction);
571 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction);
572 pnl.add(new JButton(addSelectedBeforeSelectionAction), gc);
573
574 // -----
575 gc.gridy = 6;
576 AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection();
577 selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction);
578 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction);
579 pnl.add(new JButton(addSelectedAfterSelectionAction), gc);
580
581 return pnl;
582 }
583
584 /**
585 * Creates the buttons for the basic editing layout
586 * @return {@see JPanel} with basic buttons
587 */
588 protected JPanel buildButtonPanel() {
589 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
590
591 // --- download members
592 buttonPanel.add(new SideButton(new DownlaodAction()));
593
594 // --- role editing
595 buttonPanel.add(new JLabel(tr("Role:")));
596 tfRole = new AutoCompletingTextField(10);
597 tfRole.addFocusListener(new FocusAdapter() {
598 @Override
599 public void focusGained(FocusEvent e) {
600 tfRole.selectAll();
601 }
602 });
603 tfRole.setAutoCompletionList(acList);
604 tfRole.addFocusListener(
605 new FocusAdapter() {
606 @Override
607 public void focusGained(FocusEvent e) {
608 acCache.populateWithMemberRoles(acList);
609 }
610 }
611 );
612
613 buttonPanel.add(tfRole);
614 SetRoleAction setRoleAction = new SetRoleAction();
615 memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
616 buttonPanel.add(new SideButton(setRoleAction));
617 tfRole.getDocument().addDocumentListener(setRoleAction);
618 tfRole.addActionListener(setRoleAction);
619
620 // --- copy relation action
621 buttonPanel.add(new SideButton(new DuplicateRelationAction()));
622
623 // --- apply relation action
624 buttonPanel.add(new SideButton(new ApplyAction()));
625
626 // --- delete relation action
627 buttonPanel.add(new SideButton(new DeleteCurrentRelationAction()));
628 return buttonPanel;
629 }
630
631 @Override
632 protected Dimension findMaxDialogSize() {
633 // FIXME: Make it remember dialog size
634 return new Dimension(700, 500);
635 }
636
637 @Override
638 public void dispose() {
639 selectionTableModel.unregister();
640 DataSet.selListeners.remove(memberTableModel);
641 super.dispose();
642 }
643
644 @Override
645 public void setVisible(boolean b) {
646 super.setVisible(b);
647 if (!b) {
648 dispose();
649 }
650 }
651
652 /**
653 * checks whether the current relation has members referring to itself. If so,
654 * warns the users and provides an option for removing these members.
655 *
656 */
657 protected void cleanSelfReferences() {
658 ArrayList<OsmPrimitive> toCheck = new ArrayList<OsmPrimitive>();
659 toCheck.add(getRelation());
660 if (memberTableModel.hasMembersReferringTo(toCheck)) {
661 int ret = ConditionalOptionPaneUtil.showOptionDialog(
662 "clean_relation_self_references",
663 Main.parent,
664 tr("<html>There is at least one member in this relation referring<br>"
665 + "to the relation itself.<br>"
666 + "This creates circular dependencies and is dicuraged.<br>"
667 + "How do you want to proceed with circular dependencies?</html>"),
668 tr("Warning"),
669 JOptionPane.YES_NO_OPTION,
670 JOptionPane.WARNING_MESSAGE,
671 new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")},
672 tr("Remove them, clean up relation")
673 );
674 switch(ret) {
675 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return;
676 case JOptionPane.CLOSED_OPTION: return;
677 case JOptionPane.NO_OPTION: return;
678 case JOptionPane.YES_OPTION:
679 memberTableModel.removeMembersReferringTo(toCheck);
680 break;
681 }
682 }
683 }
684
685 class AddAbortException extends Exception {
686 }
687
688 abstract class AddFromSelectionAction extends AbstractAction {
689 protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
690 return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
691 }
692
693 protected boolean confirmAddingPrimtive(OsmPrimitive primitive) throws AddAbortException {
694 String msg = tr("<html>This relation already has one or more members referring to<br>"
695 + "the primitive ''{0}''<br>"
696 + "<br>"
697 + "Do you really want to add another relation member?</html>",
698 primitive.getDisplayName(DefaultNameFormatter.getInstance())
699 );
700 int ret = ConditionalOptionPaneUtil.showOptionDialog(
701 "add_primitive_to_relation",
702 Main.parent,
703 msg,
704 tr("Multiple members referring to same primitive"),
705 JOptionPane.YES_NO_CANCEL_OPTION,
706 JOptionPane.WARNING_MESSAGE,
707 null,
708 null
709 );
710 switch(ret) {
711 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION : return true;
712 case JOptionPane.YES_OPTION: return true;
713 case JOptionPane.NO_OPTION: return false;
714 case JOptionPane.CLOSED_OPTION: return false;
715 case JOptionPane.CANCEL_OPTION: throw new AddAbortException();
716 }
717 // should not happen
718 return false;
719 }
720
721 protected void warnOfCircularReferences(OsmPrimitive primitive) {
722 String msg = tr("<html>You are trying to add a relation to itself.<br>"
723 + "<br>"
724 + "This creates circular references and is therefore discouraged.<br>"
725 + "Skipping relation ''{0}''.</html>",
726 primitive.getDisplayName(DefaultNameFormatter.getInstance())
727 );
728 JOptionPane.showMessageDialog(
729 Main.parent,
730 msg,
731 tr("Warning"),
732 JOptionPane.WARNING_MESSAGE
733 );
734 }
735
736 protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
737 if (primitives == null || primitives.isEmpty())
738 return primitives;
739 ArrayList<OsmPrimitive> ret = new ArrayList<OsmPrimitive>();
740 Iterator<OsmPrimitive> it = primitives.iterator();
741 while(it.hasNext()) {
742 OsmPrimitive primitive = it.next();
743 if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) {
744 warnOfCircularReferences(primitive);
745 continue;
746 }
747 if (isPotentialDuplicate(primitive)) {
748 if (confirmAddingPrimtive(primitive)) {
749 ret.add(primitive);
750 }
751 continue;
752 } else {
753 ret.add(primitive);
754 }
755 }
756 return ret;
757 }
758 }
759
760 class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener {
761 public AddSelectedAtStartAction() {
762 putValue(SHORT_DESCRIPTION,
763 tr("Add all primitives selected in the current dataset before the first member"));
764 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
765 // putValue(NAME, tr("Add Selected"));
766 refreshEnabled();
767 }
768
769 protected void refreshEnabled() {
770 setEnabled(selectionTableModel.getRowCount() > 0 && memberTableModel.getRowCount() > 0);
771 }
772
773 public void actionPerformed(ActionEvent e) {
774 try {
775 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
776 memberTableModel.addMembersAtBeginning(toAdd);
777 } catch(AddAbortException ex) {
778 // do nothing
779 }
780 }
781
782 public void tableChanged(TableModelEvent e) {
783 refreshEnabled();
784 }
785 }
786
787 class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener {
788 public AddSelectedAtEndAction() {
789 putValue(SHORT_DESCRIPTION, tr("Add all primitives selected in the current dataset after the last member"));
790 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
791 // putValue(NAME, tr("Add Selected"));
792 refreshEnabled();
793 }
794
795 protected void refreshEnabled() {
796 setEnabled(selectionTableModel.getRowCount() > 0);
797 }
798
799 public void actionPerformed(ActionEvent e) {
800 try {
801 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
802 memberTableModel.addMembersAtEnd(toAdd);
803 } catch(AddAbortException ex) {
804 // do nothing
805 }
806 }
807
808 public void tableChanged(TableModelEvent e) {
809 refreshEnabled();
810 }
811 }
812
813 class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
814 public AddSelectedBeforeSelection() {
815 putValue(SHORT_DESCRIPTION,
816 tr("Add all primitives selected in the current dataset before the first selected member"));
817 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
818 // putValue(NAME, tr("Add Selected"));
819 refreshEnabled();
820 }
821
822 protected void refreshEnabled() {
823 setEnabled(selectionTableModel.getRowCount() > 0
824 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
825 }
826
827 public void actionPerformed(ActionEvent e) {
828 try {
829 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
830 memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel
831 .getSelectionModel().getMinSelectionIndex());
832 } catch(AddAbortException ex) {
833 // do nothing
834 }
835
836
837 }
838
839 public void tableChanged(TableModelEvent e) {
840 refreshEnabled();
841 }
842
843 public void valueChanged(ListSelectionEvent e) {
844 refreshEnabled();
845 }
846 }
847
848 class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
849 public AddSelectedAfterSelection() {
850 putValue(SHORT_DESCRIPTION,
851 tr("Add all primitives selected in the current dataset after the last selected member"));
852 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
853 // putValue(NAME, tr("Add Selected"));
854 refreshEnabled();
855 }
856
857 protected void refreshEnabled() {
858 setEnabled(selectionTableModel.getRowCount() > 0
859 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
860 }
861
862 public void actionPerformed(ActionEvent e) {
863 try {
864 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
865 memberTableModel.addMembersAfterIdx(toAdd, memberTableModel
866 .getSelectionModel().getMaxSelectionIndex());
867 } catch(AddAbortException ex) {
868 // do nothing
869 }
870 }
871
872 public void tableChanged(TableModelEvent e) {
873 refreshEnabled();
874 }
875
876 public void valueChanged(ListSelectionEvent e) {
877 refreshEnabled();
878 }
879 }
880
881 class RemoveSelectedAction extends AbstractAction implements TableModelListener {
882 public RemoveSelectedAction() {
883 putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected primitives"));
884 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
885 // putValue(NAME, tr("Remove Selected"));
886 Shortcut.registerShortcut("relationeditor:removeselected", tr("Relation Editor: Remove Selected"),
887 KeyEvent.VK_S, Shortcut.GROUP_MNEMONIC);
888
889 updateEnabledState();
890 }
891
892 protected void updateEnabledState() {
893 DataSet ds = getLayer().data;
894 if (ds == null || ds.getSelected().isEmpty()) {
895 setEnabled(false);
896 return;
897 }
898 // only enable the action if we have members referring to the
899 // selected primitives
900 //
901 setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
902 }
903
904 public void actionPerformed(ActionEvent e) {
905 memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
906 }
907
908 public void tableChanged(TableModelEvent e) {
909 updateEnabledState();
910 }
911 }
912
913 /**
914 * Selects members in the relation editor which refer to primitives in the current
915 * selection of the context layer.
916 *
917 */
918 class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener {
919 public SelectedMembersForSelectionAction() {
920 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to primitives in the current selection"));
921 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
922 updateEnabledState();
923 }
924
925 protected void updateEnabledState() {
926 boolean enabled = selectionTableModel.getRowCount() > 0
927 && !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty();
928
929 if (enabled) {
930 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} primitives in the current selection",memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size()));
931 } else {
932 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to primitives in the current selection"));
933 }
934 setEnabled(enabled);
935 }
936
937 public void actionPerformed(ActionEvent e) {
938 memberTableModel.selectMembersReferringTo(getLayer().data.getSelected());
939 }
940
941 public void tableChanged(TableModelEvent e) {
942 updateEnabledState();
943
944 }
945 }
946
947 /**
948 * Selects primitives in the layer this editor belongs to. The selected primitives are
949 * equal to the set of primitives the currently selected relation members refer to.
950 *
951 */
952 class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener {
953 public SelectPrimitivesForSelectedMembersAction() {
954 putValue(SHORT_DESCRIPTION, tr("Select primitives for selected relation members"));
955 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
956 updateEnabledState();
957 }
958
959 protected void updateEnabledState() {
960 setEnabled(memberTable.getSelectedRowCount() > 0);
961 }
962
963 public void actionPerformed(ActionEvent e) {
964 getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives());
965 DataSet.fireSelectionChanged(getLayer().data.getSelected());
966 }
967
968 public void valueChanged(ListSelectionEvent e) {
969 updateEnabledState();
970 }
971 }
972
973 class SortAction extends AbstractAction {
974 public SortAction() {
975 putValue(SHORT_DESCRIPTION, tr("Sort the relation members"));
976 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
977 // putValue(NAME, tr("Sort"));
978 Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"), KeyEvent.VK_T,
979 Shortcut.GROUP_MNEMONIC);
980 //setEnabled(false);
981 }
982
983 public void actionPerformed(ActionEvent e) {
984 memberTableModel.sort();
985 }
986 }
987
988 class MoveUpAction extends AbstractAction implements ListSelectionListener {
989 public MoveUpAction() {
990 putValue(SHORT_DESCRIPTION, tr("Move the currently selected members up"));
991 putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
992 // putValue(NAME, tr("Move Up"));
993 Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"), KeyEvent.VK_N,
994 Shortcut.GROUP_MNEMONIC);
995 setEnabled(false);
996 }
997
998 public void actionPerformed(ActionEvent e) {
999 memberTableModel.moveUp(memberTable.getSelectedRows());
1000 }
1001
1002 public void valueChanged(ListSelectionEvent e) {
1003 setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
1004 }
1005 }
1006
1007 class MoveDownAction extends AbstractAction implements ListSelectionListener {
1008 public MoveDownAction() {
1009 putValue(SHORT_DESCRIPTION, tr("Move the currently selected members down"));
1010 putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
1011 // putValue(NAME, tr("Move Down"));
1012 Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Down"), KeyEvent.VK_J,
1013 Shortcut.GROUP_MNEMONIC);
1014 setEnabled(false);
1015 }
1016
1017 public void actionPerformed(ActionEvent e) {
1018 memberTableModel.moveDown(memberTable.getSelectedRows());
1019 }
1020
1021 public void valueChanged(ListSelectionEvent e) {
1022 setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
1023 }
1024 }
1025
1026 class RemoveAction extends AbstractAction implements ListSelectionListener {
1027 public RemoveAction() {
1028 putValue(SHORT_DESCRIPTION, tr("Remove the currently selected members from this relation"));
1029 putValue(SMALL_ICON, ImageProvider.get("dialogs", "remove"));
1030 // putValue(NAME, tr("Remove"));
1031 Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"), KeyEvent.VK_J,
1032 Shortcut.GROUP_MNEMONIC);
1033 setEnabled(false);
1034 }
1035
1036 public void actionPerformed(ActionEvent e) {
1037 memberTableModel.remove(memberTable.getSelectedRows());
1038 }
1039
1040 public void valueChanged(ListSelectionEvent e) {
1041 setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
1042 }
1043 }
1044
1045 class DeleteCurrentRelationAction extends AbstractAction {
1046 public DeleteCurrentRelationAction() {
1047 putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
1048 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1049 putValue(NAME, tr("Delete"));
1050 updateEnabledState();
1051 }
1052
1053 public void run() {
1054 Relation toDelete = getRelation();
1055 if (toDelete == null)
1056 return;
1057 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
1058 getLayer(),
1059 toDelete
1060 );
1061 }
1062
1063 public void actionPerformed(ActionEvent e) {
1064 run();
1065 }
1066
1067 protected void updateEnabledState() {
1068 setEnabled(getRelation() != null);
1069 }
1070 }
1071
1072 abstract class SavingAction extends AbstractAction {
1073 /**
1074 * apply updates to a new relation
1075 */
1076 protected void applyNewRelation() {
1077 // If the user wanted to create a new relation, but hasn't added any members or
1078 // tags, don't add an empty relation
1079 if (memberTableModel.getRowCount() == 0 && tagEditorModel.getKeys().isEmpty())
1080 return;
1081 Relation newRelation = new Relation();
1082 tagEditorModel.applyToPrimitive(newRelation);
1083 memberTableModel.applyToRelation(newRelation);
1084 Main.main.undoRedo.add(new AddCommand(getLayer(),newRelation));
1085
1086 // make sure everybody is notified about the changes
1087 //
1088 DataSet.fireSelectionChanged(getLayer().data.getSelected());
1089 getLayer().fireDataChange();
1090 GenericRelationEditor.this.setRelation(newRelation);
1091 RelationDialogManager.getRelationDialogManager().updateContext(
1092 getLayer(),
1093 getRelation(),
1094 GenericRelationEditor.this
1095 );
1096 }
1097
1098 /**
1099 * Apply the updates for an existing relation which has not been changed
1100 * outside of the relation editor.
1101 *
1102 */
1103 protected void applyExistingConflictingRelation() {
1104 Relation editedRelation = new Relation(getRelation());
1105 tagEditorModel.applyToPrimitive(editedRelation);
1106 memberTableModel.applyToRelation(editedRelation);
1107 Conflict<Relation> conflict = new Conflict<Relation>(getRelation(), editedRelation);
1108 Main.main.undoRedo.add(new ConflictAddCommand(getLayer(),conflict));
1109 }
1110
1111 /**
1112 * Apply the updates for an existing relation which has been changed
1113 * outside of the relation editor.
1114 *
1115 */
1116 protected void applyExistingNonConflictingRelation() {
1117 Relation editedRelation = new Relation(getRelation());
1118 tagEditorModel.applyToPrimitive(editedRelation);
1119 memberTableModel.applyToRelation(editedRelation);
1120 Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation));
1121 DataSet.fireSelectionChanged(getLayer().data.getSelected());
1122 getLayer().fireDataChange();
1123 // this will refresh the snapshot and update the dialog title
1124 //
1125 setRelation(getRelation());
1126 }
1127
1128 protected boolean confirmClosingBecauseOfDirtyState() {
1129 String [] options = new String[] {
1130 tr("Yes, create a conflict and close"),
1131 tr("No, continue editing")
1132 };
1133 int ret = JOptionPane.showOptionDialog(
1134 Main.parent,
1135 tr("<html>This relation has been changed outside of the editor.<br>"
1136 + "You can't apply your changes and continue editing.<br>"
1137 + "<br>"
1138 + "Do you want to create a conflict and close the editor?</html>"),
1139 tr("Conflict in data"),
1140 JOptionPane.YES_NO_OPTION,
1141 JOptionPane.WARNING_MESSAGE,
1142 null,
1143 options,
1144 options[0]
1145 );
1146 switch(ret) {
1147 case JOptionPane.CANCEL_OPTION: return false;
1148 case JOptionPane.YES_OPTION: return true;
1149 case JOptionPane.NO_OPTION: return false;
1150 }
1151 return false;
1152 }
1153
1154 protected void warnDoubleConflict() {
1155 JOptionPane.showMessageDialog(
1156 Main.parent,
1157 tr("<html>Layer ''{0}'' already has a conflict for primitive<br>"
1158 + "''{1}''.<br>"
1159 + "Please resolve this conflict first, then try again.</html>",
1160 getLayer().getName(),
1161 getRelation().getDisplayName(DefaultNameFormatter.getInstance())
1162 ),
1163 tr("Double conflict"),
1164 JOptionPane.WARNING_MESSAGE
1165 );
1166 }
1167 }
1168
1169 class ApplyAction extends SavingAction {
1170 public ApplyAction() {
1171 putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
1172 putValue(SMALL_ICON, ImageProvider.get("save"));
1173 putValue(NAME, tr("Apply"));
1174 setEnabled(true);
1175 }
1176
1177 public void run() {
1178 if (getRelation() == null) {
1179 applyNewRelation();
1180 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1181 || tagEditorModel.isDirty()) {
1182 if (isDirtyRelation()) {
1183 if (confirmClosingBecauseOfDirtyState()) {
1184 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1185 warnDoubleConflict();
1186 return;
1187 }
1188 applyExistingConflictingRelation();
1189 setVisible(false);
1190 }
1191 } else {
1192 applyExistingNonConflictingRelation();
1193 }
1194 }
1195 }
1196
1197 public void actionPerformed(ActionEvent e) {
1198 run();
1199 }
1200 }
1201
1202 class OKAction extends SavingAction {
1203 public OKAction() {
1204 putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
1205 putValue(SMALL_ICON, ImageProvider.get("ok"));
1206 putValue(NAME, tr("OK"));
1207 setEnabled(true);
1208 }
1209
1210 public void run() {
1211 if (getRelation() == null) {
1212 applyNewRelation();
1213 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
1214 || tagEditorModel.isDirty()) {
1215 if (isDirtyRelation()) {
1216 if (confirmClosingBecauseOfDirtyState()) {
1217 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
1218 warnDoubleConflict();
1219 return;
1220 }
1221 applyExistingConflictingRelation();
1222 } else
1223 return;
1224 } else {
1225 applyExistingNonConflictingRelation();
1226 }
1227 }
1228 setVisible(false);
1229 }
1230
1231 public void actionPerformed(ActionEvent e) {
1232 run();
1233 }
1234 }
1235
1236 class CancelAction extends AbstractAction {
1237 public CancelAction() {
1238 putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
1239 putValue(SMALL_ICON, ImageProvider.get("cancel"));
1240 putValue(NAME, tr("Cancel"));
1241
1242 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
1243 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
1244 getRootPane().getActionMap().put("ESCAPE", this);
1245 setEnabled(true);
1246 }
1247
1248 public void actionPerformed(ActionEvent e) {
1249 setVisible(false);
1250 }
1251 }
1252
1253 class AddTagAction extends AbstractAction {
1254 public AddTagAction() {
1255 putValue(SHORT_DESCRIPTION, tr("Add an empty tag"));
1256 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1257 // putValue(NAME, tr("Cancel"));
1258 setEnabled(true);
1259 }
1260
1261 public void actionPerformed(ActionEvent e) {
1262 tagEditorModel.appendNewTag();
1263 }
1264 }
1265
1266 class DeleteTagAction extends AbstractAction implements ListSelectionListener {
1267 public DeleteTagAction() {
1268 putValue(SHORT_DESCRIPTION, tr("Delete the currently selected tags"));
1269 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1270 // putValue(NAME, tr("Cancel"));
1271 refreshEnabled();
1272 }
1273
1274 public void actionPerformed(ActionEvent e) {
1275 run();
1276 }
1277
1278 /**
1279 * delete a selection of tag names
1280 */
1281 protected void deleteTagNames() {
1282 int[] rows = tagTable.getSelectedRows();
1283 tagEditorModel.deleteTagNames(rows);
1284 }
1285
1286 /**
1287 * delete a selection of tag values
1288 */
1289 protected void deleteTagValues() {
1290 int[] rows = tagTable.getSelectedRows();
1291 tagEditorModel.deleteTagValues(rows);
1292 }
1293
1294 /**
1295 * delete a selection of tags
1296 */
1297 protected void deleteTags() {
1298 tagEditorModel.deleteTags(tagTable.getSelectedRows());
1299 }
1300
1301 public void run() {
1302 if (!isEnabled())
1303 return;
1304 if (tagTable.getSelectedColumnCount() == 1) {
1305 if (tagTable.getSelectedColumn() == 0) {
1306 deleteTagNames();
1307 } else if (tagTable.getSelectedColumn() == 1) {
1308 deleteTagValues();
1309 } else
1310 // should not happen
1311 //
1312 throw new IllegalStateException("unexpected selected clolumn: getSelectedColumn() is "
1313 + tagTable.getSelectedColumn());
1314 } else if (tagTable.getSelectedColumnCount() == 2) {
1315 deleteTags();
1316 }
1317 if (tagEditorModel.getRowCount() == 0) {
1318 tagEditorModel.ensureOneTag();
1319 }
1320 }
1321
1322 protected void refreshEnabled() {
1323 setEnabled(tagTable.getSelectedRowCount() > 0 || tagTable.getSelectedColumnCount() > 0);
1324 }
1325
1326 public void valueChanged(ListSelectionEvent e) {
1327 refreshEnabled();
1328 }
1329 }
1330
1331 class DownlaodAction extends AbstractAction {
1332 public DownlaodAction() {
1333 putValue(SHORT_DESCRIPTION, tr("Download all incomplete ways and nodes in relation"));
1334 putValue(SMALL_ICON, ImageProvider.get("dialogs", "downloadincomplete"));
1335 putValue(NAME, tr("Download Members"));
1336 Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
1337 KeyEvent.VK_K, Shortcut.GROUP_MNEMONIC);
1338 updateEnabledState();
1339 }
1340
1341 public void actionPerformed(ActionEvent e) {
1342 if (!isEnabled())
1343 return;
1344 Main.worker.submit(new DownloadTask(GenericRelationEditor.this));
1345 }
1346
1347 protected void updateEnabledState() {
1348 setEnabled(getRelation() != null && getRelation().getId() > 0);
1349 }
1350 }
1351
1352 class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener {
1353 public SetRoleAction() {
1354 putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
1355 // FIXME: find better icon
1356 putValue(SMALL_ICON, ImageProvider.get("ok"));
1357 putValue(NAME, tr("Apply Role"));
1358 refreshEnabled();
1359 }
1360
1361 protected void refreshEnabled() {
1362 setEnabled(memberTable.getSelectedRowCount() > 0);
1363 }
1364
1365 protected boolean isEmptyRole() {
1366 return tfRole.getText() == null || tfRole.getText().trim().equals("");
1367 }
1368
1369 protected boolean confirmSettingEmptyRole(int onNumMembers) {
1370 String message = tr("<html>You are setting an empty role on {0} primitives.<br>"
1371 + "This is equal to deleting the roles of these primitives.<br>"
1372 + "Do you really want to apply the new role?</html>", onNumMembers);
1373 String [] options = new String[] {
1374 tr("Yes, apply it"),
1375 tr("No, don't apply")
1376 };
1377 int ret = ConditionalOptionPaneUtil.showOptionDialog(
1378 "relation_editor.confirm_applying_empty_role",
1379 Main.parent,
1380 message,
1381 tr("Confirm empty role"),
1382 JOptionPane.YES_NO_OPTION,
1383 JOptionPane.WARNING_MESSAGE,
1384 options,
1385 options[0]
1386 );
1387 switch(ret) {
1388 case JOptionPane.YES_OPTION: return true;
1389 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return true;
1390 default:
1391 return false;
1392 }
1393 }
1394
1395 public void actionPerformed(ActionEvent e) {
1396 if (isEmptyRole()) {
1397 if (! confirmSettingEmptyRole(memberTable.getSelectedRowCount()))
1398 return;
1399 }
1400 memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
1401 }
1402
1403 public void valueChanged(ListSelectionEvent e) {
1404 refreshEnabled();
1405 }
1406
1407 public void changedUpdate(DocumentEvent e) {
1408 refreshEnabled();
1409 }
1410
1411 public void insertUpdate(DocumentEvent e) {
1412 refreshEnabled();
1413 }
1414
1415 public void removeUpdate(DocumentEvent e) {
1416 refreshEnabled();
1417 }
1418 }
1419
1420 /**
1421 * Creates a new relation with a copy of the current editor state
1422 *
1423 */
1424 class DuplicateRelationAction extends AbstractAction {
1425 public DuplicateRelationAction() {
1426 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
1427 // FIXME provide an icon
1428 putValue(SMALL_ICON, ImageProvider.get("duplicate"));
1429 putValue(NAME, tr("Duplicate"));
1430 setEnabled(true);
1431 }
1432
1433 public void actionPerformed(ActionEvent e) {
1434 Relation copy = new Relation();
1435 tagEditorModel.applyToPrimitive(copy);
1436 memberTableModel.applyToRelation(copy);
1437 RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers());
1438 editor.setVisible(true);
1439 }
1440 }
1441
1442 /**
1443 * Action for editing the currently selected relation
1444 *
1445 *
1446 */
1447 class EditAction extends AbstractAction implements ListSelectionListener {
1448 public EditAction() {
1449 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
1450 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1451 //putValue(NAME, tr("Edit"));
1452 refreshEnabled();
1453 }
1454
1455 protected void refreshEnabled() {
1456 setEnabled(memberTable.getSelectedRowCount() == 1
1457 && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
1458 }
1459
1460 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
1461 Collection<RelationMember> members = new HashSet<RelationMember>();
1462 Collection<OsmPrimitive> selection = getLayer().data.getSelected();
1463 for (RelationMember member: r.getMembers()) {
1464 if (selection.contains(member.getMember())) {
1465 members.add(member);
1466 }
1467 }
1468 return members;
1469 }
1470
1471 public void run() {
1472 int idx = memberTable.getSelectedRow();
1473 if (idx < 0)
1474 return;
1475 OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
1476 if (!(primitive instanceof Relation))
1477 return;
1478 Relation r = (Relation) primitive;
1479 if (r.incomplete)
1480 return;
1481
1482 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r));
1483 editor.setVisible(true);
1484 }
1485
1486 public void actionPerformed(ActionEvent e) {
1487 if (!isEnabled())
1488 return;
1489 run();
1490 }
1491
1492 public void valueChanged(ListSelectionEvent e) {
1493 refreshEnabled();
1494 }
1495 }
1496
1497 class MemberTableDblClickAdapter extends MouseAdapter {
1498 @Override
1499 public void mouseClicked(MouseEvent e) {
1500 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
1501 new EditAction().run();
1502 }
1503 }
1504 }
1505
1506 /**
1507 * The asynchronous task for downloading relation members.
1508 *
1509 */
1510 class DownloadTask extends PleaseWaitRunnable {
1511 private boolean cancelled;
1512 private int conflictsCount;
1513 private Exception lastException;
1514
1515 public DownloadTask(Dialog parent) {
1516 super(tr("Download relation members"), new PleaseWaitProgressMonitor(parent), false /*
1517 * don't
1518 * ignore
1519 * exception
1520 */);
1521 }
1522
1523 @Override
1524 protected void cancel() {
1525 cancelled = true;
1526 OsmApi.getOsmApi().cancel();
1527 }
1528
1529 @Override
1530 protected void finish() {
1531 if (cancelled)
1532 return;
1533 memberTableModel.updateMemberReferences(getLayer().data);
1534 if (lastException != null) {
1535 ExceptionDialogUtil.explainException(lastException);
1536 }
1537
1538 if (conflictsCount > 0) {
1539 JOptionPane.showMessageDialog(
1540 Main.parent,
1541 tr("There were {0} conflicts during import.", conflictsCount),
1542 tr("Warning"),
1543 JOptionPane.WARNING_MESSAGE
1544 );
1545 }
1546 }
1547
1548 @Override
1549 protected void realRun() throws SAXException, IOException, OsmTransferException {
1550 try {
1551 progressMonitor.indeterminateSubTask("");
1552 OsmServerObjectReader reader = new OsmServerObjectReader(getRelation().getId(), OsmPrimitiveType.RELATION,
1553 true);
1554 DataSet dataSet = reader.parseOsm(progressMonitor
1555 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
1556 if (dataSet != null) {
1557 final MergeVisitor visitor = new MergeVisitor(getLayer().data, dataSet);
1558 visitor.merge();
1559
1560 // copy the merged layer's data source info
1561 for (DataSource src : dataSet.dataSources) {
1562 getLayer().data.dataSources.add(src);
1563 }
1564 // FIXME: this is necessary because there are dialogs listening
1565 // for DataChangeEvents which manipulate Swing components on this
1566 // thread.
1567 //
1568 SwingUtilities.invokeLater(new Runnable() {
1569 public void run() {
1570 getLayer().fireDataChange();
1571 }
1572 });
1573 if (!visitor.getConflicts().isEmpty()) {
1574 getLayer().getConflicts().add(visitor.getConflicts());
1575 conflictsCount = visitor.getConflicts().size();
1576 }
1577 }
1578 } catch (Exception e) {
1579 if (cancelled) {
1580 System.out.println(tr("Warning: ignoring exception because task is cancelled. Exception: {0}", e
1581 .toString()));
1582 return;
1583 }
1584 lastException = e;
1585 }
1586 }
1587 }
1588}
Note: See TracBrowser for help on using the repository browser.