001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.dialogs;
003
004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005 import static org.openstreetmap.josm.tools.I18n.tr;
006 import static org.openstreetmap.josm.tools.I18n.trn;
007
008 import java.awt.Point;
009 import java.awt.event.ActionEvent;
010 import java.awt.event.KeyEvent;
011 import java.awt.event.MouseAdapter;
012 import java.awt.event.MouseEvent;
013 import java.util.ArrayList;
014 import java.util.Arrays;
015 import java.util.Collection;
016 import java.util.Collections;
017 import java.util.HashSet;
018 import java.util.Iterator;
019 import java.util.LinkedList;
020 import java.util.List;
021 import java.util.Set;
022
023 import javax.swing.AbstractAction;
024 import javax.swing.AbstractListModel;
025 import javax.swing.Action;
026 import javax.swing.DefaultListSelectionModel;
027 import javax.swing.JComponent;
028 import javax.swing.JList;
029 import javax.swing.JMenuItem;
030 import javax.swing.KeyStroke;
031 import javax.swing.ListSelectionModel;
032 import javax.swing.SwingUtilities;
033 import javax.swing.event.ListSelectionEvent;
034 import javax.swing.event.ListSelectionListener;
035 import javax.swing.event.PopupMenuListener;
036
037 import org.openstreetmap.josm.Main;
038 import org.openstreetmap.josm.command.Command;
039 import org.openstreetmap.josm.command.SequenceCommand;
040 import org.openstreetmap.josm.data.SelectionChangedListener;
041 import org.openstreetmap.josm.data.osm.DataSet;
042 import org.openstreetmap.josm.data.osm.OsmPrimitive;
043 import org.openstreetmap.josm.data.osm.Relation;
044 import org.openstreetmap.josm.data.osm.RelationMember;
045 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
046 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
047 import org.openstreetmap.josm.data.osm.event.DataSetListener;
048 import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
049 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
050 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
051 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
052 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
053 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
054 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
055 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
056 import org.openstreetmap.josm.gui.DefaultNameFormatter;
057 import org.openstreetmap.josm.gui.MapView;
058 import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
059 import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
060 import org.openstreetmap.josm.gui.SideButton;
061 import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
062 import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationTask;
063 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor;
064 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
065 import org.openstreetmap.josm.gui.layer.Layer;
066 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
067 import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
068 import org.openstreetmap.josm.tools.ImageProvider;
069 import org.openstreetmap.josm.tools.InputMapUtils;
070 import org.openstreetmap.josm.tools.Shortcut;
071
072 /**
073 * A dialog showing all known relations, with buttons to add, edit, and
074 * delete them.
075 *
076 * We don't have such dialogs for nodes, segments, and ways, because those
077 * objects are visible on the map and can be selected there. Relations are not.
078 */
079 public class RelationListDialog extends ToggleDialog implements DataSetListener {
080 /** The display list. */
081 private JList displaylist;
082 /** the list model used */
083 private RelationListModel model;
084
085 /** the edit action */
086 private EditAction editAction;
087 /** the delete action */
088 private DeleteAction deleteAction;
089 private NewAction newAction;
090 private AddToRelation addToRelation;
091 /** the popup menu */
092 private RelationDialogPopupMenu popupMenu;
093
094 /**
095 * constructor
096 */
097 public RelationListDialog() {
098 super(tr("Relations"), "relationlist", tr("Open a list of all relations."),
099 Shortcut.registerShortcut("subwindow:relations", tr("Toggle: {0}", tr("Relations")),
100 KeyEvent.VK_R, Shortcut.ALT_SHIFT), 150);
101
102 // create the list of relations
103 //
104 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
105 model = new RelationListModel(selectionModel);
106 displaylist = new JList(model);
107 displaylist.setSelectionModel(selectionModel);
108 displaylist.setCellRenderer(new OsmPrimitivRenderer() {
109 /**
110 * Don't show the default tooltip in the relation list.
111 */
112 @Override
113 protected String getComponentToolTipText(OsmPrimitive value) {
114 return null;
115 }
116 });
117 displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
118 displaylist.addMouseListener(new MouseEventHandler());
119
120 // the new action
121 //
122 newAction = new NewAction();
123
124 // the edit action
125 //
126 editAction = new EditAction();
127 displaylist.addListSelectionListener(editAction);
128
129 // the duplicate action
130 //
131 DuplicateAction duplicateAction = new DuplicateAction();
132 displaylist.addListSelectionListener(duplicateAction);
133
134 // the delete action
135 //
136 deleteAction = new DeleteAction();
137 displaylist.addListSelectionListener(deleteAction);
138
139 // the select action
140 //
141 SelectAction selectAction = new SelectAction(false);
142 displaylist.addListSelectionListener(selectAction);
143
144 createLayout(displaylist, true, Arrays.asList(new SideButton[] {
145 new SideButton(newAction, false),
146 new SideButton(editAction, false),
147 new SideButton(duplicateAction, false),
148 new SideButton(deleteAction, false),
149 new SideButton(selectAction, false)
150 }));
151
152 // activate DEL in the list of relations
153 //displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "deleteRelation");
154 //displaylist.getActionMap().put("deleteRelation", deleteAction);
155
156 InputMapUtils.unassignCtrlShiftUpDown(displaylist, JComponent.WHEN_FOCUSED);
157
158 // Select relation on Ctrl-Enter
159 InputMapUtils.addEnterAction(displaylist, selectAction);
160
161 addToRelation = new AddToRelation();
162 popupMenu = new RelationDialogPopupMenu(displaylist);
163
164 // Edit relation on Ctrl-Enter
165 displaylist.getActionMap().put("edit", editAction);
166 displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK), "edit");
167 }
168
169 @Override public void showNotify() {
170 MapView.addLayerChangeListener(newAction);
171 newAction.updateEnabledState();
172 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT);
173 DataSet.addSelectionListener(addToRelation);
174 dataChanged(null);
175 }
176
177 @Override public void hideNotify() {
178 MapView.removeLayerChangeListener(newAction);
179 DatasetEventManager.getInstance().removeDatasetListener(this);
180 DataSet.removeSelectionListener(addToRelation);
181 }
182
183 /**
184 * Initializes the relation list dialog from a layer. If <code>layer</code> is null
185 * or if it isn't an {@link OsmDataLayer} the dialog is reset to an empty dialog.
186 * Otherwise it is initialized with the list of non-deleted and visible relations
187 * in the layer's dataset.
188 *
189 * @param layer the layer. May be null.
190 */
191 protected void initFromLayer(Layer layer) {
192 if (layer == null || ! (layer instanceof OsmDataLayer)) {
193 model.setRelations(null);
194 return;
195 }
196 OsmDataLayer l = (OsmDataLayer)layer;
197 model.setRelations(l.data.getRelations());
198 model.updateTitle();
199 }
200
201 /**
202 * Adds a selection listener to the relation list.
203 *
204 * @param listener the listener to add
205 */
206 public void addListSelectionListener(ListSelectionListener listener) {
207 displaylist.addListSelectionListener(listener);
208 }
209
210 /**
211 * Removes a selection listener from the relation list.
212 *
213 * @param listener the listener to remove
214 */
215 public void removeListSelectionListener(ListSelectionListener listener) {
216 displaylist.removeListSelectionListener(listener);
217 }
218
219 /**
220 * @return The selected relation in the list
221 */
222 private Relation getSelected() {
223 if(model.getSize() == 1) {
224 displaylist.setSelectedIndex(0);
225 }
226 return (Relation) displaylist.getSelectedValue();
227 }
228
229 /**
230 * Selects the relation <code>relation</code> in the list of relations.
231 *
232 * @param relation the relation
233 */
234 public void selectRelation(Relation relation) {
235 selectRelations(Collections.singleton(relation));
236 }
237
238 /**
239 * Selects the relations in the list of relations.
240 * @param relations the relations to be selected
241 */
242 public void selectRelations(Collection<Relation> relations) {
243 if (relations == null || relations.isEmpty()) {
244 model.setSelectedRelations(null);
245 } else {
246 model.setSelectedRelations(relations);
247 Integer i = model.getRelationIndex(relations.iterator().next());
248 if (i != null) { // Not all relations have to be in the list (for example when the relation list is hidden, it's not updated with new relations)
249 displaylist.scrollRectToVisible(displaylist.getCellBounds(i, i));
250 }
251 }
252 }
253
254 class MouseEventHandler extends MouseAdapter {
255 protected void setCurrentRelationAsSelection() {
256 Main.main.getCurrentDataSet().setSelected((Relation)displaylist.getSelectedValue());
257 }
258
259 protected void editCurrentRelation() {
260 new EditAction().launchEditor(getSelected());
261 }
262
263 @Override public void mouseClicked(MouseEvent e) {
264 if (Main.main.getEditLayer() == null) return;
265 if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
266 if (e.isControlDown()) {
267 editCurrentRelation();
268 } else {
269 setCurrentRelationAsSelection();
270 }
271 }
272 }
273 private void openPopup(MouseEvent e) {
274 Point p = e.getPoint();
275 int index = displaylist.locationToIndex(p);
276 if (index < 0) return;
277 if (!displaylist.getCellBounds(index, index).contains(e.getPoint()))
278 return;
279 if (! displaylist.isSelectedIndex(index)) {
280 displaylist.setSelectedIndex(index);
281 }
282 popupMenu.show(displaylist, p.x, p.y-3);
283 }
284 @Override public void mousePressed(MouseEvent e) {
285 if (Main.main.getEditLayer() == null) return;
286 if (e.isPopupTrigger()) {
287 openPopup(e);
288 }
289 }
290 @Override public void mouseReleased(MouseEvent e) {
291 if (Main.main.getEditLayer() == null) return;
292 if (e.isPopupTrigger()) {
293 openPopup(e);
294 }
295 }
296 }
297
298 /**
299 * The edit action
300 *
301 */
302 class EditAction extends AbstractAction implements ListSelectionListener{
303 public EditAction() {
304 putValue(SHORT_DESCRIPTION,tr( "Open an editor for the selected relation"));
305 putValue(NAME, tr("Edit"));
306 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
307 setEnabled(false);
308 }
309 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
310 Collection<RelationMember> members = new HashSet<RelationMember>();
311 Collection<OsmPrimitive> selection = Main.map.mapView.getEditLayer().data.getSelected();
312 for (RelationMember member: r.getMembers()) {
313 if (selection.contains(member.getMember())) {
314 members.add(member);
315 }
316 }
317 return members;
318 }
319
320 public void launchEditor(Relation toEdit) {
321 if (toEdit == null)
322 return;
323 RelationEditor.getEditor(Main.map.mapView.getEditLayer(),toEdit, getMembersForCurrentSelection(toEdit)).setVisible(true);
324 }
325
326 public void actionPerformed(ActionEvent e) {
327 if (!isEnabled())
328 return;
329 launchEditor(getSelected());
330 }
331
332 public void valueChanged(ListSelectionEvent e) {
333 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1);
334 }
335 }
336
337 /**
338 * The delete action
339 *
340 */
341 class DeleteAction extends AbstractAction implements ListSelectionListener {
342 class AbortException extends Exception {}
343
344 public DeleteAction() {
345 putValue(SHORT_DESCRIPTION,tr("Delete the selected relation"));
346 putValue(NAME, tr("Delete"));
347 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
348 setEnabled(false);
349 }
350
351 protected void deleteRelation(Relation toDelete) {
352 if (toDelete == null)
353 return;
354 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
355 Main.main.getEditLayer(),
356 toDelete
357 );
358 }
359
360 public void actionPerformed(ActionEvent e) {
361 if (!isEnabled())
362 return;
363 List<Relation> toDelete = new LinkedList<Relation>();
364 for (int i : displaylist.getSelectedIndices()) {
365 toDelete.add(model.getRelation(i));
366 }
367 for (Relation r : toDelete) {
368 deleteRelation(r);
369 }
370 displaylist.clearSelection();
371 }
372
373 public void valueChanged(ListSelectionEvent e) {
374 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
375 }
376 }
377
378 /**
379 * The action for creating a new relation
380 *
381 */
382 static class NewAction extends AbstractAction implements LayerChangeListener{
383 public NewAction() {
384 putValue(SHORT_DESCRIPTION,tr("Create a new relation"));
385 putValue(NAME, tr("New"));
386 putValue(SMALL_ICON, ImageProvider.get("dialogs", "addrelation"));
387 updateEnabledState();
388 }
389
390 public void run() {
391 RelationEditor.getEditor(Main.main.getEditLayer(),null, null).setVisible(true);
392 }
393
394 public void actionPerformed(ActionEvent e) {
395 run();
396 }
397
398 protected void updateEnabledState() {
399 setEnabled(Main.main != null && Main.main.getEditLayer() != null);
400 }
401
402 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
403 updateEnabledState();
404 }
405
406 public void layerAdded(Layer newLayer) {
407 updateEnabledState();
408 }
409
410 public void layerRemoved(Layer oldLayer) {
411 updateEnabledState();
412 }
413 }
414
415 /**
416 * Creates a new relation with a copy of the current editor state
417 *
418 */
419 class DuplicateAction extends AbstractAction implements ListSelectionListener {
420 public DuplicateAction() {
421 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
422 putValue(SMALL_ICON, ImageProvider.get("duplicate"));
423 putValue(NAME, tr("Duplicate"));
424 updateEnabledState();
425 }
426
427 public void launchEditorForDuplicate(Relation original) {
428 Relation copy = new Relation(original, true);
429 copy.setModified(true);
430 RelationEditor editor = RelationEditor.getEditor(
431 Main.main.getEditLayer(),
432 copy,
433 null /* no selected members */
434 );
435 editor.setVisible(true);
436 }
437
438 public void actionPerformed(ActionEvent e) {
439 if (!isEnabled())
440 return;
441 launchEditorForDuplicate(getSelected());
442 }
443
444 protected void updateEnabledState() {
445 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1);
446 }
447
448 public void valueChanged(ListSelectionEvent e) {
449 updateEnabledState();
450 }
451 }
452
453 /**
454 * Sets the current selection to the list of relations selected in this dialog
455 *
456 */
457 class SelectAction extends AbstractAction implements ListSelectionListener{
458 boolean add;
459 public SelectAction(boolean add) {
460 putValue(SHORT_DESCRIPTION, add ? tr("Add the selected relations to the current selection")
461 : tr("Set the current selection to the list of selected relations"));
462 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
463 putValue(NAME, add ? tr("Select relation (add)") : tr("Select relation"));
464 this.add = add;
465 updateEnabledState();
466 }
467
468 public void actionPerformed(ActionEvent e) {
469 if (!isEnabled()) return;
470 int [] idx = displaylist.getSelectedIndices();
471 if (idx == null || idx.length == 0) return;
472 ArrayList<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(idx.length);
473 for (int i: idx) {
474 selection.add(model.getRelation(i));
475 }
476 if(add) {
477 Main.map.mapView.getEditLayer().data.addSelected(selection);
478 } else {
479 Main.map.mapView.getEditLayer().data.setSelected(selection);
480 }
481 }
482
483 protected void updateEnabledState() {
484 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
485 }
486
487 public void valueChanged(ListSelectionEvent e) {
488 updateEnabledState();
489 }
490 }
491
492 /**
493 * Sets the current selection to the list of relations selected in this dialog
494 *
495 */
496 class SelectMembersAction extends AbstractAction implements ListSelectionListener{
497 boolean add;
498 public SelectMembersAction(boolean add) {
499 putValue(SHORT_DESCRIPTION,add ? tr("Add the members of all selected relations to current selection")
500 : tr("Select the members of all selected relations"));
501 putValue(SMALL_ICON, ImageProvider.get("selectall"));
502 putValue(NAME, add ? tr("Select members (add)") : tr("Select members"));
503 this.add = add;
504 updateEnabledState();
505 }
506
507 public void actionPerformed(ActionEvent e) {
508 if (!isEnabled()) return;
509 List<Relation> relations = model.getSelectedRelations();
510 HashSet<OsmPrimitive> members = new HashSet<OsmPrimitive>();
511 for(Relation r: relations) {
512 members.addAll(r.getMemberPrimitives());
513 }
514 if(add) {
515 Main.map.mapView.getEditLayer().data.addSelected(members);
516 } else {
517 Main.map.mapView.getEditLayer().data.setSelected(members);
518 }
519 }
520
521 protected void updateEnabledState() {
522 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
523 }
524
525 public void valueChanged(ListSelectionEvent e) {
526 updateEnabledState();
527 }
528 }
529
530 /**
531 * The action for downloading members of all selected relations
532 *
533 */
534 class DownloadMembersAction extends AbstractAction implements ListSelectionListener{
535
536 public DownloadMembersAction() {
537 putValue(SHORT_DESCRIPTION,tr("Download all members of the selected relations"));
538 putValue(NAME, tr("Download members"));
539 putValue(SMALL_ICON, ImageProvider.get("dialogs", "downloadincomplete"));
540 putValue("help", ht("/Dialog/RelationList#DownloadMembers"));
541 updateEnabledState();
542 }
543
544 protected void updateEnabledState() {
545 setEnabled(! model.getSelectedNonNewRelations().isEmpty());
546 }
547
548 public void valueChanged(ListSelectionEvent e) {
549 updateEnabledState();
550 }
551
552 public void actionPerformed(ActionEvent e) {
553 List<Relation> relations = model.getSelectedNonNewRelations();
554 if (relations.isEmpty())
555 return;
556 Main.worker.submit(new DownloadRelationTask(
557 model.getSelectedNonNewRelations(),
558 Main.map.mapView.getEditLayer())
559 );
560 }
561 }
562
563 /**
564 * Action for downloading incomplete members of selected relations
565 *
566 */
567 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener{
568 public DownloadSelectedIncompleteMembersAction() {
569 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
570 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
571 putValue(NAME, tr("Download incomplete members"));
572 updateEnabledState();
573 }
574
575 public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) {
576 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
577 for(Relation r: rels) {
578 ret.addAll(r.getIncompleteMembers());
579 }
580 return ret;
581 }
582
583 public void actionPerformed(ActionEvent e) {
584 if (!isEnabled())
585 return;
586 List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers();
587 if (rels.isEmpty()) return;
588 Main.worker.submit(new DownloadRelationMemberTask(
589 rels,
590 buildSetOfIncompleteMembers(rels),
591 Main.map.mapView.getEditLayer()
592 ));
593 }
594
595 protected void updateEnabledState() {
596 setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty());
597 }
598
599 public void valueChanged(ListSelectionEvent e) {
600 updateEnabledState();
601 }
602 }
603
604 class AddToRelation extends AbstractAction implements ListSelectionListener, SelectionChangedListener {
605
606 public AddToRelation() {
607 super("", ImageProvider.get("dialogs/conflict", "copyendright"));
608 putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
609 setEnabled(false);
610 }
611
612 @Override
613 public void actionPerformed(ActionEvent e) {
614 Collection<Command> cmds = new LinkedList<Command>();
615 for (Relation orig : getSelectedRelations()) {
616 Command c = GenericRelationEditor.addPrimitivesToRelation(orig, Main.main.getCurrentDataSet().getSelected());
617 if (c != null) {
618 cmds.add(c);
619 }
620 }
621 if (!cmds.isEmpty()) {
622 Main.main.undoRedo.add(new SequenceCommand(tr("Add selection to relation"), cmds));
623 }
624 }
625
626 @Override
627 public void valueChanged(ListSelectionEvent e) {
628 putValue(NAME, trn("Add selection to {0} relation", "Add selection to {0} relations",
629 getSelectedRelations().size(), getSelectedRelations().size()));
630 }
631
632 @Override
633 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
634 setEnabled(newSelection != null && !newSelection.isEmpty());
635 }
636 }
637
638 /**
639 * The list model for the list of relations displayed in the relation list
640 * dialog.
641 *
642 */
643 private class RelationListModel extends AbstractListModel {
644 private final ArrayList<Relation> relations = new ArrayList<Relation>();
645 private DefaultListSelectionModel selectionModel;
646
647 public RelationListModel(DefaultListSelectionModel selectionModel) {
648 this.selectionModel = selectionModel;
649 }
650
651 public Relation getRelation(int idx) {
652 return relations.get(idx);
653 }
654
655 public void sort() {
656 Collections.sort(
657 relations,
658 DefaultNameFormatter.getInstance().getRelationComparator()
659 );
660 }
661
662 private boolean isValid(Relation r) {
663 return !r.isDeleted() && r.isVisible() && !r.isIncomplete();
664 }
665
666 public void setRelations(Collection<Relation> relations) {
667 List<Relation> sel = getSelectedRelations();
668 this.relations.clear();
669 if (relations == null) {
670 selectionModel.clearSelection();
671 fireContentsChanged(this,0,getSize());
672 return;
673
674 }
675 for (Relation r: relations) {
676 if (isValid(r)) {
677 this.relations.add(r);
678 }
679 }
680 sort();
681 fireIntervalAdded(this, 0, getSize());
682 setSelectedRelations(sel);
683 }
684
685 /**
686 * Add all relations in <code>addedPrimitives</code> to the model for the
687 * relation list dialog
688 *
689 * @param addedPrimitives the collection of added primitives. May include nodes,
690 * ways, and relations.
691 */
692 public void addRelations(Collection<? extends OsmPrimitive> addedPrimitives) {
693 boolean added = false;
694 for (OsmPrimitive p: addedPrimitives) {
695 if (! (p instanceof Relation)) {
696 continue;
697 }
698
699 Relation r = (Relation)p;
700 if (relations.contains(r)) {
701 continue;
702 }
703 if (isValid(r)) {
704 relations.add(r);
705 added = true;
706 }
707 }
708 if (added) {
709 List<Relation> sel = getSelectedRelations();
710 sort();
711 fireIntervalAdded(this, 0, getSize());
712 setSelectedRelations(sel);
713 }
714 }
715
716 /**
717 * Removes all relations in <code>removedPrimitives</code> from the model
718 *
719 * @param removedPrimitives the removed primitives. May include nodes, ways,
720 * and relations
721 */
722 public void removeRelations(Collection<? extends OsmPrimitive> removedPrimitives) {
723 if (removedPrimitives == null) return;
724 // extract the removed relations
725 //
726 Set<Relation> removedRelations = new HashSet<Relation>();
727 for (OsmPrimitive p: removedPrimitives) {
728 if (! (p instanceof Relation)) {
729 continue;
730 }
731 removedRelations.add((Relation)p);
732 }
733 if (removedRelations.isEmpty())
734 return;
735 int size = relations.size();
736 relations.removeAll(removedRelations);
737 if (size != relations.size()) {
738 List<Relation> sel = getSelectedRelations();
739 sort();
740 fireContentsChanged(this, 0, getSize());
741 setSelectedRelations(sel);
742 }
743 }
744
745 /**
746 * Replies the list of selected relations with incomplete members
747 *
748 * @return the list of selected relations with incomplete members
749 */
750 public List<Relation> getSelectedRelationsWithIncompleteMembers() {
751 List<Relation> ret = getSelectedNonNewRelations();
752 Iterator<Relation> it = ret.iterator();
753 while(it.hasNext()) {
754 Relation r = it.next();
755 if (!r.hasIncompleteMembers()) {
756 it.remove();
757 }
758 }
759 return ret;
760 }
761
762 public Object getElementAt(int index) {
763 if (index < 0 || index >= relations.size()) return null;
764 return relations.get(index);
765 }
766
767 public int getSize() {
768 return relations.size();
769 }
770
771 /**
772 * Replies the list of selected, non-new relations. Empty list,
773 * if there are no selected, non-new relations.
774 *
775 * @return the list of selected, non-new relations.
776 */
777 public List<Relation> getSelectedNonNewRelations() {
778 ArrayList<Relation> ret = new ArrayList<Relation>();
779 for (int i=0; i<getSize();i++) {
780 if (!selectionModel.isSelectedIndex(i)) {
781 continue;
782 }
783 if (relations.get(i).isNew()) {
784 continue;
785 }
786 ret.add(relations.get(i));
787 }
788 return ret;
789 }
790
791 /**
792 * Replies the list of selected relations. Empty list,
793 * if there are no selected relations.
794 *
795 * @return the list of selected, non-new relations.
796 */
797 public List<Relation> getSelectedRelations() {
798 ArrayList<Relation> ret = new ArrayList<Relation>();
799 for (int i=0; i<getSize();i++) {
800 if (!selectionModel.isSelectedIndex(i)) {
801 continue;
802 }
803 ret.add(relations.get(i));
804 }
805 return ret;
806 }
807
808 /**
809 * Sets the selected relations.
810 *
811 * @return sel the list of selected relations
812 */
813 public void setSelectedRelations(Collection<Relation> sel) {
814 selectionModel.clearSelection();
815 if (sel == null || sel.isEmpty())
816 return;
817 for (Relation r: sel) {
818 int i = relations.indexOf(r);
819 if (i<0) {
820 continue;
821 }
822 selectionModel.addSelectionInterval(i,i);
823 }
824 }
825
826 /**
827 * Returns the index of the relation
828 *
829 * @return index of relation (null if it cannot be found)
830 */
831 public Integer getRelationIndex(Relation rel) {
832 int i = relations.indexOf(rel);
833 if (i<0)
834 return null;
835 return i;
836 }
837
838 public void updateTitle() {
839 if (getSize() > 0) {
840 RelationListDialog.this.setTitle(tr("Relations: {0}", getSize()));
841 } else {
842 RelationListDialog.this.setTitle(tr("Relations"));
843 }
844 }
845 }
846
847 class RelationDialogPopupMenu extends ListPopupMenu {
848
849 public RelationDialogPopupMenu(JList list) {
850 super(list);
851
852 // -- download members action
853 add(new DownloadMembersAction());
854
855 // -- download incomplete members action
856 add(new DownloadSelectedIncompleteMembersAction());
857
858 addSeparator();
859
860 // -- select members action
861 add(new SelectMembersAction(false));
862 add(new SelectMembersAction(true));
863
864 // -- select action
865 add(new SelectAction(false));
866 add(new SelectAction(true));
867
868 addSeparator();
869
870 add(addToRelation);
871 }
872 }
873
874 public void addPopupMenuSeparator() {
875 popupMenu.addSeparator();
876 }
877
878 public JMenuItem addPopupMenuAction(Action a) {
879 return popupMenu.add(a);
880 }
881
882 public void addPopupMenuListener(PopupMenuListener l) {
883 popupMenu.addPopupMenuListener(l);
884 }
885
886 public void removePopupMenuListener(PopupMenuListener l) {
887 popupMenu.addPopupMenuListener(l);
888 }
889
890 public Collection<Relation> getSelectedRelations() {
891 return model.getSelectedRelations();
892 }
893
894 /* ---------------------------------------------------------------------------------- */
895 /* DataSetListener */
896 /* ---------------------------------------------------------------------------------- */
897
898 public void nodeMoved(NodeMovedEvent event) {/* irrelevant in this context */}
899
900 public void wayNodesChanged(WayNodesChangedEvent event) {/* irrelevant in this context */}
901
902 public void primitivesAdded(final PrimitivesAddedEvent event) {
903 model.addRelations(event.getPrimitives());
904 model.updateTitle();
905 }
906
907 public void primitivesRemoved(final PrimitivesRemovedEvent event) {
908 model.removeRelations(event.getPrimitives());
909 model.updateTitle();
910 }
911
912 public void relationMembersChanged(final RelationMembersChangedEvent event) {
913 List<Relation> sel = model.getSelectedRelations();
914 model.sort();
915 model.setSelectedRelations(sel);
916 displaylist.repaint();
917 }
918
919 public void tagsChanged(TagsChangedEvent event) {
920 OsmPrimitive prim = event.getPrimitive();
921 if (prim == null || ! (prim instanceof Relation))
922 return;
923 // trigger a sort of the relation list because the display name may
924 // have changed
925 //
926 List<Relation> sel = model.getSelectedRelations();
927 model.sort();
928 model.setSelectedRelations(sel);
929 displaylist.repaint();
930 }
931
932 public void dataChanged(DataChangedEvent event) {
933 initFromLayer(Main.main.getEditLayer());
934 }
935
936 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */}
937 }