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

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

fixed #3041: Relation Editor: Provide action for zooming to a particular member

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