source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java @ 5619

Last change on this file since 5619 was 5619, checked in by simon04, 6 years ago

fix ArrayIndexOutOfBoundsException (introduced in r5616)

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