Ignore:
Timestamp:
2010-03-10T10:00:20+01:00 (14 years ago)
Author:
Gubaer
Message:

fixed #4651: Ability to download incomplete relation from selection
fixed #4098: Popup Menu entry "download relation members" in relation dialog should be "download incomplete relation members"
fixed two NPEs in RelationListDialog and SelectionListDialog
refactored SelectionListDialog to support better user feedback (enabled/disabled buttons and menu items)
Finally removed the sort() method on DataSet, marked as FIXME since a long time.

CAVEAT: DataSet.getSelected() now returns an unmodifiable list instead of a copy of the selection list. This may lead to UnsupportedOperationExceptions in the next few days. I tried to make sure the JOSM core uses getSelected() only for reading, but I didn't check the plugins.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java

    r3101 r3102  
    22package org.openstreetmap.josm.gui.dialogs;
    33
    4 import static org.openstreetmap.josm.tools.I18n.marktr;
    54import static org.openstreetmap.josm.tools.I18n.tr;
    65import static org.openstreetmap.josm.tools.I18n.trn;
     
    87import java.awt.BorderLayout;
    98import java.awt.Color;
     9import java.awt.Component;
     10import java.awt.GridLayout;
    1011import java.awt.Rectangle;
    1112import java.awt.event.ActionEvent;
    1213import java.awt.event.ActionListener;
    1314import java.awt.event.KeyEvent;
    14 import java.awt.event.MouseAdapter;
    1515import java.awt.event.MouseEvent;
     16import java.util.ArrayList;
    1617import java.util.Collection;
    1718import java.util.Collections;
     19import java.util.Comparator;
     20import java.util.HashSet;
    1821import java.util.LinkedList;
    19 import java.util.NoSuchElementException;
     22import java.util.List;
     23import java.util.Set;
    2024
    2125import javax.swing.AbstractAction;
     26import javax.swing.AbstractListModel;
    2227import javax.swing.BorderFactory;
    23 import javax.swing.DefaultListModel;
     28import javax.swing.DefaultListSelectionModel;
     29import javax.swing.JButton;
    2430import javax.swing.JList;
    2531import javax.swing.JMenuItem;
     
    2935import javax.swing.ListSelectionModel;
    3036import javax.swing.SwingConstants;
     37import javax.swing.event.ListDataEvent;
     38import javax.swing.event.ListDataListener;
     39import javax.swing.event.ListSelectionEvent;
     40import javax.swing.event.ListSelectionListener;
    3141import javax.swing.plaf.basic.BasicArrowButton;
    3242
    3343import org.openstreetmap.josm.Main;
    3444import org.openstreetmap.josm.actions.AutoScaleAction;
    35 import org.openstreetmap.josm.actions.search.SearchAction;
    3645import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
    3746import org.openstreetmap.josm.data.SelectionChangedListener;
    38 import org.openstreetmap.josm.data.osm.DataSet;
     47import org.openstreetmap.josm.data.osm.NameFormatter;
    3948import org.openstreetmap.josm.data.osm.Node;
    4049import org.openstreetmap.josm.data.osm.OsmPrimitive;
    4150import org.openstreetmap.josm.data.osm.Relation;
    4251import org.openstreetmap.josm.data.osm.Way;
     52import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
     53import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
     54import org.openstreetmap.josm.data.osm.event.DataSetListener;
     55import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
     56import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
     57import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
     58import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
     59import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
    4360import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
     61import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
     62import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
    4463import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
    4564import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
     65import org.openstreetmap.josm.gui.DefaultNameFormatter;
    4666import org.openstreetmap.josm.gui.MapView;
    4767import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
    4868import org.openstreetmap.josm.gui.SideButton;
    4969import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
     70import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
    5071import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     72import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    5173import org.openstreetmap.josm.tools.ImageProvider;
    5274import org.openstreetmap.josm.tools.Shortcut;
    5375
    5476/**
    55  * A small tool dialog for displaying the current selection. The selection manager
    56  * respects clicks into the selection list. Ctrl-click will remove entries from
    57  * the list while single click will make the clicked entry the only selection.
    58  *
    59  * @author imi
     77 * A small tool dialog for displaying the current selection.
     78 *
    6079 */
    61 public class SelectionListDialog extends ToggleDialog implements SelectionChangedListener, MapView.EditLayerChangeListener {
    62 
    63     private static final int SELECTION_HISTORY_SIZE = 10;
    64 
    65     /**
    66      * The selection's list data.
    67      */
    68     private final DefaultListModel list = new DefaultListModel();
    69 
    70     private LinkedList<Collection<? extends OsmPrimitive>> selectionHistory;
    71 
    72     /**
    73      * The display list.
    74      */
    75     private JList displaylist = new JList(list);
    76     private SideButton selectButton;
    77     private SideButton searchButton;
    78     private JPopupMenu popupMenu;
    79     private JMenuItem zoomToElement;
     80public class SelectionListDialog extends ToggleDialog  {
     81
     82    private JList lstPrimitives;
     83    private SelectionListModel model;
    8084
    8185    private SelectAction actSelect;
    82 
    83     /**
    84      * If the selection changed event is triggered with newSelection equals
    85      * this element, the newSelection will not be added to the selection history
    86      */
    87     private Collection<? extends OsmPrimitive> historyIgnoreSelection = null;
    88 
    89     public SelectionListDialog() {
    90         super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
    91                 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
    92 
    93         selectionHistory = new LinkedList<Collection<? extends OsmPrimitive>>();
    94         popupMenu = new JPopupMenu();
    95         displaylist.setCellRenderer(new OsmPrimitivRenderer());
    96         displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    97         displaylist.addMouseListener(new MouseAdapter() {
    98             @Override
    99             public void mouseClicked(MouseEvent e) {
    100                 if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
    101                     updateMap();
    102                 }
    103             }
    104 
    105             @Override
    106             public void mousePressed(MouseEvent e) {
    107                 showPopupMenu(e);
    108             }
    109 
    110             @Override
    111             public void mouseReleased(MouseEvent e) {
    112                 showPopupMenu(e);
    113             }
    114 
    115         });
    116 
    117         add(new JScrollPane(displaylist), BorderLayout.CENTER);
    118 
    119         JPanel buttonPanel = getButtonPanel(2);
    120         selectButton = new SideButton(actSelect = new SelectAction());
    121         buttonPanel.add(selectButton);
     86    private SearchAction actSearch;
     87    private ZoomToJOSMSelectionAction actZoomToJOSMSelection;
     88    private ZoomToListSelection actZoomToListSelection;
     89    private DownloadSelectedIncompleteMembersAction actDownloadSelectedIncompleteMembers;
     90
     91    /**
     92     * Builds the panel with the list of selected OSM primitives
     93     *
     94     * @return the panel with the list of selected OSM primitives
     95     */
     96    protected JPanel buildListPanel() {
     97        JPanel pnl = new JPanel(new BorderLayout());
     98        DefaultListSelectionModel selectionModel  = new DefaultListSelectionModel();
     99        model = new SelectionListModel(selectionModel);
     100        lstPrimitives = new JList(model);
     101        lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
     102        lstPrimitives.setSelectionModel(selectionModel);
     103        lstPrimitives.setCellRenderer(new OsmPrimitivRenderer());
     104        pnl.add(new JScrollPane(lstPrimitives), BorderLayout.CENTER);
     105
     106        return pnl;
     107    }
     108
     109    /**
     110     * Builds the row of action buttons at the bottom of this dialog
     111     *
     112     * @return the panel
     113     */
     114    protected JPanel buildActionPanel() {
     115        JPanel pnl = new  JPanel(new GridLayout(1,2));
     116
     117        // the select action
     118        final JButton selectButton = new SideButton(actSelect = new SelectAction());
     119        lstPrimitives.getSelectionModel().addListSelectionListener(actSelect);
     120        pnl.add(selectButton);
    122121        BasicArrowButton selectionHistoryMenuButton = createArrowButton(selectButton);
    123122        selectionHistoryMenuButton.addActionListener(new ActionListener() {
    124123            public void actionPerformed(ActionEvent e) {
    125                 showSelectionHistoryMenu();
     124                SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory());
    126125            }
    127126        });
    128         add(buttonPanel, BorderLayout.SOUTH);
    129 
    130         zoomToElement = new JMenuItem(tr("Zoom to selected element(s)"));
    131         zoomToElement.addActionListener(new ActionListener() {
    132             public void actionPerformed(ActionEvent e) {
    133                 zoomToSelectedElement();
    134             }
    135         });
    136 
    137         searchButton = new SideButton(marktr("Search"), "search", "SelectionList", tr("Search for objects."),
    138                 Main.main.menu.search);
    139         buttonPanel.add(searchButton);
     127
     128        // the search button
     129        final JButton searchButton = new SideButton(actSearch = new SearchAction());
     130        pnl.add(searchButton);
    140131
    141132        BasicArrowButton searchHistoryMenuButton = createArrowButton(searchButton);
    142133        searchHistoryMenuButton.addActionListener(new ActionListener() {
    143134            public void actionPerformed(ActionEvent e) {
    144                 showSearchHistoryMenu();
     135                SearchPopupMenu.launch(searchButton);
    145136            }
    146137        });
    147138
    148         popupMenu.add(zoomToElement);
    149         JMenuItem zoomToSelection = new JMenuItem(tr("Zoom to selection"));
    150         zoomToSelection.addActionListener(new ActionListener() {
    151             public void actionPerformed(ActionEvent e) {
    152                 zoomToSelection();
    153             }
    154         });
    155         popupMenu.add(zoomToSelection);
    156 
    157         if (Main.main.getCurrentDataSet() != null) {
    158             selectionChanged(Main.main.getCurrentDataSet().getSelected());
    159         }
    160 
     139        return pnl;
     140    }
     141
     142    /**
     143     * Builds the content panel for this dialog
     144     *
     145     * @return the content panel
     146     */
     147    protected JPanel buildContentPanel() {
     148        JPanel pnl = new JPanel(new BorderLayout());
     149        pnl.add(buildListPanel(), BorderLayout.CENTER);
     150        pnl.add(buildActionPanel(), BorderLayout.SOUTH);
     151        return pnl;
     152    }
     153
     154    public SelectionListDialog() {
     155        super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
     156                Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT),
     157                150, // default height
     158                true // default is "show dialog"
     159        );
     160
     161        add(buildContentPanel(), BorderLayout.CENTER);
     162        model.addListDataListener(new TitleUpdater());
     163        actZoomToJOSMSelection = new ZoomToJOSMSelectionAction();
     164        model.addListDataListener(actZoomToJOSMSelection);
     165
     166        actZoomToListSelection = new ZoomToListSelection();
     167        lstPrimitives.getSelectionModel().addListSelectionListener(actZoomToListSelection);
     168
     169        actDownloadSelectedIncompleteMembers = new DownloadSelectedIncompleteMembersAction();
     170        lstPrimitives.getSelectionModel().addListSelectionListener(actDownloadSelectedIncompleteMembers);
     171
     172        lstPrimitives.addMouseListener(new SelectionPopupMenuLauncher());
    161173    }
    162174
    163175    @Override
    164176    public void showNotify() {
    165         SelectionEventManager.getInstance().addSelectionListener(this, FireMode.IN_EDT_CONSOLIDATED);
    166         MapView.addEditLayerChangeListener(this);
    167         MapView.addEditLayerChangeListener(actSelect);
    168         updateSelection();
     177        MapView.addEditLayerChangeListener(model);
     178        SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
     179        DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
     180        MapView.addEditLayerChangeListener(actSearch);
    169181    }
    170182
    171183    @Override
    172184    public void hideNotify() {
    173         SelectionEventManager.getInstance().removeSelectionListener(this);
    174         MapView.removeEditLayerChangeListener(this);
    175         MapView.removeEditLayerChangeListener(actSelect);
    176         updateTitle(0, 0, 0);
    177     }
    178 
    179     private BasicArrowButton createArrowButton(SideButton parentButton) {
     185        MapView.removeEditLayerChangeListener(actSearch);
     186        MapView.removeEditLayerChangeListener(model);
     187        SelectionEventManager.getInstance().removeSelectionListener(model);
     188        DatasetEventManager.getInstance().removeDatasetListener(model);
     189    }
     190
     191    private BasicArrowButton createArrowButton(JButton parentButton) {
    180192        BasicArrowButton arrowButton = new BasicArrowButton(SwingConstants.SOUTH, null, null, Color.BLACK, null);
    181193        arrowButton.setBorder(BorderFactory.createEmptyBorder());
     
    185197    }
    186198
    187     @Override
    188     public void setVisible(boolean b) {
    189         super.setVisible(b);
    190         if (b && Main.main.getCurrentDataSet() != null) {
    191             selectionChanged(Main.main.getCurrentDataSet().getSelected());
    192         }
    193     }
    194 
    195     protected void showPopupMenu(MouseEvent e) {
    196         if (e.isPopupTrigger()) {
    197             zoomToElement.setVisible(displaylist.getSelectedIndex() >= 0);
    198             popupMenu.show(e.getComponent(), e.getX(), e.getY());
    199         }
    200     }
    201 
    202     public void zoomToSelection() {
    203         new AutoScaleAction("selection").actionPerformed(null);
    204     }
    205 
    206     /**
    207      * Zooms to the element(s) selected in {@link #displaylist}
    208      */
    209     public void zoomToSelectedElement() {
    210         BoundingXYVisitor box = new BoundingXYVisitor();
    211         int[] selected = displaylist.getSelectedIndices();
    212         if (selected.length == 0)
    213             return;
    214         for (int i = 0; i < selected.length; i++) {
    215             Object o = list.get(selected[i]);
    216             if (o instanceof OsmPrimitive) {
    217                 ((OsmPrimitive) o).visit(box);
    218             }
    219         }
    220         if (box.getBounds() == null)
    221             return;
    222         box.enlargeBoundingBox();
    223         Main.map.mapView.recalculateCenterScale(box);
    224     }
    225 
    226     private void showSelectionHistoryMenu() {
    227         if (selectionHistory.size() == 0)
    228             return;
    229         JPopupMenu historyMenu = new JPopupMenu();
    230         for (Collection<? extends OsmPrimitive> sel : selectionHistory) {
    231             SelectionMenuItem item = new SelectionMenuItem(sel);
    232             historyMenu.add(item);
    233         }
    234         Rectangle r = selectButton.getBounds();
    235         historyMenu.show(selectButton, r.x, r.y + r.height);
    236     }
    237 
    238     private void showSearchHistoryMenu() {
    239         if (SearchAction.searchHistory.size() == 0)
    240             return;
    241         JPopupMenu historyMenu = new JPopupMenu();
    242         for (SearchAction.SearchSetting s : SearchAction.searchHistory) {
    243             SearchMenuItem item = new SearchMenuItem(s);
    244             historyMenu.add(item);
    245         }
    246         Rectangle r = searchButton.getBounds();
    247         historyMenu.show(searchButton, r.x, r.y + r.height);
    248     }
    249 
    250     /**
    251      * Called when the selection in the dataset changed.
    252      * @param newSelection The new selection array.
    253      */
    254     public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
    255         if (list == null || !isVisible())
    256             return; // selection changed may be received in base class constructor before init
    257         OsmPrimitive selArr[] = DataSet.sort(newSelection);
    258         list.setSize(selArr.length);
    259         int i = 0;
    260         for (OsmPrimitive osm : selArr) {
    261             list.setElementAt(osm, i++);
    262         }
    263 
    264         int ways = 0;
    265         int nodes = 0;
    266         int relations = 0;
    267         for (OsmPrimitive o : newSelection) {
    268             if (o instanceof Way) {
    269                 ways++;
    270             } else if (o instanceof Node) {
    271                 nodes++;
    272             } else if (o instanceof Relation) {
    273                 relations++;
    274             }
    275         }
    276 
    277         updateTitle(nodes, ways, relations);
    278 
    279         if (selectionHistory != null && newSelection.size() > 0 && !newSelection.equals(historyIgnoreSelection)) {
    280             historyIgnoreSelection = null;
    281             try {
    282                 // Check if the newSelection has already been added to the history
    283                 Collection<? extends OsmPrimitive> first = selectionHistory.getFirst();
    284                 if (first.equals(newSelection))
    285                     return;
    286             } catch (NoSuchElementException e) {
    287             }
    288             selectionHistory.addFirst(newSelection);
    289             while (selectionHistory.size() > SELECTION_HISTORY_SIZE) {
    290                 selectionHistory.removeLast();
    291             }
    292         }
    293     }
    294 
    295     private void updateTitle(int nodes, int ways, int relations) {
    296         if( (nodes+ways+relations) != 0) {
    297             setTitle(tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", relations, ways, nodes));
    298         } else {
    299             setTitle(tr("Selection"));
    300         }
    301     }
    302 
    303     /**
    304      * Sets the selection of the map to the current selected items.
    305      */
    306     public void updateMap() {
    307         Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
    308         for (int i = 0; i < list.getSize(); ++i)
    309             if (displaylist.isSelectedIndex(i)) {
    310                 sel.add((OsmPrimitive) list.get(i));
    311             }
    312         Main.main.getCurrentDataSet().setSelected(sel);
     199    /**
     200     * The popup menu launcher
     201     */
     202    class SelectionPopupMenuLauncher extends PopupMenuLauncher {
     203        private SelectionPopup popup = new SelectionPopup();
     204
     205        @Override
     206        public void launch(MouseEvent evt) {
     207            if (model.getSelected().isEmpty()) {
     208                int idx = lstPrimitives.locationToIndex(evt.getPoint());
     209                if (idx < 0) return;
     210                model.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx)));
     211            }
     212            popup.show(lstPrimitives, evt.getX(), evt.getY());
     213        }
     214    }
     215
     216    /**
     217     * The popup menu for the selection list
     218     */
     219    class SelectionPopup extends JPopupMenu {
     220        public SelectionPopup() {
     221            add(actZoomToJOSMSelection);
     222            add(actZoomToListSelection);
     223            addSeparator();
     224            add(actDownloadSelectedIncompleteMembers);
     225        }
     226    }
     227
     228    /**
     229     * Updates the dialog title with a summary of the current JOSM selection
     230     */
     231    class TitleUpdater implements ListDataListener {
     232        protected void updateTitle() {
     233            setTitle(model.getJOSMSelectionSummary());
     234        }
     235
     236        public void contentsChanged(ListDataEvent e) {
     237            updateTitle();
     238        }
     239
     240        public void intervalAdded(ListDataEvent e) {
     241            updateTitle();
     242        }
     243
     244        public void intervalRemoved(ListDataEvent e) {
     245            updateTitle();
     246        }
     247    }
     248
     249    /**
     250     * Launches the search dialog
     251     */
     252    class SearchAction extends AbstractAction implements EditLayerChangeListener {
     253        public SearchAction() {
     254            putValue(NAME, tr("Search"));
     255            putValue(SHORT_DESCRIPTION,   tr("Search for objects"));
     256            putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
     257            updateEnabledState();
     258        }
     259
     260        public void actionPerformed(ActionEvent e) {
     261            if (!isEnabled()) return;
     262            org.openstreetmap.josm.actions.search.SearchAction.search();
     263        }
     264
     265        public void updateEnabledState() {
     266            setEnabled(Main.main != null && Main.main.getEditLayer() != null);
     267        }
     268
     269        @Override
     270        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
     271            updateEnabledState();
     272        }
     273    }
     274
     275    /**
     276     * Sets the current JOSM selection to the OSM primitives selected in the list
     277     * of this dialog
     278     */
     279    class SelectAction extends AbstractAction implements ListSelectionListener {
     280        public SelectAction() {
     281            putValue(NAME, tr("Select"));
     282            putValue(SHORT_DESCRIPTION,  tr("Set the selected elements on the map to the selected items in the list above."));
     283            putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
     284            updateEnabledState();
     285        }
     286
     287        @Override
     288        public void actionPerformed(ActionEvent e) {
     289            Collection<OsmPrimitive> sel = model.getSelected();
     290            if (sel.isEmpty())return;
     291            if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return;
     292            Main.map.mapView.getEditLayer().data.setSelected(sel);
     293        }
     294
     295        public void updateEnabledState() {
     296            setEnabled(!model.getSelected().isEmpty());
     297        }
     298
     299        public void valueChanged(ListSelectionEvent e) {
     300            updateEnabledState();
     301        }
     302    }
     303
     304    /**
     305     * The action for zooming to the primitives in the current JOSM selection
     306     *
     307     */
     308    class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener {
     309
     310        public ZoomToJOSMSelectionAction() {
     311            putValue(NAME,tr("Zoom to selection"));
     312            putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
     313            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
     314            updateEnabledState();
     315        }
     316        @Override
     317        public void actionPerformed(ActionEvent e) {
     318            new AutoScaleAction("selection").autoScale();
     319        }
     320
     321        public void updateEnabledState() {
     322            setEnabled(model.getSize() > 0);
     323        }
     324
     325        public void contentsChanged(ListDataEvent e) {
     326            updateEnabledState();
     327        }
     328
     329        public void intervalAdded(ListDataEvent e) {
     330            updateEnabledState();
     331        }
     332
     333        public void intervalRemoved(ListDataEvent e) {
     334            updateEnabledState();
     335        }
     336    }
     337
     338    /**
     339     * The action for zooming to the primitives which are currently selected in
     340     * the list displaying the JOSM selection
     341     *
     342     */
     343    class ZoomToListSelection extends AbstractAction implements ListSelectionListener{
     344        public ZoomToListSelection() {
     345            putValue(NAME, tr("Zoom to selected element(s)"));
     346            putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)"));
     347            putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
     348            updateEnabledState();
     349        }
     350
     351        public void actionPerformed(ActionEvent e) {
     352            BoundingXYVisitor box = new BoundingXYVisitor();
     353            Collection<OsmPrimitive> sel = model.getSelected();
     354            if (sel.isEmpty()) return;
     355            box.computeBoundingBox(sel);
     356            if (box.getBounds() == null)
     357                return;
     358            box.enlargeBoundingBox();
     359            Main.map.mapView.recalculateCenterScale(box);
     360        }
     361
     362        public void updateEnabledState() {
     363            setEnabled(!model.getSelected().isEmpty());
     364        }
     365
     366        public void valueChanged(ListSelectionEvent e) {
     367            updateEnabledState();
     368        }
     369    }
     370
     371    /**
     372     * The list model for the list of OSM primitives in the current JOSM selection.
     373     *
     374     * The model also maintains a history of the last {@see SelectionListModel#SELECTION_HISTORY_SIZE}
     375     * JOSM selection.
     376     *
     377     */
     378    static private class SelectionListModel extends AbstractListModel implements EditLayerChangeListener, SelectionChangedListener, DataSetListener{
     379
     380        private static final int SELECTION_HISTORY_SIZE = 10;
     381
     382        private final LinkedList<Collection<? extends OsmPrimitive>> history = new LinkedList<Collection<? extends OsmPrimitive>>();
     383        private final List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>();
     384        private DefaultListSelectionModel selectionModel;
     385
     386        /**
     387         * Constructor
     388         * @param selectionModel the selection model used in the list
     389         */
     390        public SelectionListModel(DefaultListSelectionModel selectionModel) {
     391            this.selectionModel = selectionModel;
     392        }
     393
     394        /**
     395         * Replies a summary of the current JOSM selection
     396         *
     397         * @return a summary of the current JOSM selection
     398         */
     399        public String getJOSMSelectionSummary() {
     400            if (selection.isEmpty()) return tr("Selection");
     401            int numNodes = 0;
     402            int numWays = 0;
     403            int numRelations = 0;
     404            for (OsmPrimitive p: selection) {
     405                switch(p.getType()) {
     406                case NODE: numNodes++; break;
     407                case WAY: numWays++; break;
     408                case RELATION: numRelations++; break;
     409                }
     410            }
     411            return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes);
     412        }
     413
     414        /**
     415         * Remembers a JOSM selection the history of JOSM selections
     416         *
     417         * @param selection the JOSM selection. Ignored if null or empty.
     418         */
     419        public void remember(Collection<? extends OsmPrimitive> selection) {
     420            if (selection == null)return;
     421            if (selection.isEmpty())return;
     422            if (history.isEmpty()) {
     423                history.add(selection);
     424                return;
     425            }
     426            if (history.getFirst().equals(selection)) return;
     427            history.addFirst(selection);
     428            while (history.size() > SELECTION_HISTORY_SIZE) {
     429                history.removeLast();
     430            }
     431        }
     432
     433        /**
     434         * Replies the history of JOSM selections
     435         *
     436         * @return
     437         */
     438        public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
     439            return history;
     440        }
     441
     442        @Override
     443        public Object getElementAt(int index) {
     444            return selection.get(index);
     445        }
     446
     447        @Override
     448        public int getSize() {
     449            return selection.size();
     450        }
     451
     452        /**
     453         * Replies the collection of OSM primitives currently selected in the view
     454         * of this model
     455         *
     456         * @return
     457         */
     458        public Collection<OsmPrimitive> getSelected() {
     459            Set<OsmPrimitive> sel = new HashSet<OsmPrimitive>();
     460            for(int i=0; i< getSize();i++) {
     461                if (selectionModel.isSelectedIndex(i)) {
     462                    sel.add(selection.get(i));
     463                }
     464            }
     465            return sel;
     466        }
     467
     468        /**
     469         * Sets the OSM primitives to be selected in the view of this model
     470         *
     471         * @param sel the collection of primitives to select
     472         */
     473        public void setSelected(Collection<OsmPrimitive> sel) {
     474            selectionModel.clearSelection();
     475            if (sel == null) return;
     476            for (OsmPrimitive p: sel){
     477                int i = selection.indexOf(p);
     478                if (i >= 0){
     479                    selectionModel.addSelectionInterval(i, i);
     480                }
     481            }
     482        }
     483
     484        @Override
     485        protected void fireContentsChanged(Object source, int index0, int index1) {
     486            Collection<OsmPrimitive> sel = getSelected();
     487            super.fireContentsChanged(source, index0, index1);
     488            setSelected(sel);
     489        }
     490
     491        /**
     492         * Sets the collection of currently selected OSM objects
     493         *
     494         * @param selection the collection of currently selected OSM objects
     495         */
     496        public void setJOSMSelection(Collection<? extends OsmPrimitive> selection) {
     497            this.selection.clear();
     498            if (selection == null) {
     499                fireContentsChanged(this, 0, getSize());
     500                return;
     501            }
     502            this.selection.addAll(selection);
     503            sort();
     504            fireContentsChanged(this, 0, getSize());
     505            remember(selection);
     506        }
     507
     508        /**
     509         * Sorts the primitives in the list
     510         */
     511        public void sort() {
     512            Collections.sort(
     513                    this.selection,
     514                    new Comparator<OsmPrimitive>() {
     515                        NameFormatter nf = DefaultNameFormatter.getInstance();
     516                        @Override
     517                        public int compare(OsmPrimitive o1, OsmPrimitive o2) {
     518
     519                            if (o1.getType() != o2.getType())
     520                                return o1.getType().compareTo(o2.getType());
     521                            return o1.getDisplayName(nf).compareTo(o2.getDisplayName(nf));
     522                        }
     523                    }
     524            );
     525        }
     526
     527        /**
     528         * Triggers a refresh of the view for all primitives in {@code toUpdate}
     529         * which are currently displayed in the view
     530         *
     531         * @param toUpdate the collection of primitives to update
     532         */
     533        public void update(Collection<? extends OsmPrimitive> toUpdate) {
     534            if (toUpdate == null) return;
     535            if (toUpdate.isEmpty()) return;
     536            Collection<OsmPrimitive> sel = getSelected();
     537            for (OsmPrimitive p: toUpdate){
     538                int i = selection.indexOf(p);
     539                if (i >= 0) {
     540                    super.fireContentsChanged(this, i,i);
     541                }
     542            }
     543            setSelected(sel);
     544        }
     545
     546        /**
     547         * Replies the list of selected relations with incomplete members
     548         *
     549         * @return the list of selected relations with incomplete members
     550         */
     551        public List<Relation> getSelectedRelationsWithIncompleteMembers() {
     552            List<Relation> ret = new LinkedList<Relation>();
     553            for(int i=0; i<getSize(); i++) {
     554                if (!selectionModel.isSelectedIndex(i)) {
     555                    continue;
     556                }
     557                OsmPrimitive p = selection.get(i);
     558                if (! (p instanceof Relation)) {
     559                    continue;
     560                }
     561                if (p.isNew()) {
     562                    continue;
     563                }
     564                Relation r = (Relation)p;
     565                if (r.hasIncompleteMembers()) {
     566                    ret.add(r);
     567                }
     568            }
     569            return ret;
     570        }
     571
     572        /* ------------------------------------------------------------------------ */
     573        /* interface EditLayerChangeListener                                        */
     574        /* ------------------------------------------------------------------------ */
     575        public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
     576            if (newLayer == null) {
     577                setJOSMSelection(null);
     578            } else {
     579                setJOSMSelection(newLayer.data.getSelected());
     580            }
     581        }
     582
     583        /* ------------------------------------------------------------------------ */
     584        /* interface SelectionChangeListener                                        */
     585        /* ------------------------------------------------------------------------ */
     586        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     587            setJOSMSelection(newSelection);
     588        }
     589
     590        /* ------------------------------------------------------------------------ */
     591        /* interface DataSetListener                                                */
     592        /* ------------------------------------------------------------------------ */
     593        public void dataChanged(DataChangedEvent event) {
     594            // refresh the whole list
     595            fireContentsChanged(this, 0, getSize());
     596        }
     597
     598        public void nodeMoved(NodeMovedEvent event) {
     599            // may influence the display name of primitives, update the data
     600            update(event.getPrimitives());
     601        }
     602
     603        public void otherDatasetChange(AbstractDatasetChangedEvent event) {
     604            // may influence the display name of primitives, update the data
     605            update(event.getPrimitives());
     606        }
     607
     608        public void relationMembersChanged(RelationMembersChangedEvent event) {
     609            // may influence the display name of primitives, update the data
     610            update(event.getPrimitives());
     611        }
     612
     613        public void tagsChanged(TagsChangedEvent event) {
     614            // may influence the display name of primitives, update the data
     615            update(event.getPrimitives());
     616        }
     617
     618        public void wayNodesChanged(WayNodesChangedEvent event) {
     619            // may influence the display name of primitives, update the data
     620            update(event.getPrimitives());
     621        }
     622
     623        @Override
     624        public void primtivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */}
     625        @Override
     626        public void primtivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/}
     627    }
     628
     629    /**
     630     * A specialized {@link JMenuItem} for presenting one entry of the search history
     631     *
     632     * @author Jan Peter Stotz
     633     */
     634    protected static class SearchMenuItem extends JMenuItem implements ActionListener {
     635        protected SearchSetting s;
     636
     637        public SearchMenuItem(SearchSetting s) {
     638            super(s.toString());
     639            this.s = s;
     640            addActionListener(this);
     641        }
     642
     643        public void actionPerformed(ActionEvent e) {
     644            org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
     645        }
     646    }
     647
     648    /**
     649     * The popup menu for the search history entries
     650     *
     651     */
     652    protected static class SearchPopupMenu extends JPopupMenu {
     653        static public void launch(Component parent) {
     654            if (org.openstreetmap.josm.actions.search.SearchAction.searchHistory.isEmpty())
     655                return;
     656            JPopupMenu menu = new SearchPopupMenu();
     657            Rectangle r = parent.getBounds();
     658            menu.show(parent, r.x, r.y + r.height);
     659        }
     660
     661        public SearchPopupMenu() {
     662            for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.searchHistory) {
     663                add(new SearchMenuItem(ss));
     664            }
     665        }
    313666    }
    314667
     
    318671     * @author Jan Peter Stotz
    319672     */
    320     protected class SelectionMenuItem extends JMenuItem implements ActionListener {
     673    protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
    321674        protected Collection<? extends OsmPrimitive> sel;
    322675
     
    336689                }
    337690            }
    338             String text = "";
     691            StringBuffer text = new StringBuffer();
    339692            if(ways != 0) {
    340                 text += (text.length() > 0 ? ", " : "")
    341                 + trn("{0} way", "{0} ways", ways, ways);
     693                text.append(text.length() > 0 ? ", " : "")
     694                .append(trn("{0} way", "{0} ways", ways, ways));
    342695            }
    343696            if(nodes != 0) {
    344                 text += (text.length() > 0 ? ", " : "")
    345                 + trn("{0} node", "{0} nodes", nodes, nodes);
     697                text.append(text.length() > 0 ? ", " : "")
     698                .append(trn("{0} node", "{0} nodes", nodes, nodes));
    346699            }
    347700            if(relations != 0) {
    348                 text += (text.length() > 0 ? ", " : "")
    349                 + trn("{0} relation", "{0} relations", relations, relations);
     701                text.append(text.length() > 0 ? ", " : "")
     702                .append(trn("{0} relation", "{0} relations", relations, relations));
    350703            }
    351704            setText(tr("Selection: {0}", text));
     
    354707
    355708        public void actionPerformed(ActionEvent e) {
    356             historyIgnoreSelection = sel;
    357709            Main.main.getCurrentDataSet().setSelected(sel);
    358710        }
    359 
    360     }
    361 
    362     /**
    363      * A specialized {@link JMenuItem} for presenting one entry of the search history
     711    }
     712
     713    /**
     714     * The popup menue for the JOSM selection history entries
    364715     *
    365      * @author Jan Peter Stotz
    366      */
    367     protected static class SearchMenuItem extends JMenuItem implements ActionListener {
    368         protected SearchSetting s;
    369 
    370         public SearchMenuItem(SearchSetting s) {
    371             super(s.toString());
    372             this.s = s;
    373             addActionListener(this);
     716     */
     717    protected static class SelectionHistoryPopup extends JPopupMenu {
     718        static public void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
     719            if (history == null || history.isEmpty()) return;
     720            JPopupMenu menu = new SelectionHistoryPopup(history);
     721            Rectangle r = parent.getBounds();
     722            menu.show(parent, r.x, r.y + r.height);
     723        }
     724
     725        public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
     726            for (Collection<? extends OsmPrimitive> sel : history) {
     727                add(new SelectionMenuItem(sel));
     728            }
     729        }
     730    }
     731
     732    /**
     733     * Action for downloading incomplete members of selected relations
     734     *
     735     */
     736    class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener{
     737        public DownloadSelectedIncompleteMembersAction() {
     738            putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
     739            putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
     740            putValue(NAME, tr("Download incomplete members"));
     741            updateEnabledState();
     742        }
     743
     744        public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) {
     745            Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
     746            for(Relation r: rels) {
     747                ret.addAll(r.getIncompleteMembers());
     748            }
     749            return ret;
    374750        }
    375751
    376752        public void actionPerformed(ActionEvent e) {
    377             SearchAction.searchWithoutHistory(s);
    378         }
    379 
    380     }
    381 
    382     private void updateSelection() {
    383         if (Main.main.getCurrentDataSet() == null) {
    384             selectionChanged(Collections.<OsmPrimitive>emptyList());
    385         } else {
    386             selectionChanged(Main.main.getCurrentDataSet().getSelected());
    387         }
    388     }
    389 
    390     public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
    391         updateSelection();
    392     }
    393 
    394     class SelectAction extends AbstractAction implements EditLayerChangeListener {
    395         public SelectAction() {
    396             putValue(NAME, tr("Select"));
    397             putValue(SHORT_DESCRIPTION,  tr("Set the selected elements on the map to the selected items in the list above."));
    398             putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
    399             updateEnabledState();
    400         }
    401 
    402         @Override
    403         public void actionPerformed(ActionEvent e) {
    404             updateMap();
    405         }
    406 
    407         public void updateEnabledState() {
    408             setEnabled(Main.main != null && Main.main.getEditLayer() != null);
    409         }
    410 
    411         @Override
    412         public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
     753            if (!isEnabled())
     754                return;
     755            List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers();
     756            if (rels.isEmpty()) return;
     757            Main.worker.submit(new DownloadRelationMemberTask(
     758                    rels,
     759                    buildSetOfIncompleteMembers(rels),
     760                    Main.map.mapView.getEditLayer()
     761            ));
     762        }
     763
     764        protected void updateEnabledState() {
     765            setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty());
     766        }
     767
     768        public void valueChanged(ListSelectionEvent e) {
    413769            updateEnabledState();
    414770        }
Note: See TracChangeset for help on using the changeset viewer.