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

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

fixed #3143: Apply role should be enabled for empty role

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