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

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

fixed #3925: Download relation members takes a long time to return

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