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

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

fix #8433 - NPEs when opening relation editor or selecting in list from selection toggle dialog's context menu with active filter of relation list

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