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

Last change on this file since 2434 was 2434, checked in by Gubaer, 14 years ago

Improved detection of modified state in OsmDataLayer.

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