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

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

fix EDT violations

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