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

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

removed all occurences of setAlwaysOnTop(). See http://josm.openstreetmap.de/wiki/JosmWinMgtDemoApplication

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