001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.Component; 008import java.awt.GraphicsEnvironment; 009import java.awt.Rectangle; 010import java.awt.datatransfer.Transferable; 011import java.awt.event.ActionEvent; 012import java.awt.event.ActionListener; 013import java.awt.event.KeyEvent; 014import java.awt.event.MouseEvent; 015import java.util.ArrayList; 016import java.util.Arrays; 017import java.util.Collection; 018import java.util.Collections; 019import java.util.HashSet; 020import java.util.Iterator; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Set; 024 025import javax.swing.AbstractAction; 026import javax.swing.AbstractListModel; 027import javax.swing.DefaultListSelectionModel; 028import javax.swing.JComponent; 029import javax.swing.JList; 030import javax.swing.JMenuItem; 031import javax.swing.JPopupMenu; 032import javax.swing.ListSelectionModel; 033import javax.swing.TransferHandler; 034import javax.swing.event.ListDataEvent; 035import javax.swing.event.ListDataListener; 036import javax.swing.event.ListSelectionEvent; 037import javax.swing.event.ListSelectionListener; 038 039import org.openstreetmap.josm.Main; 040import org.openstreetmap.josm.actions.AbstractSelectAction; 041import org.openstreetmap.josm.actions.AutoScaleAction; 042import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 043import org.openstreetmap.josm.actions.relation.EditRelationAction; 044import org.openstreetmap.josm.actions.relation.SelectInRelationListAction; 045import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting; 046import org.openstreetmap.josm.data.SelectionChangedListener; 047import org.openstreetmap.josm.data.coor.LatLon; 048import org.openstreetmap.josm.data.osm.DataSet; 049import org.openstreetmap.josm.data.osm.Node; 050import org.openstreetmap.josm.data.osm.OsmPrimitive; 051import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 052import org.openstreetmap.josm.data.osm.Relation; 053import org.openstreetmap.josm.data.osm.Way; 054import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 055import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 056import org.openstreetmap.josm.data.osm.event.DataSetListener; 057import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 058import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 059import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 060import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 061import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 062import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 063import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 064import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 065import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 066import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 067import org.openstreetmap.josm.gui.DefaultNameFormatter; 068import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 069import org.openstreetmap.josm.gui.PopupMenuHandler; 070import org.openstreetmap.josm.gui.SideButton; 071import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable; 072import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager; 073import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 074import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 075import org.openstreetmap.josm.gui.layer.OsmDataLayer; 076import org.openstreetmap.josm.gui.util.GuiHelper; 077import org.openstreetmap.josm.gui.util.HighlightHelper; 078import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 079import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 080import org.openstreetmap.josm.tools.ImageProvider; 081import org.openstreetmap.josm.tools.InputMapUtils; 082import org.openstreetmap.josm.tools.Shortcut; 083import org.openstreetmap.josm.tools.SubclassFilteredCollection; 084import org.openstreetmap.josm.tools.Utils; 085 086/** 087 * A small tool dialog for displaying the current selection. 088 * @since 8 089 */ 090public class SelectionListDialog extends ToggleDialog { 091 private JList<OsmPrimitive> lstPrimitives; 092 private final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 093 private final SelectionListModel model = new SelectionListModel(selectionModel); 094 095 private final SelectAction actSelect = new SelectAction(); 096 private final SearchAction actSearch = new SearchAction(); 097 private final ShowHistoryAction actShowHistory = new ShowHistoryAction(); 098 private final ZoomToJOSMSelectionAction actZoomToJOSMSelection = new ZoomToJOSMSelectionAction(); 099 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(); 103 104 /** the popup menu and its handler */ 105 private final ListPopupMenu popupMenu; 106 private final transient PopupMenuHandler popupMenuHandler; 107 108 /** 109 * Builds the content panel for this dialog 110 */ 111 protected void buildContentPanel() { 112 lstPrimitives = new JList<>(model); 113 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 114 lstPrimitives.setSelectionModel(selectionModel); 115 lstPrimitives.setCellRenderer(new OsmPrimitivRenderer()); 116 lstPrimitives.setTransferHandler(new SelectionTransferHandler()); 117 if (!GraphicsEnvironment.isHeadless()) { 118 lstPrimitives.setDragEnabled(true); 119 } 120 121 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect); 122 lstPrimitives.getSelectionModel().addListSelectionListener(actShowHistory); 123 124 // the select action 125 final SideButton selectButton = new SideButton(actSelect); 126 selectButton.createArrow(new ActionListener() { 127 @Override 128 public void actionPerformed(ActionEvent e) { 129 SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory()); 130 } 131 }); 132 133 // the search button 134 final SideButton searchButton = new SideButton(actSearch); 135 searchButton.createArrow(new ActionListener() { 136 @Override 137 public void actionPerformed(ActionEvent e) { 138 SearchPopupMenu.launch(searchButton); 139 } 140 }); 141 142 createLayout(lstPrimitives, true, Arrays.asList(new SideButton[] { 143 selectButton, searchButton, new SideButton(actShowHistory) 144 })); 145 } 146 147 /** 148 * Constructs a new {@code SelectionListDialog}. 149 */ 150 public SelectionListDialog() { 151 super(tr("Selection"), "selectionlist", tr("Open a selection list window."), 152 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", 153 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT), 154 150, // default height 155 true // default is "show dialog" 156 ); 157 158 buildContentPanel(); 159 model.addListDataListener(new TitleUpdater()); 160 model.addListDataListener(actZoomToJOSMSelection); 161 162 popupMenu = new ListPopupMenu(lstPrimitives); 163 popupMenuHandler = setupPopupMenuHandler(); 164 165 lstPrimitives.addListSelectionListener(new ListSelectionListener() { 166 @Override 167 public void valueChanged(ListSelectionEvent e) { 168 actZoomToListSelection.valueChanged(e); 169 popupMenuHandler.setPrimitives(model.getSelected()); 170 } 171 }); 172 173 lstPrimitives.addMouseListener(new MouseEventHandler()); 174 175 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection); 176 } 177 178 @Override 179 public void showNotify() { 180 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED); 181 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED); 182 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT); 183 Main.getLayerManager().addActiveLayerChangeListener(actSearch); 184 // editLayerChanged also gets the selection history of the level. Listener calls setJOSMSelection when fired. 185 Main.getLayerManager().addAndFireActiveLayerChangeListener(model); 186 actSearch.updateEnabledState(); 187 } 188 189 @Override 190 public void hideNotify() { 191 Main.getLayerManager().removeActiveLayerChangeListener(actSearch); 192 Main.getLayerManager().removeActiveLayerChangeListener(model); 193 SelectionEventManager.getInstance().removeSelectionListener(actShowHistory); 194 SelectionEventManager.getInstance().removeSelectionListener(model); 195 DatasetEventManager.getInstance().removeDatasetListener(model); 196 } 197 198 /** 199 * Responds to double clicks on the list of selected objects and launches the popup menu 200 */ 201 class MouseEventHandler extends PopupMenuLauncher { 202 private final HighlightHelper helper = new HighlightHelper(); 203 private final boolean highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true); 204 205 MouseEventHandler() { 206 super(popupMenu); 207 } 208 209 @Override 210 public void mouseClicked(MouseEvent e) { 211 int idx = lstPrimitives.locationToIndex(e.getPoint()); 212 if (idx < 0) return; 213 if (isDoubleClick(e)) { 214 OsmDataLayer layer = Main.getLayerManager().getEditLayer(); 215 if (layer == null) return; 216 OsmPrimitive osm = model.getElementAt(idx); 217 Collection<OsmPrimitive> sel = layer.data.getSelected(); 218 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) { 219 // Select primitive if it's not the whole current selection 220 layer.data.setSelected(Collections.singleton(osm)); 221 } else if (osm instanceof Relation) { 222 // else open relation editor if applicable 223 actEditRelationSelection.actionPerformed(null); 224 } 225 } else if (highlightEnabled && Main.isDisplayingMapView()) { 226 if (helper.highlightOnly(model.getElementAt(idx))) { 227 Main.map.mapView.repaint(); 228 } 229 } 230 } 231 232 @Override 233 public void mouseExited(MouseEvent me) { 234 if (highlightEnabled) helper.clear(); 235 super.mouseExited(me); 236 } 237 } 238 239 private PopupMenuHandler setupPopupMenuHandler() { 240 PopupMenuHandler handler = new PopupMenuHandler(popupMenu); 241 handler.addAction(actZoomToJOSMSelection); 242 handler.addAction(actZoomToListSelection); 243 handler.addSeparator(); 244 handler.addAction(actSetRelationSelection); 245 handler.addAction(actEditRelationSelection); 246 handler.addSeparator(); 247 handler.addAction(actDownloadSelIncompleteMembers); 248 return handler; 249 } 250 251 /** 252 * Replies the popup menu handler. 253 * @return The popup menu handler 254 */ 255 public PopupMenuHandler getPopupMenuHandler() { 256 return popupMenuHandler; 257 } 258 259 /** 260 * Replies the selected OSM primitives. 261 * @return The selected OSM primitives 262 */ 263 public Collection<OsmPrimitive> getSelectedPrimitives() { 264 return model.getSelected(); 265 } 266 267 /** 268 * Updates the dialog title with a summary of the current JOSM selection 269 */ 270 class TitleUpdater implements ListDataListener { 271 protected void updateTitle() { 272 setTitle(model.getJOSMSelectionSummary()); 273 } 274 275 @Override 276 public void contentsChanged(ListDataEvent e) { 277 updateTitle(); 278 } 279 280 @Override 281 public void intervalAdded(ListDataEvent e) { 282 updateTitle(); 283 } 284 285 @Override 286 public void intervalRemoved(ListDataEvent e) { 287 updateTitle(); 288 } 289 } 290 291 /** 292 * Launches the search dialog 293 */ 294 static class SearchAction extends AbstractAction implements ActiveLayerChangeListener { 295 /** 296 * Constructs a new {@code SearchAction}. 297 */ 298 SearchAction() { 299 putValue(NAME, tr("Search")); 300 putValue(SHORT_DESCRIPTION, tr("Search for objects")); 301 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true); 302 updateEnabledState(); 303 } 304 305 @Override 306 public void actionPerformed(ActionEvent e) { 307 if (!isEnabled()) return; 308 org.openstreetmap.josm.actions.search.SearchAction.search(); 309 } 310 311 protected void updateEnabledState() { 312 setEnabled(Main.getLayerManager().getEditLayer() != null); 313 } 314 315 @Override 316 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 317 updateEnabledState(); 318 } 319 } 320 321 /** 322 * Sets the current JOSM selection to the OSM primitives selected in the list 323 * of this dialog 324 */ 325 class SelectAction extends AbstractSelectAction implements ListSelectionListener { 326 /** 327 * Constructs a new {@code SelectAction}. 328 */ 329 SelectAction() { 330 updateEnabledState(); 331 } 332 333 @Override 334 public void actionPerformed(ActionEvent e) { 335 Collection<OsmPrimitive> sel = model.getSelected(); 336 if (sel.isEmpty()) return; 337 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); 338 if (editLayer == null) return; 339 editLayer.data.setSelected(sel); 340 model.selectionModel.setSelectionInterval(0, sel.size()-1); 341 } 342 343 protected void updateEnabledState() { 344 setEnabled(!model.isSelectionEmpty()); 345 } 346 347 @Override 348 public void valueChanged(ListSelectionEvent e) { 349 updateEnabledState(); 350 } 351 } 352 353 /** 354 * The action for showing history information of the current history item. 355 */ 356 class ShowHistoryAction extends AbstractAction implements ListSelectionListener, SelectionChangedListener { 357 /** 358 * Constructs a new {@code ShowHistoryAction}. 359 */ 360 ShowHistoryAction() { 361 putValue(NAME, tr("History")); 362 putValue(SHORT_DESCRIPTION, tr("Display the history of the selected objects.")); 363 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true); 364 updateEnabledState(model.getSize()); 365 } 366 367 @Override 368 public void actionPerformed(ActionEvent e) { 369 Collection<OsmPrimitive> sel = model.getSelected(); 370 if (sel.isEmpty() && model.getSize() != 1) { 371 return; 372 } else if (sel.isEmpty()) { 373 sel = Collections.singleton(model.getElementAt(0)); 374 } 375 HistoryBrowserDialogManager.getInstance().showHistory(sel); 376 } 377 378 protected void updateEnabledState(int osmSelectionSize) { 379 // See #10830 - allow to click on history button is a single object is selected, even if not selected again in the list 380 setEnabled(!model.isSelectionEmpty() || osmSelectionSize == 1); 381 } 382 383 @Override 384 public void valueChanged(ListSelectionEvent e) { 385 updateEnabledState(model.getSize()); 386 } 387 388 @Override 389 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 390 updateEnabledState(newSelection.size()); 391 } 392 } 393 394 /** 395 * The action for zooming to the primitives in the current JOSM selection 396 * 397 */ 398 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener { 399 400 ZoomToJOSMSelectionAction() { 401 putValue(NAME, tr("Zoom to selection")); 402 putValue(SHORT_DESCRIPTION, tr("Zoom to selection")); 403 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); 404 updateEnabledState(); 405 } 406 407 @Override 408 public void actionPerformed(ActionEvent e) { 409 AutoScaleAction.autoScale("selection"); 410 } 411 412 public void updateEnabledState() { 413 setEnabled(model.getSize() > 0); 414 } 415 416 @Override 417 public void contentsChanged(ListDataEvent e) { 418 updateEnabledState(); 419 } 420 421 @Override 422 public void intervalAdded(ListDataEvent e) { 423 updateEnabledState(); 424 } 425 426 @Override 427 public void intervalRemoved(ListDataEvent e) { 428 updateEnabledState(); 429 } 430 } 431 432 /** 433 * The action for zooming to the primitives which are currently selected in 434 * the list displaying the JOSM selection 435 * 436 */ 437 class ZoomToListSelection extends AbstractAction implements ListSelectionListener { 438 /** 439 * Constructs a new {@code ZoomToListSelection}. 440 */ 441 ZoomToListSelection() { 442 putValue(NAME, tr("Zoom to selected element(s)")); 443 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)")); 444 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); 445 updateEnabledState(); 446 } 447 448 @Override 449 public void actionPerformed(ActionEvent e) { 450 BoundingXYVisitor box = new BoundingXYVisitor(); 451 Collection<OsmPrimitive> sel = model.getSelected(); 452 if (sel.isEmpty()) return; 453 box.computeBoundingBox(sel); 454 if (box.getBounds() == null) 455 return; 456 box.enlargeBoundingBox(); 457 Main.map.mapView.zoomTo(box); 458 } 459 460 protected void updateEnabledState() { 461 setEnabled(!model.isSelectionEmpty()); 462 } 463 464 @Override 465 public void valueChanged(ListSelectionEvent e) { 466 updateEnabledState(); 467 } 468 } 469 470 /** 471 * The list model for the list of OSM primitives in the current JOSM selection. 472 * 473 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE} 474 * JOSM selection. 475 * 476 */ 477 private static class SelectionListModel extends AbstractListModel<OsmPrimitive> 478 implements ActiveLayerChangeListener, SelectionChangedListener, DataSetListener { 479 480 private static final int SELECTION_HISTORY_SIZE = 10; 481 482 // Variable to store history from currentDataSet() 483 private LinkedList<Collection<? extends OsmPrimitive>> history; 484 private final transient List<OsmPrimitive> selection = new ArrayList<>(); 485 private final DefaultListSelectionModel selectionModel; 486 487 /** 488 * Constructor 489 * @param selectionModel the selection model used in the list 490 */ 491 SelectionListModel(DefaultListSelectionModel selectionModel) { 492 this.selectionModel = selectionModel; 493 } 494 495 /** 496 * Replies a summary of the current JOSM selection 497 * 498 * @return a summary of the current JOSM selection 499 */ 500 public synchronized String getJOSMSelectionSummary() { 501 if (selection.isEmpty()) return tr("Selection"); 502 int numNodes = 0; 503 int numWays = 0; 504 int numRelations = 0; 505 for (OsmPrimitive p: selection) { 506 switch(p.getType()) { 507 case NODE: numNodes++; break; 508 case WAY: numWays++; break; 509 case RELATION: numRelations++; break; 510 default: throw new AssertionError(); 511 } 512 } 513 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes); 514 } 515 516 /** 517 * Remembers a JOSM selection the history of JOSM selections 518 * 519 * @param selection the JOSM selection. Ignored if null or empty. 520 */ 521 public void remember(Collection<? extends OsmPrimitive> selection) { 522 if (selection == null) return; 523 if (selection.isEmpty()) return; 524 if (history == null) return; 525 if (history.isEmpty()) { 526 history.add(selection); 527 return; 528 } 529 if (history.getFirst().equals(selection)) return; 530 history.addFirst(selection); 531 for (int i = 1; i < history.size(); ++i) { 532 if (history.get(i).equals(selection)) { 533 history.remove(i); 534 break; 535 } 536 } 537 int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE); 538 while (history.size() > maxsize) { 539 history.removeLast(); 540 } 541 } 542 543 /** 544 * Replies the history of JOSM selections 545 * 546 * @return history of JOSM selections 547 */ 548 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() { 549 return history; 550 } 551 552 @Override 553 public synchronized OsmPrimitive getElementAt(int index) { 554 return selection.get(index); 555 } 556 557 @Override 558 public synchronized int getSize() { 559 return selection.size(); 560 } 561 562 /** 563 * Determines if no OSM primitives are currently selected. 564 * @return {@code true} if no OSM primitives are currently selected 565 * @since 10383 566 */ 567 public boolean isSelectionEmpty() { 568 return selectionModel.isSelectionEmpty(); 569 } 570 571 /** 572 * Replies the collection of OSM primitives currently selected in the view of this model 573 * 574 * @return choosen elements in the view 575 */ 576 public synchronized Collection<OsmPrimitive> getSelected() { 577 Set<OsmPrimitive> sel = new HashSet<>(); 578 for (int i = 0; i < getSize(); i++) { 579 if (selectionModel.isSelectedIndex(i)) { 580 sel.add(selection.get(i)); 581 } 582 } 583 return sel; 584 } 585 586 /** 587 * Sets the OSM primitives to be selected in the view of this model 588 * 589 * @param sel the collection of primitives to select 590 */ 591 public synchronized void setSelected(Collection<OsmPrimitive> sel) { 592 selectionModel.clearSelection(); 593 if (sel == null) return; 594 for (OsmPrimitive p: sel) { 595 int i = selection.indexOf(p); 596 if (i >= 0) { 597 selectionModel.addSelectionInterval(i, i); 598 } 599 } 600 } 601 602 @Override 603 protected void fireContentsChanged(Object source, int index0, int index1) { 604 Collection<OsmPrimitive> sel = getSelected(); 605 super.fireContentsChanged(source, index0, index1); 606 setSelected(sel); 607 } 608 609 /** 610 * Sets the collection of currently selected OSM objects 611 * 612 * @param selection the collection of currently selected OSM objects 613 */ 614 public void setJOSMSelection(final Collection<? extends OsmPrimitive> selection) { 615 synchronized (this) { 616 this.selection.clear(); 617 if (selection != null) { 618 this.selection.addAll(selection); 619 sort(); 620 } 621 } 622 GuiHelper.runInEDTAndWait(new Runnable() { 623 @Override public void run() { 624 fireContentsChanged(this, 0, getSize()); 625 if (selection != null) { 626 remember(selection); 627 if (selection.size() == 2) { 628 Iterator<? extends OsmPrimitive> it = selection.iterator(); 629 OsmPrimitive n1 = it.next(); 630 OsmPrimitive n2 = it.next(); 631 // show distance between two selected nodes with coordinates 632 if (n1 instanceof Node && n2 instanceof Node) { 633 LatLon c1 = ((Node) n1).getCoor(); 634 LatLon c2 = ((Node) n2).getCoor(); 635 if (c1 != null && c2 != null) { 636 Main.map.statusLine.setDist(c1.greatCircleDistance(c2)); 637 return; 638 } 639 } 640 } 641 Main.map.statusLine.setDist( 642 new SubclassFilteredCollection<OsmPrimitive, Way>(selection, OsmPrimitive.wayPredicate)); 643 } 644 } 645 }); 646 } 647 648 /** 649 * Triggers a refresh of the view for all primitives in {@code toUpdate} 650 * which are currently displayed in the view 651 * 652 * @param toUpdate the collection of primitives to update 653 */ 654 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) { 655 if (toUpdate == null) return; 656 if (toUpdate.isEmpty()) return; 657 Collection<OsmPrimitive> sel = getSelected(); 658 for (OsmPrimitive p: toUpdate) { 659 int i = selection.indexOf(p); 660 if (i >= 0) { 661 super.fireContentsChanged(this, i, i); 662 } 663 } 664 setSelected(sel); 665 } 666 667 /** 668 * Sorts the current elements in the selection 669 */ 670 public synchronized void sort() { 671 if (this.selection.size() <= Main.pref.getInteger("selection.no_sort_above", 100000)) { 672 boolean quick = this.selection.size() > Main.pref.getInteger("selection.fast_sort_above", 10000); 673 Collections.sort(this.selection, new OsmPrimitiveComparator(quick, false)); 674 } 675 } 676 677 /* ------------------------------------------------------------------------ */ 678 /* interface ActiveLayerChangeListener */ 679 /* ------------------------------------------------------------------------ */ 680 @Override 681 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 682 DataSet newData = e.getSource().getEditDataSet(); 683 if (newData == null) { 684 setJOSMSelection(null); 685 history = null; 686 } else { 687 history = newData.getSelectionHistory(); 688 setJOSMSelection(newData.getAllSelected()); 689 } 690 } 691 692 /* ------------------------------------------------------------------------ */ 693 /* interface SelectionChangeListener */ 694 /* ------------------------------------------------------------------------ */ 695 @Override 696 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 697 setJOSMSelection(newSelection); 698 } 699 700 /* ------------------------------------------------------------------------ */ 701 /* interface DataSetListener */ 702 /* ------------------------------------------------------------------------ */ 703 @Override 704 public void dataChanged(DataChangedEvent event) { 705 // refresh the whole list 706 fireContentsChanged(this, 0, getSize()); 707 } 708 709 @Override 710 public void nodeMoved(NodeMovedEvent event) { 711 // may influence the display name of primitives, update the data 712 update(event.getPrimitives()); 713 } 714 715 @Override 716 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 717 // may influence the display name of primitives, update the data 718 update(event.getPrimitives()); 719 } 720 721 @Override 722 public void relationMembersChanged(RelationMembersChangedEvent event) { 723 // may influence the display name of primitives, update the data 724 update(event.getPrimitives()); 725 } 726 727 @Override 728 public void tagsChanged(TagsChangedEvent event) { 729 // may influence the display name of primitives, update the data 730 update(event.getPrimitives()); 731 } 732 733 @Override 734 public void wayNodesChanged(WayNodesChangedEvent event) { 735 // may influence the display name of primitives, update the data 736 update(event.getPrimitives()); 737 } 738 739 @Override 740 public void primitivesAdded(PrimitivesAddedEvent event) { 741 /* ignored - handled by SelectionChangeListener */ 742 } 743 744 @Override 745 public void primitivesRemoved(PrimitivesRemovedEvent event) { 746 /* ignored - handled by SelectionChangeListener*/ 747 } 748 } 749 750 /** 751 * A specialized {@link JMenuItem} for presenting one entry of the search history 752 * 753 * @author Jan Peter Stotz 754 */ 755 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 756 protected final transient SearchSetting s; 757 758 public SearchMenuItem(SearchSetting s) { 759 super(Utils.shortenString(s.toString(), 760 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY)); 761 this.s = s; 762 addActionListener(this); 763 } 764 765 @Override 766 public void actionPerformed(ActionEvent e) { 767 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 768 } 769 } 770 771 /** 772 * The popup menu for the search history entries 773 * 774 */ 775 protected static class SearchPopupMenu extends JPopupMenu { 776 public static void launch(Component parent) { 777 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 778 return; 779 JPopupMenu menu = new SearchPopupMenu(); 780 Rectangle r = parent.getBounds(); 781 menu.show(parent, r.x, r.y + r.height); 782 } 783 784 /** 785 * Constructs a new {@code SearchPopupMenu}. 786 */ 787 public SearchPopupMenu() { 788 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 789 add(new SearchMenuItem(ss)); 790 } 791 } 792 } 793 794 /** 795 * A specialized {@link JMenuItem} for presenting one entry of the selection history 796 * 797 * @author Jan Peter Stotz 798 */ 799 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 800 protected transient Collection<? extends OsmPrimitive> sel; 801 802 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 803 this.sel = sel; 804 int ways = 0; 805 int nodes = 0; 806 int relations = 0; 807 for (OsmPrimitive o : sel) { 808 if (!o.isSelectable()) continue; // skip unselectable primitives 809 if (o instanceof Way) { 810 ways++; 811 } else if (o instanceof Node) { 812 nodes++; 813 } else if (o instanceof Relation) { 814 relations++; 815 } 816 } 817 StringBuilder text = new StringBuilder(); 818 if (ways != 0) { 819 text.append(text.length() > 0 ? ", " : "") 820 .append(trn("{0} way", "{0} ways", ways, ways)); 821 } 822 if (nodes != 0) { 823 text.append(text.length() > 0 ? ", " : "") 824 .append(trn("{0} node", "{0} nodes", nodes, nodes)); 825 } 826 if (relations != 0) { 827 text.append(text.length() > 0 ? ", " : "") 828 .append(trn("{0} relation", "{0} relations", relations, relations)); 829 } 830 if (ways + nodes + relations == 0) { 831 text.append(tr("Unselectable now")); 832 this.sel = new ArrayList<>(); // empty selection 833 } 834 DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 835 if (ways + nodes + relations == 1) { 836 text.append(": "); 837 for (OsmPrimitive o : sel) { 838 text.append(o.getDisplayName(df)); 839 } 840 setText(text.toString()); 841 } else { 842 setText(tr("Selection: {0}", text)); 843 } 844 addActionListener(this); 845 } 846 847 @Override 848 public void actionPerformed(ActionEvent e) { 849 Main.getLayerManager().getEditDataSet().setSelected(sel); 850 } 851 } 852 853 /** 854 * The popup menu for the JOSM selection history entries 855 */ 856 protected static class SelectionHistoryPopup extends JPopupMenu { 857 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) { 858 if (history == null || history.isEmpty()) return; 859 JPopupMenu menu = new SelectionHistoryPopup(history); 860 Rectangle r = parent.getBounds(); 861 menu.show(parent, r.x, r.y + r.height); 862 } 863 864 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) { 865 for (Collection<? extends OsmPrimitive> sel : history) { 866 add(new SelectionMenuItem(sel)); 867 } 868 } 869 } 870 871 /** 872 * A transfer handler class for drag-and-drop support. 873 */ 874 protected class SelectionTransferHandler extends TransferHandler { 875 876 @Override 877 public int getSourceActions(JComponent c) { 878 return COPY; 879 } 880 881 @Override 882 protected Transferable createTransferable(JComponent c) { 883 return new PrimitiveTransferable(getSelectedPrimitives()); 884 } 885 } 886}