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