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

Last change on this file since 1922 was 1922, checked in by Gubaer, 16 years ago

fixed #3207 again: Highlighting members in relation editor is broken

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