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

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

fixed #3156 : Download members should always download the members
fixed #3155 : Download members should be disabled if editing a new relation

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