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

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

fix many checkstyle violations

  • Property svn:eol-style set to native
File size: 25.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseEvent;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Set;
19
20import javax.swing.AbstractAction;
21import javax.swing.AbstractListModel;
22import javax.swing.DefaultListSelectionModel;
23import javax.swing.FocusManager;
24import javax.swing.JComponent;
25import javax.swing.JList;
26import javax.swing.JPanel;
27import javax.swing.JPopupMenu;
28import javax.swing.JScrollPane;
29import javax.swing.KeyStroke;
30import javax.swing.ListSelectionModel;
31import javax.swing.UIManager;
32import javax.swing.event.DocumentEvent;
33import javax.swing.event.DocumentListener;
34import javax.swing.event.ListSelectionEvent;
35import javax.swing.event.ListSelectionListener;
36
37import org.openstreetmap.josm.Main;
38import org.openstreetmap.josm.actions.relation.AddSelectionToRelations;
39import org.openstreetmap.josm.actions.relation.DeleteRelationsAction;
40import org.openstreetmap.josm.actions.relation.DownloadMembersAction;
41import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction;
42import org.openstreetmap.josm.actions.relation.DuplicateRelationAction;
43import org.openstreetmap.josm.actions.relation.EditRelationAction;
44import org.openstreetmap.josm.actions.relation.SelectMembersAction;
45import org.openstreetmap.josm.actions.relation.SelectRelationAction;
46import org.openstreetmap.josm.actions.search.SearchCompiler;
47import org.openstreetmap.josm.data.osm.DataSet;
48import org.openstreetmap.josm.data.osm.OsmPrimitive;
49import org.openstreetmap.josm.data.osm.Relation;
50import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
51import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
52import org.openstreetmap.josm.data.osm.event.DataSetListener;
53import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
54import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
55import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
56import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
57import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
58import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
59import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
60import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
61import org.openstreetmap.josm.gui.DefaultNameFormatter;
62import org.openstreetmap.josm.gui.MapView;
63import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
64import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
65import org.openstreetmap.josm.gui.PopupMenuHandler;
66import org.openstreetmap.josm.gui.SideButton;
67import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
68import org.openstreetmap.josm.gui.layer.Layer;
69import org.openstreetmap.josm.gui.layer.OsmDataLayer;
70import org.openstreetmap.josm.gui.util.GuiHelper;
71import org.openstreetmap.josm.gui.util.HighlightHelper;
72import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
73import org.openstreetmap.josm.gui.widgets.JosmTextField;
74import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
75import org.openstreetmap.josm.tools.ImageProvider;
76import org.openstreetmap.josm.tools.InputMapUtils;
77import org.openstreetmap.josm.tools.Predicate;
78import org.openstreetmap.josm.tools.Shortcut;
79import org.openstreetmap.josm.tools.Utils;
80
81/**
82 * A dialog showing all known relations, with buttons to add, edit, and
83 * delete them.
84 *
85 * We don't have such dialogs for nodes, segments, and ways, because those
86 * objects are visible on the map and can be selected there. Relations are not.
87 */
88public class RelationListDialog extends ToggleDialog implements DataSetListener {
89    /** The display list. */
90    private final JList<Relation> displaylist;
91    /** the list model used */
92    private final RelationListModel model;
93
94    private final NewAction newAction;
95
96    /** the popup menu and its handler */
97    private final JPopupMenu popupMenu = new JPopupMenu();
98    private final transient PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu);
99
100    private final JosmTextField filter;
101
102    // Actions
103    /** the edit action */
104    private final EditRelationAction editAction = new EditRelationAction();
105    /** the delete action */
106    private final DeleteRelationsAction deleteRelationsAction = new DeleteRelationsAction();
107    /** the duplicate action */
108    private final DuplicateRelationAction duplicateAction = new DuplicateRelationAction();
109    private final DownloadMembersAction downloadMembersAction = new DownloadMembersAction();
110    private final DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction();
111    private final SelectMembersAction selectMembersAction = new SelectMembersAction(false);
112    private final SelectMembersAction addMembersToSelectionAction = new SelectMembersAction(true);
113    private final SelectRelationAction selectRelationAction = new SelectRelationAction(false);
114    private final SelectRelationAction addRelationToSelectionAction = new SelectRelationAction(true);
115    /** add all selected primitives to the given relations */
116    private final AddSelectionToRelations addSelectionToRelations = new AddSelectionToRelations();
117
118    private final transient HighlightHelper highlightHelper = new HighlightHelper();
119    private boolean highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true);
120
121    /**
122     * Constructs <code>RelationListDialog</code>
123     */
124    public RelationListDialog() {
125        super(tr("Relations"), "relationlist", tr("Open a list of all relations."),
126                Shortcut.registerShortcut("subwindow:relations", tr("Toggle: {0}", tr("Relations")),
127                KeyEvent.VK_R, Shortcut.ALT_SHIFT), 150, true);
128
129        // create the list of relations
130        //
131        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
132        model = new RelationListModel(selectionModel);
133        displaylist = new JList<>(model);
134        displaylist.setSelectionModel(selectionModel);
135        displaylist.setCellRenderer(new OsmPrimitivRenderer() {
136            /**
137             * Don't show the default tooltip in the relation list.
138             */
139            @Override
140            protected String getComponentToolTipText(OsmPrimitive value) {
141                return null;
142            }
143        });
144        displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
145        displaylist.addMouseListener(new MouseEventHandler());
146
147        // the new action
148        //
149        newAction = new NewAction();
150
151        filter = setupFilter();
152
153        displaylist.addListSelectionListener(new ListSelectionListener() {
154            @Override
155            public void valueChanged(ListSelectionEvent e) {
156                updateActionsRelationLists();
157            }
158        });
159
160        // Setup popup menu handler
161        setupPopupMenuHandler();
162
163        JPanel pane = new JPanel(new BorderLayout());
164        pane.add(filter, BorderLayout.NORTH);
165        pane.add(new JScrollPane(displaylist), BorderLayout.CENTER);
166        createLayout(pane, false, Arrays.asList(new SideButton[]{
167                new SideButton(newAction, false),
168                new SideButton(editAction, false),
169                new SideButton(duplicateAction, false),
170                new SideButton(deleteRelationsAction, false),
171                new SideButton(selectRelationAction, false)
172        }));
173
174        InputMapUtils.unassignCtrlShiftUpDown(displaylist, JComponent.WHEN_FOCUSED);
175
176        // Select relation on Enter
177        InputMapUtils.addEnterAction(displaylist, selectRelationAction);
178
179        // Edit relation on Ctrl-Enter
180        displaylist.getActionMap().put("edit", editAction);
181        displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK), "edit");
182
183        // Do not hide copy action because of default JList override (fix #9815)
184        displaylist.getActionMap().put("copy", Main.main.menu.copy);
185        displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, GuiHelper.getMenuShortcutKeyMaskEx()), "copy");
186
187        updateActionsRelationLists();
188    }
189
190    // inform all actions about list of relations they need
191    private void updateActionsRelationLists() {
192        List<Relation> sel = model.getSelectedRelations();
193        popupMenuHandler.setPrimitives(sel);
194
195        Component focused = FocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
196
197        //update highlights
198        if (highlightEnabled && focused==displaylist && Main.isDisplayingMapView()) {
199            if (highlightHelper.highlightOnly(sel)) {
200                Main.map.mapView.repaint();
201            }
202        }
203    }
204
205    @Override
206    public void showNotify() {
207        MapView.addLayerChangeListener(newAction);
208        newAction.updateEnabledState();
209        DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT);
210        DataSet.addSelectionListener(addSelectionToRelations);
211        dataChanged(null);
212    }
213
214    @Override
215    public void hideNotify() {
216        MapView.removeLayerChangeListener(newAction);
217        DatasetEventManager.getInstance().removeDatasetListener(this);
218        DataSet.removeSelectionListener(addSelectionToRelations);
219    }
220
221    private void resetFilter() {
222        filter.setText(null);
223    }
224
225    /**
226     * Initializes the relation list dialog from a layer. If <code>layer</code> is null
227     * or if it isn't an {@link OsmDataLayer} the dialog is reset to an empty dialog.
228     * Otherwise it is initialized with the list of non-deleted and visible relations
229     * in the layer's dataset.
230     *
231     * @param layer the layer. May be null.
232     */
233    protected void initFromLayer(Layer layer) {
234        if (!(layer instanceof OsmDataLayer)) {
235            model.setRelations(null);
236            return;
237        }
238        OsmDataLayer l = (OsmDataLayer)layer;
239        model.setRelations(l.data.getRelations());
240        model.updateTitle();
241        updateActionsRelationLists();
242    }
243
244    /**
245     * @return The selected relation in the list
246     */
247    private Relation getSelected() {
248        if (model.getSize() == 1) {
249            displaylist.setSelectedIndex(0);
250        }
251        return displaylist.getSelectedValue();
252    }
253
254    /**
255     * Selects the relation <code>relation</code> in the list of relations.
256     *
257     * @param relation  the relation
258     */
259    public void selectRelation(Relation relation) {
260        selectRelations(Collections.singleton(relation));
261    }
262
263    /**
264     * Selects the relations in the list of relations.
265     * @param relations  the relations to be selected
266     */
267    public void selectRelations(Collection<Relation> relations) {
268        if (relations == null || relations.isEmpty()) {
269            model.setSelectedRelations(null);
270        } else {
271            model.setSelectedRelations(relations);
272            Integer i = model.getVisibleRelationIndex(relations.iterator().next());
273            if (i != null) {
274                // Not all relations have to be in the list
275                // (for example when the relation list is hidden, it's not updated with new relations)
276                displaylist.scrollRectToVisible(displaylist.getCellBounds(i, i));
277            }
278        }
279    }
280
281    private JosmTextField  setupFilter() {
282        final JosmTextField f = new DisableShortcutsOnFocusGainedTextField();
283        f.setToolTipText(tr("Relation list filter"));
284        f.getDocument().addDocumentListener(new DocumentListener() {
285
286            private void setFilter() {
287                try {
288                    f.setBackground(UIManager.getColor("TextField.background"));
289                    f.setToolTipText(tr("Relation list filter"));
290                    model.setFilter(SearchCompiler.compile(filter.getText(), false, false));
291                } catch (SearchCompiler.ParseError ex) {
292                    f.setBackground(new Color(255, 224, 224));
293                    f.setToolTipText(ex.getMessage());
294                    model.setFilter(new SearchCompiler.Always());
295                }
296            }
297
298            @Override
299            public void insertUpdate(DocumentEvent e) {
300                setFilter();
301            }
302
303            @Override
304            public void removeUpdate(DocumentEvent e) {
305                setFilter();
306            }
307
308            @Override
309            public void changedUpdate(DocumentEvent e) {
310                setFilter();
311            }
312        });
313        return f;
314    }
315
316    class MouseEventHandler extends PopupMenuLauncher {
317
318        public MouseEventHandler() {
319            super(popupMenu);
320        }
321
322        @Override
323        public void mouseExited(MouseEvent me) {
324            if (highlightEnabled) highlightHelper.clear();
325        }
326
327        protected void setCurrentRelationAsSelection() {
328            Main.main.getCurrentDataSet().setSelected(displaylist.getSelectedValue());
329        }
330
331        protected void editCurrentRelation() {
332            EditRelationAction.launchEditor(getSelected());
333        }
334
335        @Override
336        public void mouseClicked(MouseEvent e) {
337            if (!Main.main.hasEditLayer()) return;
338            if (isDoubleClick(e)) {
339                if (e.isControlDown()) {
340                    editCurrentRelation();
341                } else {
342                    setCurrentRelationAsSelection();
343                }
344            }
345        }
346    }
347
348    /**
349     * The action for creating a new relation
350     *
351     */
352    static class NewAction extends AbstractAction implements LayerChangeListener{
353        public NewAction() {
354            putValue(SHORT_DESCRIPTION,tr("Create a new relation"));
355            putValue(NAME, tr("New"));
356            putValue(SMALL_ICON, ImageProvider.get("dialogs", "addrelation"));
357            updateEnabledState();
358        }
359
360        public void run() {
361            RelationEditor.getEditor(Main.main.getEditLayer(),null, null).setVisible(true);
362        }
363
364        @Override
365        public void actionPerformed(ActionEvent e) {
366            run();
367        }
368
369        protected void updateEnabledState() {
370            setEnabled(Main.main != null && Main.main.hasEditLayer());
371        }
372
373        @Override
374        public void activeLayerChange(Layer oldLayer, Layer newLayer) {
375            updateEnabledState();
376        }
377
378        @Override
379        public void layerAdded(Layer newLayer) {
380            updateEnabledState();
381        }
382
383        @Override
384        public void layerRemoved(Layer oldLayer) {
385            updateEnabledState();
386        }
387    }
388
389    /**
390     * The list model for the list of relations displayed in the relation list dialog.
391     *
392     */
393    private class RelationListModel extends AbstractListModel<Relation> {
394        private final transient List<Relation> relations = new ArrayList<>();
395        private transient List<Relation> filteredRelations;
396        private DefaultListSelectionModel selectionModel;
397        private transient SearchCompiler.Match filter;
398
399        public RelationListModel(DefaultListSelectionModel selectionModel) {
400            this.selectionModel = selectionModel;
401        }
402
403        public void sort() {
404            Collections.sort(
405                    relations,
406                    DefaultNameFormatter.getInstance().getRelationComparator()
407                    );
408        }
409
410        private boolean isValid(Relation r) {
411            return !r.isDeleted() && r.isVisible() && !r.isIncomplete();
412        }
413
414        public void setRelations(Collection<Relation> relations) {
415            List<Relation> sel =  getSelectedRelations();
416            this.relations.clear();
417            this.filteredRelations = null;
418            if (relations == null) {
419                selectionModel.clearSelection();
420                fireContentsChanged(this,0,getSize());
421                return;
422            }
423            for (Relation r: relations) {
424                if (isValid(r)) {
425                    this.relations.add(r);
426                }
427            }
428            sort();
429            updateFilteredRelations();
430            fireIntervalAdded(this, 0, getSize());
431            setSelectedRelations(sel);
432        }
433
434        /**
435         * Add all relations in <code>addedPrimitives</code> to the model for the
436         * relation list dialog
437         *
438         * @param addedPrimitives the collection of added primitives. May include nodes,
439         * ways, and relations.
440         */
441        public void addRelations(Collection<? extends OsmPrimitive> addedPrimitives) {
442            boolean added = false;
443            for (OsmPrimitive p: addedPrimitives) {
444                if (!(p instanceof Relation)) {
445                    continue;
446                }
447
448                Relation r = (Relation)p;
449                if (relations.contains(r)) {
450                    continue;
451                }
452                if (isValid(r)) {
453                    relations.add(r);
454                    added = true;
455                }
456            }
457            if (added) {
458                List<Relation> sel = getSelectedRelations();
459                sort();
460                updateFilteredRelations();
461                fireIntervalAdded(this, 0, getSize());
462                setSelectedRelations(sel);
463            }
464        }
465
466        /**
467         * Removes all relations in <code>removedPrimitives</code> from the model
468         *
469         * @param removedPrimitives the removed primitives. May include nodes, ways,
470         *   and relations
471         */
472        public void removeRelations(Collection<? extends OsmPrimitive> removedPrimitives) {
473            if (removedPrimitives == null) return;
474            // extract the removed relations
475            //
476            Set<Relation> removedRelations = new HashSet<>();
477            for (OsmPrimitive p: removedPrimitives) {
478                if (!(p instanceof Relation)) {
479                    continue;
480                }
481                removedRelations.add((Relation)p);
482            }
483            if (removedRelations.isEmpty())
484                return;
485            int size = relations.size();
486            relations.removeAll(removedRelations);
487            if (filteredRelations != null) {
488                filteredRelations.removeAll(removedRelations);
489            }
490            if (size != relations.size()) {
491                List<Relation> sel = getSelectedRelations();
492                sort();
493                fireContentsChanged(this, 0, getSize());
494                setSelectedRelations(sel);
495            }
496        }
497
498        private void updateFilteredRelations() {
499            if (filter != null) {
500                filteredRelations = new ArrayList<>(Utils.filter(relations, new Predicate<Relation>() {
501                    @Override
502                    public boolean evaluate(Relation r) {
503                        return filter.match(r);
504                    }
505                }));
506            } else if (filteredRelations != null) {
507                filteredRelations = null;
508            }
509        }
510
511        public void setFilter(final SearchCompiler.Match filter) {
512            this.filter = filter;
513            updateFilteredRelations();
514            List<Relation> sel = getSelectedRelations();
515            fireContentsChanged(this, 0, getSize());
516            setSelectedRelations(sel);
517            updateTitle();
518        }
519
520        private List<Relation> getVisibleRelations() {
521            return filteredRelations == null ? relations : filteredRelations;
522        }
523
524        private Relation getVisibleRelation(int index) {
525            if (index < 0 || index >= getVisibleRelations().size()) return null;
526            return getVisibleRelations().get(index);
527        }
528
529        @Override
530        public Relation getElementAt(int index) {
531            return getVisibleRelation(index);
532        }
533
534        @Override
535        public int getSize() {
536            return getVisibleRelations().size();
537        }
538
539        /**
540         * Replies the list of selected relations. Empty list,
541         * if there are no selected relations.
542         *
543         * @return the list of selected, non-new relations.
544         */
545        public List<Relation> getSelectedRelations() {
546            List<Relation> ret = new ArrayList<>();
547            for (int i=0; i<getSize();i++) {
548                if (!selectionModel.isSelectedIndex(i)) {
549                    continue;
550                }
551                ret.add(getVisibleRelation(i));
552            }
553            return ret;
554        }
555
556        /**
557         * Sets the selected relations.
558         *
559         * @param sel the list of selected relations
560         */
561        public void setSelectedRelations(Collection<Relation> sel) {
562            selectionModel.clearSelection();
563            if (sel == null || sel.isEmpty())
564                return;
565            if (!getVisibleRelations().containsAll(sel)) {
566                resetFilter();
567            }
568            for (Relation r: sel) {
569                Integer i = getVisibleRelationIndex(r);
570                if (i != null) {
571                    selectionModel.addSelectionInterval(i,i);
572                }
573            }
574        }
575
576        private Integer getVisibleRelationIndex(Relation rel) {
577            int i = getVisibleRelations().indexOf(rel);
578            if (i<0)
579                return null;
580            return i;
581        }
582
583        public void updateTitle() {
584            if (!relations.isEmpty() && relations.size() != getSize()) {
585                RelationListDialog.this.setTitle(tr("Relations: {0}/{1}", getSize(), relations.size()));
586            } else if (getSize() > 0) {
587                RelationListDialog.this.setTitle(tr("Relations: {0}", getSize()));
588            } else {
589                RelationListDialog.this.setTitle(tr("Relations"));
590            }
591        }
592    }
593
594    private final void setupPopupMenuHandler() {
595
596        // -- select action
597        popupMenuHandler.addAction(selectRelationAction);
598        popupMenuHandler.addAction(addRelationToSelectionAction);
599
600        // -- select members action
601        popupMenuHandler.addAction(selectMembersAction);
602        popupMenuHandler.addAction(addMembersToSelectionAction);
603
604        popupMenuHandler.addSeparator();
605        // -- download members action
606        popupMenuHandler.addAction(downloadMembersAction);
607
608        // -- download incomplete members action
609        popupMenuHandler.addAction(downloadSelectedIncompleteMembersAction);
610
611        popupMenuHandler.addSeparator();
612        popupMenuHandler.addAction(editAction).setVisible(false);
613        popupMenuHandler.addAction(duplicateAction).setVisible(false);
614        popupMenuHandler.addAction(deleteRelationsAction).setVisible(false);
615
616        popupMenuHandler.addAction(addSelectionToRelations);
617    }
618
619    /* ---------------------------------------------------------------------------------- */
620    /* Methods that can be called from plugins                                            */
621    /* ---------------------------------------------------------------------------------- */
622
623    /**
624     * Replies the popup menu handler.
625     * @return The popup menu handler
626     */
627    public PopupMenuHandler getPopupMenuHandler() {
628        return popupMenuHandler;
629    }
630
631    /**
632     * Replies the list of selected relations. Empty list, if there are no selected relations.
633     * @return the list of selected, non-new relations.
634     */
635    public Collection<Relation> getSelectedRelations() {
636        return model.getSelectedRelations();
637    }
638
639    /* ---------------------------------------------------------------------------------- */
640    /* DataSetListener                                                                    */
641    /* ---------------------------------------------------------------------------------- */
642
643    @Override
644    public void nodeMoved(NodeMovedEvent event) {
645        /* irrelevant in this context */
646    }
647
648    @Override
649    public void wayNodesChanged(WayNodesChangedEvent event) {
650        /* irrelevant in this context */
651    }
652
653    @Override
654    public void primitivesAdded(final PrimitivesAddedEvent event) {
655        model.addRelations(event.getPrimitives());
656        model.updateTitle();
657    }
658
659    @Override
660    public void primitivesRemoved(final PrimitivesRemovedEvent event) {
661        model.removeRelations(event.getPrimitives());
662        model.updateTitle();
663    }
664
665    @Override
666    public void relationMembersChanged(final RelationMembersChangedEvent event) {
667        List<Relation> sel = model.getSelectedRelations();
668        model.sort();
669        model.setSelectedRelations(sel);
670        displaylist.repaint();
671    }
672
673    @Override
674    public void tagsChanged(TagsChangedEvent event) {
675        OsmPrimitive prim = event.getPrimitive();
676        if (!(prim instanceof Relation))
677            return;
678        // trigger a sort of the relation list because the display name may have changed
679        //
680        List<Relation> sel = model.getSelectedRelations();
681        model.sort();
682        model.setSelectedRelations(sel);
683        displaylist.repaint();
684    }
685
686    @Override
687    public void dataChanged(DataChangedEvent event) {
688        initFromLayer(Main.main.getEditLayer());
689    }
690
691    @Override
692    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
693        /* ignore */
694    }
695}
Note: See TracBrowser for help on using the repository browser.