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                return relations.get(index);
764            }
765    
766            public int getSize() {
767                return relations.size();
768            }
769    
770            /**
771             * Replies the list of selected, non-new relations. Empty list,
772             * if there are no selected, non-new relations.
773             *
774             * @return the list of selected, non-new relations.
775             */
776            public List<Relation> getSelectedNonNewRelations() {
777                ArrayList<Relation> ret = new ArrayList<Relation>();
778                for (int i=0; i<getSize();i++) {
779                    if (!selectionModel.isSelectedIndex(i)) {
780                        continue;
781                    }
782                    if (relations.get(i).isNew()) {
783                        continue;
784                    }
785                    ret.add(relations.get(i));
786                }
787                return ret;
788            }
789    
790            /**
791             * Replies the list of selected relations. Empty list,
792             * if there are no selected relations.
793             *
794             * @return the list of selected, non-new relations.
795             */
796            public List<Relation> getSelectedRelations() {
797                ArrayList<Relation> ret = new ArrayList<Relation>();
798                for (int i=0; i<getSize();i++) {
799                    if (!selectionModel.isSelectedIndex(i)) {
800                        continue;
801                    }
802                    ret.add(relations.get(i));
803                }
804                return ret;
805            }
806    
807            /**
808             * Sets the selected relations.
809             *
810             * @return sel the list of selected relations
811             */
812            public void setSelectedRelations(Collection<Relation> sel) {
813                selectionModel.clearSelection();
814                if (sel == null || sel.isEmpty())
815                    return;
816                for (Relation r: sel) {
817                    int i = relations.indexOf(r);
818                    if (i<0) {
819                        continue;
820                    }
821                    selectionModel.addSelectionInterval(i,i);
822                }
823            }
824    
825            /**
826             * Returns the index of the relation
827             *
828             * @return index of relation (null if it cannot be found)
829             */
830            public Integer getRelationIndex(Relation rel) {
831                int i = relations.indexOf(rel);
832                if (i<0)
833                    return null;
834                return i;
835            }
836    
837            public void updateTitle() {
838                if (getSize() > 0) {
839                    RelationListDialog.this.setTitle(tr("Relations: {0}", getSize()));
840                } else {
841                    RelationListDialog.this.setTitle(tr("Relations"));
842                }
843            }
844        }
845    
846        class RelationDialogPopupMenu extends ListPopupMenu {
847    
848            public RelationDialogPopupMenu(JList list) {
849                super(list);
850    
851                // -- download members action
852                add(new DownloadMembersAction());
853    
854                // -- download incomplete members action
855                add(new DownloadSelectedIncompleteMembersAction());
856    
857                addSeparator();
858    
859                // -- select members action
860                add(new SelectMembersAction(false));
861                add(new SelectMembersAction(true));
862    
863                // -- select action
864                add(new SelectAction(false));
865                add(new SelectAction(true));
866    
867                addSeparator();
868    
869                add(addToRelation);
870            }
871        }
872    
873        public void addPopupMenuSeparator() {
874            popupMenu.addSeparator();
875        }
876    
877        public JMenuItem addPopupMenuAction(Action a) {
878            return popupMenu.add(a);
879        }
880    
881        public void addPopupMenuListener(PopupMenuListener l) {
882            popupMenu.addPopupMenuListener(l);
883        }
884    
885        public void removePopupMenuListener(PopupMenuListener l) {
886            popupMenu.addPopupMenuListener(l);
887        }
888    
889        public Collection<Relation> getSelectedRelations() {
890            return model.getSelectedRelations();
891        }
892    
893        /* ---------------------------------------------------------------------------------- */
894        /* DataSetListener                                                                    */
895        /* ---------------------------------------------------------------------------------- */
896    
897        public void nodeMoved(NodeMovedEvent event) {/* irrelevant in this context */}
898    
899        public void wayNodesChanged(WayNodesChangedEvent event) {/* irrelevant in this context */}
900    
901        public void primitivesAdded(final PrimitivesAddedEvent event) {
902            model.addRelations(event.getPrimitives());
903            model.updateTitle();
904        }
905    
906        public void primitivesRemoved(final PrimitivesRemovedEvent event) {
907            model.removeRelations(event.getPrimitives());
908            model.updateTitle();
909        }
910    
911        public void relationMembersChanged(final RelationMembersChangedEvent event) {
912            List<Relation> sel = model.getSelectedRelations();
913            model.sort();
914            model.setSelectedRelations(sel);
915            displaylist.repaint();
916        }
917    
918        public void tagsChanged(TagsChangedEvent event) {
919            OsmPrimitive prim = event.getPrimitive();
920            if (prim == null || ! (prim instanceof Relation))
921                return;
922            // trigger a sort of the relation list because the display name may
923            // have changed
924            //
925            List<Relation> sel = model.getSelectedRelations();
926            model.sort();
927            model.setSelectedRelations(sel);
928            displaylist.repaint();
929        }
930    
931        public void dataChanged(DataChangedEvent event) {
932            initFromLayer(Main.main.getEditLayer());
933        }
934    
935        public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */}
936    }