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

Last change on this file since 5709 was 5709, checked in by Don-vip, 6 years ago

fix #8424 - "Select" and "Delete" actions operate on wrong relations when filter is used in relation toggle dialog

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