source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java @ 5241

Revision 5200, 33.1 KB checked in by akks, 5 weeks ago (diff)

see #7626, fix #7463: keys Ctrl-Shift-Up/Down, Enter, Spacebar work better in toggle dialogs
Enter and Spacebar = useful actions for list items (select, toggle, etc.)

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Component;
8import java.awt.Rectangle;
9import java.awt.event.ActionEvent;
10import java.awt.event.ActionListener;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.Comparator;
19import java.util.HashSet;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Set;
23
24import javax.swing.AbstractAction;
25import javax.swing.AbstractListModel;
26import javax.swing.Action;
27import javax.swing.DefaultListSelectionModel;
28import javax.swing.JList;
29import javax.swing.JMenuItem;
30import javax.swing.JPopupMenu;
31import javax.swing.ListSelectionModel;
32import javax.swing.SwingUtilities;
33import javax.swing.event.ListDataEvent;
34import javax.swing.event.ListDataListener;
35import javax.swing.event.ListSelectionEvent;
36import javax.swing.event.ListSelectionListener;
37import javax.swing.event.PopupMenuListener;
38
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.actions.AutoScaleAction;
41import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
42import org.openstreetmap.josm.data.SelectionChangedListener;
43import org.openstreetmap.josm.data.osm.Node;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
46import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
47import org.openstreetmap.josm.data.osm.Relation;
48import org.openstreetmap.josm.data.osm.RelationMember;
49import org.openstreetmap.josm.data.osm.Way;
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.SelectionEventManager;
60import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
61import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
62import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
63import org.openstreetmap.josm.gui.DefaultNameFormatter;
64import org.openstreetmap.josm.gui.MapView;
65import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
66import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
67import org.openstreetmap.josm.gui.SideButton;
68import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
69import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
70import org.openstreetmap.josm.gui.layer.OsmDataLayer;
71import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
72import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
73import org.openstreetmap.josm.tools.ImageProvider;
74import org.openstreetmap.josm.tools.InputMapUtils;
75import org.openstreetmap.josm.tools.Shortcut;
76
77/**
78 * A small tool dialog for displaying the current selection.
79 *
80 */
81public class SelectionListDialog extends ToggleDialog  {
82    private JList lstPrimitives;
83    private SelectionListModel model;
84
85    private SelectAction actSelect;
86    private SearchAction actSearch;
87    private ZoomToJOSMSelectionAction actZoomToJOSMSelection;
88    private ZoomToListSelection actZoomToListSelection;
89    private SetRelationSelection actSetRelationSelection;
90    private EditRelationSelection actEditRelationSelection;
91    private DownloadSelectedIncompleteMembersAction actDownloadSelectedIncompleteMembers;
92
93    private SelectionPopup popupMenu;
94
95    /**
96     * Builds the content panel for this dialog
97     */
98    protected void buildContentPanel() {
99        DefaultListSelectionModel selectionModel  = new DefaultListSelectionModel();
100        model = new SelectionListModel(selectionModel);
101        lstPrimitives = new JList(model);
102        lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
103        lstPrimitives.setSelectionModel(selectionModel);
104        lstPrimitives.setCellRenderer(new OsmPrimitivRenderer());
105        lstPrimitives.setTransferHandler(null); // Fix #6290. Drag & Drop is not supported anyway and Copy/Paste is better propagated to main window
106
107        // the select action
108        final SideButton selectButton = new SideButton(actSelect = new SelectAction());
109        lstPrimitives.getSelectionModel().addListSelectionListener(actSelect);
110        selectButton.createArrow(new ActionListener() {
111            public void actionPerformed(ActionEvent e) {
112                SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory());
113            }
114        });
115
116        // the search button
117        final SideButton searchButton = new SideButton(actSearch = new SearchAction());
118        searchButton.createArrow(new ActionListener() {
119            public void actionPerformed(ActionEvent e) {
120                SearchPopupMenu.launch(searchButton);
121            }
122        });
123
124        createLayout(lstPrimitives, true, Arrays.asList(new SideButton[] {
125            selectButton, searchButton
126        }));
127    }
128
129    public SelectionListDialog() {
130        super(tr("Selection"), "selectionlist", tr("Open a selection list window."),
131                Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}",
132                tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT),
133                150, // default height
134                true // default is "show dialog"
135        );
136
137        buildContentPanel();
138        model.addListDataListener(new TitleUpdater());
139        actZoomToJOSMSelection = new ZoomToJOSMSelectionAction();
140        model.addListDataListener(actZoomToJOSMSelection);
141
142        actZoomToListSelection = new ZoomToListSelection();
143        actSetRelationSelection = new SetRelationSelection();
144        actEditRelationSelection = new EditRelationSelection();
145        actDownloadSelectedIncompleteMembers = new DownloadSelectedIncompleteMembersAction();
146
147        lstPrimitives.addMouseListener(new SelectionPopupMenuLauncher());
148        lstPrimitives.addMouseListener(new DblClickHandler());
149
150        popupMenu = new SelectionPopup(lstPrimitives);
151        InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection);
152    }
153
154    @Override
155    public void showNotify() {
156        MapView.addEditLayerChangeListener(model);
157        SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
158        DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
159        MapView.addEditLayerChangeListener(actSearch);
160        // editLayerChanged also gets the selection history of the level
161        model.editLayerChanged(null, Main.map.mapView.getEditLayer());
162        if (Main.map.mapView.getEditLayer() != null) {
163            model.setJOSMSelection(Main.map.mapView.getEditLayer().data.getSelected());
164        }
165        actSearch.updateEnabledState();
166    }
167
168    @Override
169    public void hideNotify() {
170        MapView.removeEditLayerChangeListener(actSearch);
171        MapView.removeEditLayerChangeListener(model);
172        SelectionEventManager.getInstance().removeSelectionListener(model);
173        DatasetEventManager.getInstance().removeDatasetListener(model);
174    }
175
176    /**
177     * Responds to double clicks on the list of selected objects
178     */
179    class DblClickHandler extends MouseAdapter {
180        @Override
181        public void mouseClicked(MouseEvent e) {
182            if (e.getClickCount() < 2 || ! SwingUtilities.isLeftMouseButton(e)) return;
183            int idx = lstPrimitives.locationToIndex(e.getPoint());
184            if (idx < 0) return;
185            OsmDataLayer layer = Main.main.getEditLayer();
186            if(layer == null) return;
187            layer.data.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx)));
188        }
189    }
190
191    /**
192     * The popup menu launcher
193     */
194    class SelectionPopupMenuLauncher extends PopupMenuLauncher {
195
196        @Override
197        public void launch(MouseEvent evt) {
198            if (model.getSelected().isEmpty()) {
199                int idx = lstPrimitives.locationToIndex(evt.getPoint());
200                if (idx < 0) return;
201                model.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx)));
202            }
203            popupMenu.show(lstPrimitives, evt.getX(), evt.getY());
204        }
205    }
206
207    /**
208     * The popup menu for the selection list
209     */
210    class SelectionPopup extends ListPopupMenu {
211        public SelectionPopup(JList list) {
212            super(list);
213            add(actZoomToJOSMSelection);
214            add(actZoomToListSelection);
215            addSeparator();
216            add(actSetRelationSelection);
217            add(actEditRelationSelection);
218            addSeparator();
219            add(actDownloadSelectedIncompleteMembers);
220        }
221    }
222
223    public void addPopupMenuSeparator() {
224        popupMenu.addSeparator();
225    }
226
227    public JMenuItem addPopupMenuAction(Action a) {
228        return popupMenu.add(a);
229    }
230
231    public void addPopupMenuListener(PopupMenuListener l) {
232        popupMenu.addPopupMenuListener(l);
233    }
234
235    public void removePopupMenuListener(PopupMenuListener l) {
236        popupMenu.addPopupMenuListener(l);
237    }
238
239    public Collection<OsmPrimitive> getSelectedPrimitives() {
240        return model.getSelected();
241    }
242
243    /**
244     * Updates the dialog title with a summary of the current JOSM selection
245     */
246    class TitleUpdater implements ListDataListener {
247        protected void updateTitle() {
248            setTitle(model.getJOSMSelectionSummary());
249        }
250
251        public void contentsChanged(ListDataEvent e) {
252            updateTitle();
253        }
254
255        public void intervalAdded(ListDataEvent e) {
256            updateTitle();
257        }
258
259        public void intervalRemoved(ListDataEvent e) {
260            updateTitle();
261        }
262    }
263
264    /**
265     * Launches the search dialog
266     */
267    static class SearchAction extends AbstractAction implements EditLayerChangeListener {
268        public SearchAction() {
269            putValue(NAME, tr("Search"));
270            putValue(SHORT_DESCRIPTION,   tr("Search for objects"));
271            putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
272            updateEnabledState();
273        }
274
275        public void actionPerformed(ActionEvent e) {
276            if (!isEnabled()) return;
277            org.openstreetmap.josm.actions.search.SearchAction.search();
278        }
279
280        public void updateEnabledState() {
281            setEnabled(Main.main != null && Main.main.getEditLayer() != null);
282        }
283
284        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
285            updateEnabledState();
286        }
287    }
288
289    /**
290     * Sets the current JOSM selection to the OSM primitives selected in the list
291     * of this dialog
292     */
293    class SelectAction extends AbstractAction implements ListSelectionListener {
294        public SelectAction() {
295            putValue(NAME, tr("Select"));
296            putValue(SHORT_DESCRIPTION,  tr("Set the selected elements on the map to the selected items in the list above."));
297            putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
298            updateEnabledState();
299        }
300
301        public void actionPerformed(ActionEvent e) {
302            Collection<OsmPrimitive> sel = model.getSelected();
303            if (sel.isEmpty())return;
304            if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return;
305            Main.map.mapView.getEditLayer().data.setSelected(sel);
306        }
307
308        public void updateEnabledState() {
309            setEnabled(!model.getSelected().isEmpty());
310        }
311
312        public void valueChanged(ListSelectionEvent e) {
313            updateEnabledState();
314        }
315    }
316
317    /**
318     * The action for zooming to the primitives in the current JOSM selection
319     *
320     */
321    class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener {
322
323        public ZoomToJOSMSelectionAction() {
324            putValue(NAME,tr("Zoom to selection"));
325            putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
326            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
327            updateEnabledState();
328        }
329
330        public void actionPerformed(ActionEvent e) {
331            AutoScaleAction.autoScale("selection");
332        }
333
334        public void updateEnabledState() {
335            setEnabled(model.getSize() > 0);
336        }
337
338        public void contentsChanged(ListDataEvent e) {
339            updateEnabledState();
340        }
341
342        public void intervalAdded(ListDataEvent e) {
343            updateEnabledState();
344        }
345
346        public void intervalRemoved(ListDataEvent e) {
347            updateEnabledState();
348        }
349    }
350
351    /**
352     * The action for zooming to the primitives which are currently selected in
353     * the list displaying the JOSM selection
354     *
355     */
356    class ZoomToListSelection extends AbstractAction implements ListSelectionListener{
357        public ZoomToListSelection() {
358            putValue(NAME, tr("Zoom to selected element(s)"));
359            putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)"));
360            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
361            updateEnabledState();
362        }
363
364        public void actionPerformed(ActionEvent e) {
365            BoundingXYVisitor box = new BoundingXYVisitor();
366            Collection<OsmPrimitive> sel = model.getSelected();
367            if (sel.isEmpty()) return;
368            box.computeBoundingBox(sel);
369            if (box.getBounds() == null)
370                return;
371            box.enlargeBoundingBox();
372            Main.map.mapView.recalculateCenterScale(box);
373        }
374
375        public void updateEnabledState() {
376            setEnabled(!model.getSelected().isEmpty());
377        }
378
379        public void valueChanged(ListSelectionEvent e) {
380            updateEnabledState();
381        }
382    }
383
384    /**
385     * The action for setting and editing a relation in relation list dialog
386     *
387     */
388    class EditRelationSelection extends SetRelationSelection {
389        public EditRelationSelection() {
390            putValue(NAME, tr("Call editor for relation"));
391            putValue(SHORT_DESCRIPTION, tr("Call relation editor for selected relation"));
392            putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
393            updateEnabledState();
394        }
395
396        @Override
397        public void actionPerformed(ActionEvent e) {
398            Relation relation = (Relation)model.getSelected().toArray()[0];
399            Collection<RelationMember> members = new HashSet<RelationMember>();
400            Collection<OsmPrimitive> selection = model.getAllElements();
401            for (RelationMember member: relation.getMembers()) {
402                if (selection.contains(member.getMember())) {
403                    members.add(member);
404                }
405            }
406            Main.map.relationListDialog.selectRelation(relation);
407            RelationEditor.getEditor(Main.map.mapView.getEditLayer(), relation,
408                    members).setVisible(true);
409        }
410    }
411
412    /**
413     * The action for setting a relation in relation list dialog
414     *
415     */
416    class SetRelationSelection extends AbstractAction implements ListSelectionListener{
417        public SetRelationSelection() {
418            putValue(NAME, tr("Select in relation list"));
419            putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
420            putValue(SMALL_ICON, ImageProvider.get("dialogs", "selectionlist"));
421            updateEnabledState();
422        }
423
424        public void actionPerformed(ActionEvent e) {
425            Relation relation = (Relation)model.getSelected().toArray()[0];
426            Main.map.relationListDialog.selectRelation(relation);
427        }
428
429        public void updateEnabledState() {
430            Object[] sel = model.getSelected().toArray();
431            setEnabled(sel.length == 1 && sel[0] instanceof Relation);
432        }
433
434        public void valueChanged(ListSelectionEvent e) {
435            updateEnabledState();
436        }
437    }
438
439    /**
440     * The list model for the list of OSM primitives in the current JOSM selection.
441     *
442     * The model also maintains a history of the last {@see SelectionListModel#SELECTION_HISTORY_SIZE}
443     * JOSM selection.
444     *
445     */
446    static private class SelectionListModel extends AbstractListModel implements EditLayerChangeListener, SelectionChangedListener, DataSetListener{
447
448        private static final int SELECTION_HISTORY_SIZE = 10;
449
450        // Variable to store history from currentDataSet()
451        private LinkedList<Collection<? extends OsmPrimitive>> history;
452        private final List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>();
453        private DefaultListSelectionModel selectionModel;
454
455        /**
456         * Constructor
457         * @param selectionModel the selection model used in the list
458         */
459        public SelectionListModel(DefaultListSelectionModel selectionModel) {
460            this.selectionModel = selectionModel;
461        }
462
463        /**
464         * Replies a summary of the current JOSM selection
465         *
466         * @return a summary of the current JOSM selection
467         */
468        public String getJOSMSelectionSummary() {
469            if (selection.isEmpty()) return tr("Selection");
470            int numNodes = 0;
471            int numWays = 0;
472            int numRelations = 0;
473            for (OsmPrimitive p: selection) {
474                switch(p.getType()) {
475                case NODE: numNodes++; break;
476                case WAY: numWays++; break;
477                case RELATION: numRelations++; break;
478                }
479            }
480            return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes);
481        }
482
483        /**
484         * Remembers a JOSM selection the history of JOSM selections
485         *
486         * @param selection the JOSM selection. Ignored if null or empty.
487         */
488        public void remember(Collection<? extends OsmPrimitive> selection) {
489            if (selection == null)return;
490            if (selection.isEmpty())return;
491            if (history == null) return;
492            if (history.isEmpty()) {
493                history.add(selection);
494                return;
495            }
496            if (history.getFirst().equals(selection)) return;
497            history.addFirst(selection);
498            for(int i = 1; i < history.size(); ++i) {
499                if(history.get(i).equals(selection)) {
500                    history.remove(i);
501                    break;
502                }
503            }
504            int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE);
505            while (history.size() > maxsize) {
506                history.removeLast();
507            }
508        }
509
510        /**
511         * Replies the history of JOSM selections
512         *
513         * @return
514         */
515        public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
516            return history;
517        }
518
519        public Object getElementAt(int index) {
520            return selection.get(index);
521        }
522
523        public int getSize() {
524            return selection.size();
525        }
526
527        /**
528         * Replies the collection of OSM primitives currently selected in the view
529         * of this model
530         *
531         * @return
532         */
533        public Collection<OsmPrimitive> getSelected() {
534            Set<OsmPrimitive> sel = new HashSet<OsmPrimitive>();
535            for(int i=0; i< getSize();i++) {
536                if (selectionModel.isSelectedIndex(i)) {
537                    sel.add(selection.get(i));
538                }
539            }
540            return sel;
541        }
542
543        /**
544         * Replies the collection of OSM primitives in the view
545         * of this model
546         *
547         * @return
548         */
549        public Collection<OsmPrimitive> getAllElements() {
550            return selection;
551        }
552
553        /**
554         * Sets the OSM primitives to be selected in the view of this model
555         *
556         * @param sel the collection of primitives to select
557         */
558        public void setSelected(Collection<OsmPrimitive> sel) {
559            selectionModel.clearSelection();
560            if (sel == null) return;
561            for (OsmPrimitive p: sel){
562                int i = selection.indexOf(p);
563                if (i >= 0){
564                    selectionModel.addSelectionInterval(i, i);
565                }
566            }
567        }
568
569        @Override
570        protected void fireContentsChanged(Object source, int index0, int index1) {
571            Collection<OsmPrimitive> sel = getSelected();
572            super.fireContentsChanged(source, index0, index1);
573            setSelected(sel);
574        }
575
576        /**
577         * Sets the collection of currently selected OSM objects
578         *
579         * @param selection the collection of currently selected OSM objects
580         */
581        public void setJOSMSelection(Collection<? extends OsmPrimitive> selection) {
582            this.selection.clear();
583            if (selection == null) {
584                fireContentsChanged(this, 0, getSize());
585                return;
586            }
587            this.selection.addAll(selection);
588            sort();
589            fireContentsChanged(this, 0, getSize());
590            remember(selection);
591            double dist = -1;
592            if(this.selection.size() == 1) {
593                OsmPrimitive o = this.selection.get(0);
594                if(o instanceof Way)
595                   dist = ((Way)o).getLength();
596            }
597            Main.map.statusLine.setDist(dist);
598        }
599
600        /**
601         * Triggers a refresh of the view for all primitives in {@code toUpdate}
602         * which are currently displayed in the view
603         *
604         * @param toUpdate the collection of primitives to update
605         */
606        public void update(Collection<? extends OsmPrimitive> toUpdate) {
607            if (toUpdate == null) return;
608            if (toUpdate.isEmpty()) return;
609            Collection<OsmPrimitive> sel = getSelected();
610            for (OsmPrimitive p: toUpdate){
611                int i = selection.indexOf(p);
612                if (i >= 0) {
613                    super.fireContentsChanged(this, i,i);
614                }
615            }
616            setSelected(sel);
617        }
618
619        /**
620         * Replies the list of selected relations with incomplete members
621         *
622         * @return the list of selected relations with incomplete members
623         */
624        public List<Relation> getSelectedRelationsWithIncompleteMembers() {
625            List<Relation> ret = new LinkedList<Relation>();
626            for(int i=0; i<getSize(); i++) {
627                if (!selectionModel.isSelectedIndex(i)) {
628                    continue;
629                }
630                OsmPrimitive p = selection.get(i);
631                if (! (p instanceof Relation)) {
632                    continue;
633                }
634                if (p.isNew()) {
635                    continue;
636                }
637                Relation r = (Relation)p;
638                if (r.hasIncompleteMembers()) {
639                    ret.add(r);
640                }
641            }
642            return ret;
643        }
644
645        /**
646         * Sorts the current elements in the selection
647         */
648        public void sort() {
649            if (this.selection.size()>Main.pref.getInteger("selection.no_sort_above",100000)) return;
650            if (this.selection.size()>Main.pref.getInteger("selection.fast_sort_above",10000)) {
651                Collections.sort(this.selection, new OsmPrimitiveQuickComparator());
652            } else {
653                Collections.sort(this.selection, new OsmPrimitiveComparator());
654            }
655        }
656
657        /* ------------------------------------------------------------------------ */
658        /* interface EditLayerChangeListener                                        */
659        /* ------------------------------------------------------------------------ */
660        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
661            if (newLayer == null) {
662                setJOSMSelection(null);
663                history = null;
664            } else {
665                history = newLayer.data.getSelectionHistory();
666                setJOSMSelection(newLayer.data.getSelected());
667            }
668        }
669
670        /* ------------------------------------------------------------------------ */
671        /* interface SelectionChangeListener                                        */
672        /* ------------------------------------------------------------------------ */
673        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
674            setJOSMSelection(newSelection);
675        }
676
677        /* ------------------------------------------------------------------------ */
678        /* interface DataSetListener                                                */
679        /* ------------------------------------------------------------------------ */
680        public void dataChanged(DataChangedEvent event) {
681            // refresh the whole list
682            fireContentsChanged(this, 0, getSize());
683        }
684
685        public void nodeMoved(NodeMovedEvent event) {
686            // may influence the display name of primitives, update the data
687            update(event.getPrimitives());
688        }
689
690        public void otherDatasetChange(AbstractDatasetChangedEvent event) {
691            // may influence the display name of primitives, update the data
692            update(event.getPrimitives());
693        }
694
695        public void relationMembersChanged(RelationMembersChangedEvent event) {
696            // may influence the display name of primitives, update the data
697            update(event.getPrimitives());
698        }
699
700        public void tagsChanged(TagsChangedEvent event) {
701            // may influence the display name of primitives, update the data
702            update(event.getPrimitives());
703        }
704
705        public void wayNodesChanged(WayNodesChangedEvent event) {
706            // may influence the display name of primitives, update the data
707            update(event.getPrimitives());
708        }
709
710        public void primitivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */}
711        public void primitivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/}
712    }
713
714    /**
715     * A specialized {@link JMenuItem} for presenting one entry of the search history
716     *
717     * @author Jan Peter Stotz
718     */
719    protected static class SearchMenuItem extends JMenuItem implements ActionListener {
720        final protected SearchSetting s;
721
722        public SearchMenuItem(SearchSetting s) {
723            super(s.toString());
724            this.s = s;
725            addActionListener(this);
726        }
727
728        public void actionPerformed(ActionEvent e) {
729            org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
730        }
731    }
732
733    /**
734     * The popup menu for the search history entries
735     *
736     */
737    protected static class SearchPopupMenu extends JPopupMenu {
738        static public void launch(Component parent) {
739            if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty())
740                return;
741            JPopupMenu menu = new SearchPopupMenu();
742            Rectangle r = parent.getBounds();
743            menu.show(parent, r.x, r.y + r.height);
744        }
745
746        public SearchPopupMenu() {
747            for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) {
748                add(new SearchMenuItem(ss));
749            }
750        }
751    }
752
753    /**
754     * A specialized {@link JMenuItem} for presenting one entry of the selection history
755     *
756     * @author Jan Peter Stotz
757     */
758    protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
759        final private DefaultNameFormatter df = DefaultNameFormatter.getInstance();
760        protected Collection<? extends OsmPrimitive> sel;
761
762        public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) {
763            super();
764            this.sel = sel;
765            int ways = 0;
766            int nodes = 0;
767            int relations = 0;
768            for (OsmPrimitive o : sel) {
769                if (! o.isSelectable()) continue; // skip unselectable primitives
770                if (o instanceof Way) {
771                    ways++;
772                } else if (o instanceof Node) {
773                    nodes++;
774                } else if (o instanceof Relation) {
775                    relations++;
776                }
777            }
778            StringBuffer text = new StringBuffer();
779            if(ways != 0) {
780                text.append(text.length() > 0 ? ", " : "")
781                .append(trn("{0} way", "{0} ways", ways, ways));
782            }
783            if(nodes != 0) {
784                text.append(text.length() > 0 ? ", " : "")
785                .append(trn("{0} node", "{0} nodes", nodes, nodes));
786            }
787            if(relations != 0) {
788                text.append(text.length() > 0 ? ", " : "")
789                .append(trn("{0} relation", "{0} relations", relations, relations));
790            }
791            if(ways + nodes + relations == 0) {
792                text.append(tr("Unselectable now"));
793                this.sel=new ArrayList<OsmPrimitive>(); // empty selection
794            }           
795            if(ways + nodes + relations == 1)
796            {
797                text.append(": ");
798                for(OsmPrimitive o : sel) {
799                    text.append(o.getDisplayName(df));
800                }
801                setText(text.toString());
802            } else {
803                setText(tr("Selection: {0}", text));
804            }
805            addActionListener(this);
806        }
807
808        public void actionPerformed(ActionEvent e) {
809            Main.main.getCurrentDataSet().setSelected(sel);
810        }
811    }
812
813    /**
814     * The popup menue for the JOSM selection history entries
815     *
816     */
817    protected static class SelectionHistoryPopup extends JPopupMenu {
818        static public void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
819            if (history == null || history.isEmpty()) return;
820            JPopupMenu menu = new SelectionHistoryPopup(history);
821            Rectangle r = parent.getBounds();
822            menu.show(parent, r.x, r.y + r.height);
823        }
824
825        public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
826            for (Collection<? extends OsmPrimitive> sel : history) {
827                add(new SelectionMenuItem(sel));
828            }
829        }
830    }
831
832    /**
833     * Action for downloading incomplete members of selected relations
834     *
835     */
836    class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener {
837        public DownloadSelectedIncompleteMembersAction() {
838            putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
839            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
840            putValue(NAME, tr("Download incomplete members"));
841            updateEnabledState();
842        }
843
844        public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) {
845            Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
846            for(Relation r: rels) {
847                ret.addAll(r.getIncompleteMembers());
848            }
849            return ret;
850        }
851
852        public void actionPerformed(ActionEvent e) {
853            if (!isEnabled())
854                return;
855            List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers();
856            if (rels.isEmpty()) return;
857            Main.worker.submit(new DownloadRelationMemberTask(
858                    rels,
859                    buildSetOfIncompleteMembers(rels),
860                    Main.map.mapView.getEditLayer()
861            ));
862        }
863
864        protected void updateEnabledState() {
865            setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty());
866        }
867
868        public void valueChanged(ListSelectionEvent e) {
869            updateEnabledState();
870        }
871    }
872
873    /** Quicker comparator, comparing just by type and ID's */
874    static private class OsmPrimitiveQuickComparator implements Comparator<OsmPrimitive> {
875
876        private int compareId(OsmPrimitive a, OsmPrimitive b) {
877            long id_a=a.getUniqueId();
878            long id_b=b.getUniqueId();
879            if (id_a<id_b) return -1;
880            if (id_a>id_b) return 1;
881            return 0;
882        }
883
884        private int compareType(OsmPrimitive a, OsmPrimitive b) {
885            // show ways before relations, then nodes
886            if (a.getType().equals(OsmPrimitiveType.WAY)) return -1;
887            if (a.getType().equals(OsmPrimitiveType.NODE)) return 1;
888            // a is a relation
889            if (b.getType().equals(OsmPrimitiveType.WAY)) return 1;
890            // b is a node
891            return -1;
892        }
893
894        public int compare(OsmPrimitive a, OsmPrimitive b) {
895            if (a.getType().equals(b.getType()))
896                return compareId(a, b);
897            return compareType(a, b);
898        }
899    }
900
901}
Note: See TracBrowser for help on using the repository browser.