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

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

introduce PrimitiveRenderer to replace OsmPrimitivRenderer (now deprecated). Change NameFormatter API to support IPrimitive instead of OsmPrimitive. Enhances interfaces in consequence.

  • Property svn:eol-style set to native
File size: 32.6 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[626]2package org.openstreetmap.josm.gui.dialogs;
3
[582]4import static org.openstreetmap.josm.tools.I18n.tr;
[1195]5import static org.openstreetmap.josm.tools.I18n.trn;
[582]6
[3102]7import java.awt.Component;
[9376]8import java.awt.GraphicsEnvironment;
[967]9import java.awt.Rectangle;
[9369]10import java.awt.datatransfer.Transferable;
[582]11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
[3102]15import java.util.ArrayList;
[4354]16import java.util.Arrays;
[582]17import java.util.Collection;
[1970]18import java.util.Collections;
[11680]19import java.util.Comparator;
[3102]20import java.util.HashSet;
[582]21import java.util.LinkedList;
[3102]22import java.util.List;
23import java.util.Set;
[582]24
[3099]25import javax.swing.AbstractAction;
[3102]26import javax.swing.AbstractListModel;
27import javax.swing.DefaultListSelectionModel;
[9369]28import javax.swing.JComponent;
[582]29import javax.swing.JList;
[967]30import javax.swing.JMenuItem;
31import javax.swing.JPopupMenu;
[582]32import javax.swing.ListSelectionModel;
[9369]33import javax.swing.TransferHandler;
[3102]34import javax.swing.event.ListDataEvent;
35import javax.swing.event.ListDataListener;
36import javax.swing.event.ListSelectionEvent;
37import javax.swing.event.ListSelectionListener;
[582]38
[7949]39import org.openstreetmap.josm.actions.AbstractSelectAction;
[967]40import org.openstreetmap.josm.actions.AutoScaleAction;
[5793]41import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction;
42import org.openstreetmap.josm.actions.relation.EditRelationAction;
43import org.openstreetmap.josm.actions.relation.SelectInRelationListAction;
[582]44import org.openstreetmap.josm.data.SelectionChangedListener;
[10332]45import org.openstreetmap.josm.data.osm.DataSet;
[12663]46import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
[967]47import org.openstreetmap.josm.data.osm.Node;
[582]48import org.openstreetmap.josm.data.osm.OsmPrimitive;
[4113]49import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
[1195]50import org.openstreetmap.josm.data.osm.Relation;
[967]51import org.openstreetmap.josm.data.osm.Way;
[3102]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;
[4068]56import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
[3102]57import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
58import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
59import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
60import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
[2912]61import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
[3102]62import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
63import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
[12846]64import org.openstreetmap.josm.data.osm.search.SearchSetting;
[967]65import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
[12630]66import org.openstreetmap.josm.gui.MainApplication;
[13564]67import org.openstreetmap.josm.gui.PrimitiveRenderer;
[5821]68import org.openstreetmap.josm.gui.PopupMenuHandler;
[744]69import org.openstreetmap.josm.gui.SideButton;
[9369]70import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
[10604]71import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
[7750]72import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
[10332]73import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
74import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
[5984]75import org.openstreetmap.josm.gui.util.GuiHelper;
[6025]76import org.openstreetmap.josm.gui.util.HighlightHelper;
[4459]77import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
[3102]78import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
[12846]79import org.openstreetmap.josm.spi.preferences.Config;
[3099]80import org.openstreetmap.josm.tools.ImageProvider;
[5200]81import org.openstreetmap.josm.tools.InputMapUtils;
[1084]82import org.openstreetmap.josm.tools.Shortcut;
[6742]83import org.openstreetmap.josm.tools.Utils;
[12909]84import org.openstreetmap.josm.tools.bugreport.BugReport;
[626]85
86/**
[3102]87 * A small tool dialog for displaying the current selection.
[6353]88 * @since 8
[626]89 */
[10378]90public class SelectionListDialog extends ToggleDialog {
[7001]91 private JList<OsmPrimitive> lstPrimitives;
[7781]92 private final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
93 private final SelectionListModel model = new SelectionListModel(selectionModel);
[626]94
[7781]95 private final SelectAction actSelect = new SelectAction();
96 private final SearchAction actSearch = new SearchAction();
97 private final ShowHistoryAction actShowHistory = new ShowHistoryAction();
98 private final ZoomToJOSMSelectionAction actZoomToJOSMSelection = new ZoomToJOSMSelectionAction();
99 private final ZoomToListSelection actZoomToListSelection = new ZoomToListSelection();
100 private final SelectInRelationListAction actSetRelationSelection = new SelectInRelationListAction();
101 private final EditRelationAction actEditRelationSelection = new EditRelationAction();
102 private final DownloadSelectedIncompleteMembersAction actDownloadSelIncompleteMembers = new DownloadSelectedIncompleteMembersAction();
[626]103
[5821]104 /** the popup menu and its handler */
105 private final ListPopupMenu popupMenu;
[8308]106 private final transient PopupMenuHandler popupMenuHandler;
[4459]107
[967]108 /**
[4354]109 * Builds the content panel for this dialog
[967]110 */
[4354]111 protected void buildContentPanel() {
[7005]112 lstPrimitives = new JList<>(model);
[3102]113 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
114 lstPrimitives.setSelectionModel(selectionModel);
[13564]115 lstPrimitives.setCellRenderer(new PrimitiveRenderer());
[9369]116 lstPrimitives.setTransferHandler(new SelectionTransferHandler());
[9376]117 if (!GraphicsEnvironment.isHeadless()) {
118 lstPrimitives.setDragEnabled(true);
119 }
[626]120
[7750]121 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect);
122 lstPrimitives.getSelectionModel().addListSelectionListener(actShowHistory);
123
[3102]124 // the select action
[5825]125 final SideButton selectButton = new SideButton(actSelect);
[10611]126 selectButton.createArrow(e -> SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory()));
[626]127
[3102]128 // the search button
[5825]129 final SideButton searchButton = new SideButton(actSearch);
[13545]130 searchButton.createArrow(e -> SearchPopupMenu.launch(searchButton), true);
[967]131
[12279]132 createLayout(lstPrimitives, true, Arrays.asList(
[7750]133 selectButton, searchButton, new SideButton(actShowHistory)
[12279]134 ));
[3102]135 }
[967]136
[5984]137 /**
138 * Constructs a new {@code SelectionListDialog}.
139 */
[3102]140 public SelectionListDialog() {
[4462]141 super(tr("Selection"), "selectionlist", tr("Open a selection list window."),
[4957]142 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}",
[4982]143 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT),
[3102]144 150, // default height
145 true // default is "show dialog"
146 );
147
[4354]148 buildContentPanel();
[3102]149 model.addListDataListener(new TitleUpdater());
150 model.addListDataListener(actZoomToJOSMSelection);
151
[5821]152 popupMenu = new ListPopupMenu(lstPrimitives);
153 popupMenuHandler = setupPopupMenuHandler();
154
[10611]155 lstPrimitives.addListSelectionListener(e -> {
156 actZoomToListSelection.valueChanged(e);
157 popupMenuHandler.setPrimitives(model.getSelected());
[5793]158 });
[6070]159
[5958]160 lstPrimitives.addMouseListener(new MouseEventHandler());
[4957]161
[5200]162 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection);
[2658]163 }
164
165 @Override
166 public void showNotify() {
[7781]167 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED);
[3102]168 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
169 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
[12636]170 MainApplication.getLayerManager().addActiveLayerChangeListener(actSearch);
[10332]171 // editLayerChanged also gets the selection history of the level. Listener calls setJOSMSelection when fired.
[12636]172 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(model);
[3469]173 actSearch.updateEnabledState();
[967]174 }
175
[2621]176 @Override
[2658]177 public void hideNotify() {
[12636]178 MainApplication.getLayerManager().removeActiveLayerChangeListener(actSearch);
179 MainApplication.getLayerManager().removeActiveLayerChangeListener(model);
[7781]180 SelectionEventManager.getInstance().removeSelectionListener(actShowHistory);
[3102]181 SelectionEventManager.getInstance().removeSelectionListener(model);
182 DatasetEventManager.getInstance().removeDatasetListener(model);
[2621]183 }
184
[3102]185 /**
[5958]186 * Responds to double clicks on the list of selected objects and launches the popup menu
[3114]187 */
[5958]188 class MouseEventHandler extends PopupMenuLauncher {
[6025]189 private final HighlightHelper helper = new HighlightHelper();
[12846]190 private final boolean highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true);
[8510]191
[8836]192 MouseEventHandler() {
[5958]193 super(popupMenu);
194 }
[6070]195
[3114]196 @Override
197 public void mouseClicked(MouseEvent e) {
[6025]198 int idx = lstPrimitives.locationToIndex(e.getPoint());
199 if (idx < 0) return;
[5958]200 if (isDoubleClick(e)) {
[13434]201 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
202 if (ds == null) return;
[7657]203 OsmPrimitive osm = model.getElementAt(idx);
[13434]204 Collection<OsmPrimitive> sel = ds.getSelected();
[7657]205 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) {
206 // Select primitive if it's not the whole current selection
[13434]207 ds.setSelected(Collections.singleton(osm));
[7657]208 } else if (osm instanceof Relation) {
209 // else open relation editor if applicable
210 actEditRelationSelection.actionPerformed(null);
211 }
[12630]212 } else if (highlightEnabled && MainApplication.isDisplayingMapView() && helper.highlightOnly(model.getElementAt(idx))) {
213 MainApplication.getMap().mapView.repaint();
[3102]214 }
[1814]215 }
[6025]216
217 @Override
218 public void mouseExited(MouseEvent me) {
[6058]219 if (highlightEnabled) helper.clear();
[6025]220 super.mouseExited(me);
221 }
[967]222 }
223
[5825]224 private PopupMenuHandler setupPopupMenuHandler() {
[5821]225 PopupMenuHandler handler = new PopupMenuHandler(popupMenu);
226 handler.addAction(actZoomToJOSMSelection);
227 handler.addAction(actZoomToListSelection);
228 handler.addSeparator();
229 handler.addAction(actSetRelationSelection);
230 handler.addAction(actEditRelationSelection);
231 handler.addSeparator();
[7781]232 handler.addAction(actDownloadSelIncompleteMembers);
[5821]233 return handler;
234 }
235
[3102]236 /**
[5821]237 * Replies the popup menu handler.
238 * @return The popup menu handler
[3102]239 */
[5821]240 public PopupMenuHandler getPopupMenuHandler() {
241 return popupMenuHandler;
[967]242 }
243
[5984]244 /**
245 * Replies the selected OSM primitives.
246 * @return The selected OSM primitives
247 */
[4536]248 public Collection<OsmPrimitive> getSelectedPrimitives() {
249 return model.getSelected();
250 }
[4957]251
[3102]252 /**
253 * Updates the dialog title with a summary of the current JOSM selection
254 */
255 class TitleUpdater implements ListDataListener {
256 protected void updateTitle() {
257 setTitle(model.getJOSMSelectionSummary());
258 }
259
[5793]260 @Override
[3102]261 public void contentsChanged(ListDataEvent e) {
262 updateTitle();
263 }
264
[5793]265 @Override
[3102]266 public void intervalAdded(ListDataEvent e) {
267 updateTitle();
268 }
269
[5793]270 @Override
[3102]271 public void intervalRemoved(ListDataEvent e) {
272 updateTitle();
273 }
[967]274 }
275
276 /**
[3102]277 * Launches the search dialog
[967]278 */
[10332]279 static class SearchAction extends AbstractAction implements ActiveLayerChangeListener {
[7750]280 /**
281 * Constructs a new {@code SearchAction}.
282 */
[8836]283 SearchAction() {
[3102]284 putValue(NAME, tr("Search"));
[10378]285 putValue(SHORT_DESCRIPTION, tr("Search for objects"));
[10369]286 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
[3102]287 updateEnabledState();
[967]288 }
[3102]289
[5793]290 @Override
[3102]291 public void actionPerformed(ActionEvent e) {
292 if (!isEnabled()) return;
293 org.openstreetmap.josm.actions.search.SearchAction.search();
294 }
295
[7750]296 protected void updateEnabledState() {
[13434]297 setEnabled(MainApplication.getLayerManager().getActiveDataSet() != null);
[3102]298 }
299
[5793]300 @Override
[10332]301 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
[3102]302 updateEnabledState();
303 }
[967]304 }
305
[3102]306 /**
307 * Sets the current JOSM selection to the OSM primitives selected in the list
308 * of this dialog
309 */
[7949]310 class SelectAction extends AbstractSelectAction implements ListSelectionListener {
[7750]311 /**
312 * Constructs a new {@code SelectAction}.
313 */
[8836]314 SelectAction() {
[3102]315 updateEnabledState();
[967]316 }
[3102]317
[5799]318 @Override
[3102]319 public void actionPerformed(ActionEvent e) {
320 Collection<OsmPrimitive> sel = model.getSelected();
[8510]321 if (sel.isEmpty()) return;
[13434]322 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
323 if (ds == null) return;
324 ds.setSelected(sel);
[7949]325 model.selectionModel.setSelectionInterval(0, sel.size()-1);
[3102]326 }
327
[7750]328 protected void updateEnabledState() {
[10383]329 setEnabled(!model.isSelectionEmpty());
[3102]330 }
331
[5799]332 @Override
[3102]333 public void valueChanged(ListSelectionEvent e) {
334 updateEnabledState();
335 }
[967]336 }
337
[3102]338 /**
[7750]339 * The action for showing history information of the current history item.
340 */
[7781]341 class ShowHistoryAction extends AbstractAction implements ListSelectionListener, SelectionChangedListener {
[7750]342 /**
343 * Constructs a new {@code ShowHistoryAction}.
344 */
[8836]345 ShowHistoryAction() {
[7750]346 putValue(NAME, tr("History"));
347 putValue(SHORT_DESCRIPTION, tr("Display the history of the selected objects."));
[10369]348 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true);
[7781]349 updateEnabledState(model.getSize());
[7750]350 }
351
352 @Override
353 public void actionPerformed(ActionEvent e) {
354 Collection<OsmPrimitive> sel = model.getSelected();
[7781]355 if (sel.isEmpty() && model.getSize() != 1) {
356 return;
357 } else if (sel.isEmpty()) {
358 sel = Collections.singleton(model.getElementAt(0));
359 }
[7750]360 HistoryBrowserDialogManager.getInstance().showHistory(sel);
361 }
362
[7781]363 protected void updateEnabledState(int osmSelectionSize) {
364 // See #10830 - allow to click on history button is a single object is selected, even if not selected again in the list
[10383]365 setEnabled(!model.isSelectionEmpty() || osmSelectionSize == 1);
[7750]366 }
367
368 @Override
369 public void valueChanged(ListSelectionEvent e) {
[7781]370 updateEnabledState(model.getSize());
[7750]371 }
[7781]372
373 @Override
374 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
375 updateEnabledState(newSelection.size());
376 }
[7750]377 }
378
379 /**
[3102]380 * The action for zooming to the primitives in the current JOSM selection
[3146]381 *
[3102]382 */
383 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener {
384
[8836]385 ZoomToJOSMSelectionAction() {
[8510]386 putValue(NAME, tr("Zoom to selection"));
[3102]387 putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
[10369]388 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
[3102]389 updateEnabledState();
[967]390 }
[3105]391
[5793]392 @Override
[3102]393 public void actionPerformed(ActionEvent e) {
[3327]394 AutoScaleAction.autoScale("selection");
[3102]395 }
396
397 public void updateEnabledState() {
398 setEnabled(model.getSize() > 0);
399 }
400
[5793]401 @Override
[3102]402 public void contentsChanged(ListDataEvent e) {
403 updateEnabledState();
404 }
405
[5793]406 @Override
[3102]407 public void intervalAdded(ListDataEvent e) {
408 updateEnabledState();
409 }
410
[5793]411 @Override
[3102]412 public void intervalRemoved(ListDataEvent e) {
413 updateEnabledState();
414 }
[967]415 }
416
417 /**
[3102]418 * The action for zooming to the primitives which are currently selected in
419 * the list displaying the JOSM selection
[3146]420 *
[967]421 */
[7750]422 class ZoomToListSelection extends AbstractAction implements ListSelectionListener {
423 /**
424 * Constructs a new {@code ZoomToListSelection}.
425 */
[8836]426 ZoomToListSelection() {
[3102]427 putValue(NAME, tr("Zoom to selected element(s)"));
428 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)"));
[10369]429 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
[3102]430 updateEnabledState();
[1814]431 }
[1677]432
[5793]433 @Override
[3102]434 public void actionPerformed(ActionEvent e) {
435 BoundingXYVisitor box = new BoundingXYVisitor();
436 Collection<OsmPrimitive> sel = model.getSelected();
437 if (sel.isEmpty()) return;
438 box.computeBoundingBox(sel);
439 if (box.getBounds() == null)
440 return;
441 box.enlargeBoundingBox();
[12630]442 MainApplication.getMap().mapView.zoomTo(box);
[3102]443 }
444
[7750]445 protected void updateEnabledState() {
[10383]446 setEnabled(!model.isSelectionEmpty());
[3102]447 }
448
[3327]449 @Override
[3290]450 public void valueChanged(ListSelectionEvent e) {
451 updateEnabledState();
452 }
453 }
454
455 /**
[3102]456 * The list model for the list of OSM primitives in the current JOSM selection.
[3146]457 *
[5266]458 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE}
[3102]459 * JOSM selection.
[3146]460 *
[3102]461 */
[12909]462 static class SelectionListModel extends AbstractListModel<OsmPrimitive>
[10332]463 implements ActiveLayerChangeListener, SelectionChangedListener, DataSetListener {
[3102]464
465 private static final int SELECTION_HISTORY_SIZE = 10;
466
[4064]467 // Variable to store history from currentDataSet()
468 private LinkedList<Collection<? extends OsmPrimitive>> history;
[8308]469 private final transient List<OsmPrimitive> selection = new ArrayList<>();
[9078]470 private final DefaultListSelectionModel selectionModel;
[3102]471
472 /**
473 * Constructor
474 * @param selectionModel the selection model used in the list
475 */
[8836]476 SelectionListModel(DefaultListSelectionModel selectionModel) {
[3102]477 this.selectionModel = selectionModel;
478 }
479
480 /**
481 * Replies a summary of the current JOSM selection
[3146]482 *
[3102]483 * @return a summary of the current JOSM selection
484 */
[8367]485 public synchronized String getJOSMSelectionSummary() {
[3102]486 if (selection.isEmpty()) return tr("Selection");
487 int numNodes = 0;
488 int numWays = 0;
489 int numRelations = 0;
490 for (OsmPrimitive p: selection) {
491 switch(p.getType()) {
492 case NODE: numNodes++; break;
493 case WAY: numWays++; break;
494 case RELATION: numRelations++; break;
[10217]495 default: throw new AssertionError();
[3102]496 }
[1814]497 }
[3102]498 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes);
[1228]499 }
500
[3102]501 /**
502 * Remembers a JOSM selection the history of JOSM selections
[3146]503 *
[3102]504 * @param selection the JOSM selection. Ignored if null or empty.
505 */
506 public void remember(Collection<? extends OsmPrimitive> selection) {
[8510]507 if (selection == null) return;
508 if (selection.isEmpty()) return;
[4064]509 if (history == null) return;
[3102]510 if (history.isEmpty()) {
511 history.add(selection);
512 return;
513 }
514 if (history.getFirst().equals(selection)) return;
515 history.addFirst(selection);
[8510]516 for (int i = 1; i < history.size(); ++i) {
517 if (history.get(i).equals(selection)) {
[3286]518 history.remove(i);
519 break;
520 }
521 }
[12846]522 int maxsize = Config.getPref().getInt("select.history-size", SELECTION_HISTORY_SIZE);
[3361]523 while (history.size() > maxsize) {
[3102]524 history.removeLast();
525 }
526 }
[2912]527
[3102]528 /**
529 * Replies the history of JOSM selections
[3146]530 *
[5903]531 * @return history of JOSM selections
[3102]532 */
533 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
534 return history;
535 }
536
[5799]537 @Override
[8367]538 public synchronized OsmPrimitive getElementAt(int index) {
[3102]539 return selection.get(index);
540 }
541
[5799]542 @Override
[8367]543 public synchronized int getSize() {
[3102]544 return selection.size();
545 }
546
547 /**
[10383]548 * Determines if no OSM primitives are currently selected.
549 * @return {@code true} if no OSM primitives are currently selected
550 * @since 10383
551 */
552 public boolean isSelectionEmpty() {
553 return selectionModel.isSelectionEmpty();
554 }
555
556 /**
557 * Replies the collection of OSM primitives currently selected in the view of this model
[3146]558 *
[5903]559 * @return choosen elements in the view
[3102]560 */
[8367]561 public synchronized Collection<OsmPrimitive> getSelected() {
[7005]562 Set<OsmPrimitive> sel = new HashSet<>();
[8510]563 for (int i = 0; i < getSize(); i++) {
[3102]564 if (selectionModel.isSelectedIndex(i)) {
565 sel.add(selection.get(i));
566 }
[2903]567 }
[3102]568 return sel;
569 }
570
571 /**
572 * Sets the OSM primitives to be selected in the view of this model
[3146]573 *
[3102]574 * @param sel the collection of primitives to select
575 */
[8367]576 public synchronized void setSelected(Collection<OsmPrimitive> sel) {
[11365]577 selectionModel.setValueIsAdjusting(true);
[3102]578 selectionModel.clearSelection();
[11365]579 if (sel != null) {
580 for (OsmPrimitive p: sel) {
581 int i = selection.indexOf(p);
582 if (i >= 0) {
583 selectionModel.addSelectionInterval(i, i);
584 }
[3102]585 }
[2903]586 }
[11365]587 selectionModel.setValueIsAdjusting(false);
[2903]588 }
[3102]589
590 @Override
591 protected void fireContentsChanged(Object source, int index0, int index1) {
592 Collection<OsmPrimitive> sel = getSelected();
593 super.fireContentsChanged(source, index0, index1);
594 setSelected(sel);
595 }
596
597 /**
598 * Sets the collection of currently selected OSM objects
[3146]599 *
[3102]600 * @param selection the collection of currently selected OSM objects
601 */
[8368]602 public void setJOSMSelection(final Collection<? extends OsmPrimitive> selection) {
603 synchronized (this) {
604 this.selection.clear();
605 if (selection != null) {
606 this.selection.addAll(selection);
607 sort();
608 }
[3102]609 }
[5984]610 GuiHelper.runInEDTAndWait(new Runnable() {
611 @Override public void run() {
612 fireContentsChanged(this, 0, getSize());
613 if (selection != null) {
614 remember(selection);
615 }
[5713]616 }
[5984]617 });
[3102]618 }
619
620 /**
621 * Triggers a refresh of the view for all primitives in {@code toUpdate}
622 * which are currently displayed in the view
[3146]623 *
[3102]624 * @param toUpdate the collection of primitives to update
625 */
[8367]626 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) {
[3102]627 if (toUpdate == null) return;
628 if (toUpdate.isEmpty()) return;
629 Collection<OsmPrimitive> sel = getSelected();
[8510]630 for (OsmPrimitive p: toUpdate) {
[3102]631 int i = selection.indexOf(p);
632 if (i >= 0) {
[8510]633 super.fireContentsChanged(this, i, i);
[3102]634 }
635 }
636 setSelected(sel);
637 }
638
639 /**
[3124]640 * Sorts the current elements in the selection
641 */
[8367]642 public synchronized void sort() {
[12909]643 int size = selection.size();
644 if (size > 1 && size <= Config.getPref().getInt("selection.no_sort_above", 100_000)) {
645 boolean quick = size > Config.getPref().getInt("selection.fast_sort_above", 10_000);
[12846]646 Comparator<OsmPrimitive> c = Config.getPref().getBoolean("selection.sort_relations_before_ways", true)
[11680]647 ? OsmPrimitiveComparator.orderingRelationsWaysNodes()
648 : OsmPrimitiveComparator.orderingWaysRelationsNodes();
[12909]649 try {
650 selection.sort(c.thenComparing(quick
651 ? OsmPrimitiveComparator.comparingUniqueId()
652 : OsmPrimitiveComparator.comparingNames()));
653 } catch (IllegalArgumentException e) {
654 throw BugReport.intercept(e).put("size", size).put("quick", quick).put("selection", selection);
655 }
[3654]656 }
[3124]657 }
658
[3102]659 /* ------------------------------------------------------------------------ */
[10332]660 /* interface ActiveLayerChangeListener */
[3102]661 /* ------------------------------------------------------------------------ */
[5793]662 @Override
[10332]663 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
664 DataSet newData = e.getSource().getEditDataSet();
665 if (newData == null) {
[3102]666 setJOSMSelection(null);
[4064]667 history = null;
[3102]668 } else {
[10332]669 history = newData.getSelectionHistory();
670 setJOSMSelection(newData.getAllSelected());
[3102]671 }
672 }
673
674 /* ------------------------------------------------------------------------ */
[13141]675 /* interface SelectionChangedListener */
[3102]676 /* ------------------------------------------------------------------------ */
[5793]677 @Override
[3102]678 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
679 setJOSMSelection(newSelection);
680 }
681
682 /* ------------------------------------------------------------------------ */
683 /* interface DataSetListener */
684 /* ------------------------------------------------------------------------ */
[5793]685 @Override
[3102]686 public void dataChanged(DataChangedEvent event) {
687 // refresh the whole list
688 fireContentsChanged(this, 0, getSize());
689 }
690
[5793]691 @Override
[3102]692 public void nodeMoved(NodeMovedEvent event) {
693 // may influence the display name of primitives, update the data
694 update(event.getPrimitives());
695 }
696
[5793]697 @Override
[3102]698 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
699 // may influence the display name of primitives, update the data
700 update(event.getPrimitives());
701 }
702
[5793]703 @Override
[3102]704 public void relationMembersChanged(RelationMembersChangedEvent event) {
705 // may influence the display name of primitives, update the data
706 update(event.getPrimitives());
707 }
708
[5793]709 @Override
[3102]710 public void tagsChanged(TagsChangedEvent event) {
711 // may influence the display name of primitives, update the data
712 update(event.getPrimitives());
713 }
714
[5793]715 @Override
[3102]716 public void wayNodesChanged(WayNodesChangedEvent event) {
717 // may influence the display name of primitives, update the data
718 update(event.getPrimitives());
719 }
720
[5793]721 @Override
[7949]722 public void primitivesAdded(PrimitivesAddedEvent event) {
723 /* ignored - handled by SelectionChangeListener */
724 }
725
[5793]726 @Override
[7949]727 public void primitivesRemoved(PrimitivesRemovedEvent event) {
728 /* ignored - handled by SelectionChangeListener*/
729 }
[967]730 }
731
[3102]732 /**
733 * A specialized {@link JMenuItem} for presenting one entry of the search history
734 *
735 * @author Jan Peter Stotz
736 */
737 protected static class SearchMenuItem extends JMenuItem implements ActionListener {
[8308]738 protected final transient SearchSetting s;
[3102]739
740 public SearchMenuItem(SearchSetting s) {
[7949]741 super(Utils.shortenString(s.toString(),
742 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY));
[3102]743 this.s = s;
744 addActionListener(this);
[2930]745 }
[3102]746
[5793]747 @Override
[3102]748 public void actionPerformed(ActionEvent e) {
749 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
750 }
[2930]751 }
752
[967]753 /**
[3102]754 * The popup menu for the search history entries
[3146]755 *
[967]756 */
[3102]757 protected static class SearchPopupMenu extends JPopupMenu {
[6889]758 public static void launch(Component parent) {
[3176]759 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty())
[3102]760 return;
761 JPopupMenu menu = new SearchPopupMenu();
762 Rectangle r = parent.getBounds();
763 menu.show(parent, r.x, r.y + r.height);
764 }
765
[8468]766 /**
767 * Constructs a new {@code SearchPopupMenu}.
768 */
[3102]769 public SearchPopupMenu() {
[3176]770 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) {
[3102]771 add(new SearchMenuItem(ss));
[1814]772 }
[3102]773 }
[967]774 }
775
776 /**
777 * A specialized {@link JMenuItem} for presenting one entry of the selection history
[1023]778 *
[967]779 * @author Jan Peter Stotz
780 */
[3102]781 protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
[8308]782 protected transient Collection<? extends OsmPrimitive> sel;
[967]783
784 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) {
785 this.sel = sel;
786 int ways = 0;
787 int nodes = 0;
[1195]788 int relations = 0;
[967]789 for (OsmPrimitive o : sel) {
[8444]790 if (!o.isSelectable()) continue; // skip unselectable primitives
[1814]791 if (o instanceof Way) {
[967]792 ways++;
[1814]793 } else if (o instanceof Node) {
[967]794 nodes++;
[1814]795 } else if (o instanceof Relation) {
[1195]796 relations++;
[1814]797 }
[967]798 }
[6822]799 StringBuilder text = new StringBuilder();
[8395]800 if (ways != 0) {
[3102]801 text.append(text.length() > 0 ? ", " : "")
802 .append(trn("{0} way", "{0} ways", ways, ways));
[1814]803 }
[8395]804 if (nodes != 0) {
[3102]805 text.append(text.length() > 0 ? ", " : "")
806 .append(trn("{0} node", "{0} nodes", nodes, nodes));
[1814]807 }
[8395]808 if (relations != 0) {
[3102]809 text.append(text.length() > 0 ? ", " : "")
810 .append(trn("{0} relation", "{0} relations", relations, relations));
[1814]811 }
[8395]812 if (ways + nodes + relations == 0) {
[5036]813 text.append(tr("Unselectable now"));
[8510]814 this.sel = new ArrayList<>(); // empty selection
[6070]815 }
[8468]816 DefaultNameFormatter df = DefaultNameFormatter.getInstance();
[8395]817 if (ways + nodes + relations == 1) {
[3286]818 text.append(": ");
[8510]819 for (OsmPrimitive o : sel) {
[3327]820 text.append(o.getDisplayName(df));
821 }
[3286]822 setText(text.toString());
[3327]823 } else {
824 setText(tr("Selection: {0}", text));
[3286]825 }
[967]826 addActionListener(this);
827 }
828
[5793]829 @Override
[967]830 public void actionPerformed(ActionEvent e) {
[13434]831 MainApplication.getLayerManager().getActiveDataSet().setSelected(sel);
[967]832 }
833 }
834
835 /**
[5958]836 * The popup menu for the JOSM selection history entries
[967]837 */
[3102]838 protected static class SelectionHistoryPopup extends JPopupMenu {
[6889]839 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
[3102]840 if (history == null || history.isEmpty()) return;
841 JPopupMenu menu = new SelectionHistoryPopup(history);
842 Rectangle r = parent.getBounds();
843 menu.show(parent, r.x, r.y + r.height);
[967]844 }
845
[3102]846 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
847 for (Collection<? extends OsmPrimitive> sel : history) {
848 add(new SelectionMenuItem(sel));
849 }
[967]850 }
851 }
[9369]852
853 /**
854 * A transfer handler class for drag-and-drop support.
855 */
856 protected class SelectionTransferHandler extends TransferHandler {
857
858 @Override
859 public int getSourceActions(JComponent c) {
860 return COPY;
861 }
862
863 @Override
864 protected Transferable createTransferable(JComponent c) {
[10604]865 return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives()));
[9369]866 }
867 }
[626]868}
Note: See TracBrowser for help on using the repository browser.