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.AutoScaleAction; 037import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 038import org.openstreetmap.josm.actions.relation.EditRelationAction; 039import org.openstreetmap.josm.actions.relation.SelectInRelationListAction; 040import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting; 041import org.openstreetmap.josm.data.SelectionChangedListener; 042import org.openstreetmap.josm.data.osm.Node; 043import org.openstreetmap.josm.data.osm.OsmPrimitive; 044import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 045import org.openstreetmap.josm.data.osm.Relation; 046import org.openstreetmap.josm.data.osm.Way; 047import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 048import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 049import org.openstreetmap.josm.data.osm.event.DataSetListener; 050import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 051import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 052import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 053import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 054import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 055import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 056import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 057import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 058import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 059import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 060import org.openstreetmap.josm.gui.DefaultNameFormatter; 061import org.openstreetmap.josm.gui.MapView; 062import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener; 063import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 064import org.openstreetmap.josm.gui.PopupMenuHandler; 065import org.openstreetmap.josm.gui.SideButton; 066import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager; 067import org.openstreetmap.josm.gui.layer.OsmDataLayer; 068import org.openstreetmap.josm.gui.util.GuiHelper; 069import org.openstreetmap.josm.gui.util.HighlightHelper; 070import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 071import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 072import org.openstreetmap.josm.tools.ImageProvider; 073import org.openstreetmap.josm.tools.InputMapUtils; 074import org.openstreetmap.josm.tools.Shortcut; 075import org.openstreetmap.josm.tools.SubclassFilteredCollection; 076import org.openstreetmap.josm.tools.Utils; 077 078/** 079 * A small tool dialog for displaying the current selection. 080 * @since 8 081 */ 082public class SelectionListDialog extends ToggleDialog { 083 private JList<OsmPrimitive> lstPrimitives; 084 private final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 085 private final SelectionListModel model = new SelectionListModel(selectionModel); 086 087 private final SelectAction actSelect = new SelectAction(); 088 private final SearchAction actSearch = new SearchAction(); 089 private final ShowHistoryAction actShowHistory = new ShowHistoryAction(); 090 private final ZoomToJOSMSelectionAction actZoomToJOSMSelection = new ZoomToJOSMSelectionAction(); 091 private final ZoomToListSelection actZoomToListSelection = new ZoomToListSelection(); 092 private final SelectInRelationListAction actSetRelationSelection = new SelectInRelationListAction(); 093 private final EditRelationAction actEditRelationSelection = new EditRelationAction(); 094 private final DownloadSelectedIncompleteMembersAction actDownloadSelIncompleteMembers = new DownloadSelectedIncompleteMembersAction(); 095 096 /** the popup menu and its handler */ 097 private final ListPopupMenu popupMenu; 098 private final PopupMenuHandler popupMenuHandler; 099 100 /** 101 * Builds the content panel for this dialog 102 */ 103 protected void buildContentPanel() { 104 lstPrimitives = new JList<>(model); 105 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 106 lstPrimitives.setSelectionModel(selectionModel); 107 lstPrimitives.setCellRenderer(new OsmPrimitivRenderer()); 108 // Fix #6290. Drag & Drop is not supported anyway and Copy/Paste is better propagated to main window 109 lstPrimitives.setTransferHandler(null); 110 111 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect); 112 lstPrimitives.getSelectionModel().addListSelectionListener(actShowHistory); 113 114 // the select action 115 final SideButton selectButton = new SideButton(actSelect); 116 selectButton.createArrow(new ActionListener() { 117 @Override 118 public void actionPerformed(ActionEvent e) { 119 SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory()); 120 } 121 }); 122 123 // the search button 124 final SideButton searchButton = new SideButton(actSearch); 125 searchButton.createArrow(new ActionListener() { 126 @Override 127 public void actionPerformed(ActionEvent e) { 128 SearchPopupMenu.launch(searchButton); 129 } 130 }); 131 132 createLayout(lstPrimitives, true, Arrays.asList(new SideButton[] { 133 selectButton, searchButton, new SideButton(actShowHistory) 134 })); 135 } 136 137 /** 138 * Constructs a new {@code SelectionListDialog}. 139 */ 140 public SelectionListDialog() { 141 super(tr("Selection"), "selectionlist", tr("Open a selection list window."), 142 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", 143 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT), 144 150, // default height 145 true // default is "show dialog" 146 ); 147 148 buildContentPanel(); 149 model.addListDataListener(new TitleUpdater()); 150 model.addListDataListener(actZoomToJOSMSelection); 151 152 popupMenu = new ListPopupMenu(lstPrimitives); 153 popupMenuHandler = setupPopupMenuHandler(); 154 155 lstPrimitives.addListSelectionListener(new ListSelectionListener() { 156 @Override 157 public void valueChanged(ListSelectionEvent e) { 158 actZoomToListSelection.valueChanged(e); 159 popupMenuHandler.setPrimitives(model.getSelected()); 160 } 161 }); 162 163 lstPrimitives.addMouseListener(new MouseEventHandler()); 164 165 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection); 166 } 167 168 @Override 169 public void showNotify() { 170 MapView.addEditLayerChangeListener(model); 171 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED); 172 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED); 173 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT); 174 MapView.addEditLayerChangeListener(actSearch); 175 // editLayerChanged also gets the selection history of the level 176 OsmDataLayer editLayer = Main.main.getEditLayer(); 177 model.editLayerChanged(null, editLayer); 178 if (editLayer != null) { 179 model.setJOSMSelection(editLayer.data.getAllSelected()); 180 } 181 actSearch.updateEnabledState(); 182 } 183 184 @Override 185 public void hideNotify() { 186 MapView.removeEditLayerChangeListener(actSearch); 187 MapView.removeEditLayerChangeListener(model); 188 SelectionEventManager.getInstance().removeSelectionListener(actShowHistory); 189 SelectionEventManager.getInstance().removeSelectionListener(model); 190 DatasetEventManager.getInstance().removeDatasetListener(model); 191 } 192 193 /** 194 * Responds to double clicks on the list of selected objects and launches the popup menu 195 */ 196 class MouseEventHandler extends PopupMenuLauncher { 197 private final HighlightHelper helper = new HighlightHelper(); 198 private boolean highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true); 199 public MouseEventHandler() { 200 super(popupMenu); 201 } 202 203 @Override 204 public void mouseClicked(MouseEvent e) { 205 int idx = lstPrimitives.locationToIndex(e.getPoint()); 206 if (idx < 0) return; 207 if (isDoubleClick(e)) { 208 OsmDataLayer layer = Main.main.getEditLayer(); 209 if (layer == null) return; 210 OsmPrimitive osm = model.getElementAt(idx); 211 Collection<OsmPrimitive> sel = layer.data.getSelected(); 212 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) { 213 // Select primitive if it's not the whole current selection 214 layer.data.setSelected(Collections.singleton(osm)); 215 } else if (osm instanceof Relation) { 216 // else open relation editor if applicable 217 actEditRelationSelection.actionPerformed(null); 218 } 219 } else if (highlightEnabled && Main.isDisplayingMapView()) { 220 if (helper.highlightOnly(model.getElementAt(idx))) { 221 Main.map.mapView.repaint(); 222 } 223 } 224 } 225 226 @Override 227 public void mouseExited(MouseEvent me) { 228 if (highlightEnabled) helper.clear(); 229 super.mouseExited(me); 230 } 231 } 232 233 private PopupMenuHandler setupPopupMenuHandler() { 234 PopupMenuHandler handler = new PopupMenuHandler(popupMenu); 235 handler.addAction(actZoomToJOSMSelection); 236 handler.addAction(actZoomToListSelection); 237 handler.addSeparator(); 238 handler.addAction(actSetRelationSelection); 239 handler.addAction(actEditRelationSelection); 240 handler.addSeparator(); 241 handler.addAction(actDownloadSelIncompleteMembers); 242 return handler; 243 } 244 245 /** 246 * Replies the popup menu handler. 247 * @return The popup menu handler 248 */ 249 public PopupMenuHandler getPopupMenuHandler() { 250 return popupMenuHandler; 251 } 252 253 /** 254 * Replies the selected OSM primitives. 255 * @return The selected OSM primitives 256 */ 257 public Collection<OsmPrimitive> getSelectedPrimitives() { 258 return model.getSelected(); 259 } 260 261 /** 262 * Updates the dialog title with a summary of the current JOSM selection 263 */ 264 class TitleUpdater implements ListDataListener { 265 protected void updateTitle() { 266 setTitle(model.getJOSMSelectionSummary()); 267 } 268 269 @Override 270 public void contentsChanged(ListDataEvent e) { 271 updateTitle(); 272 } 273 274 @Override 275 public void intervalAdded(ListDataEvent e) { 276 updateTitle(); 277 } 278 279 @Override 280 public void intervalRemoved(ListDataEvent e) { 281 updateTitle(); 282 } 283 } 284 285 /** 286 * Launches the search dialog 287 */ 288 static class SearchAction extends AbstractAction implements EditLayerChangeListener { 289 /** 290 * Constructs a new {@code SearchAction}. 291 */ 292 public SearchAction() { 293 putValue(NAME, tr("Search")); 294 putValue(SHORT_DESCRIPTION, tr("Search for objects")); 295 putValue(SMALL_ICON, ImageProvider.get("dialogs","search")); 296 updateEnabledState(); 297 } 298 299 @Override 300 public void actionPerformed(ActionEvent e) { 301 if (!isEnabled()) return; 302 org.openstreetmap.josm.actions.search.SearchAction.search(); 303 } 304 305 protected void updateEnabledState() { 306 setEnabled(Main.main != null && Main.main.hasEditLayer()); 307 } 308 309 @Override 310 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 311 updateEnabledState(); 312 } 313 } 314 315 /** 316 * Sets the current JOSM selection to the OSM primitives selected in the list 317 * of this dialog 318 */ 319 class SelectAction extends AbstractAction implements ListSelectionListener { 320 /** 321 * Constructs a new {@code SelectAction}. 322 */ 323 public SelectAction() { 324 putValue(NAME, tr("Select")); 325 putValue(SHORT_DESCRIPTION, tr("Set the selected elements on the map to the selected items in the list above.")); 326 putValue(SMALL_ICON, ImageProvider.get("dialogs","select")); 327 updateEnabledState(); 328 } 329 330 @Override 331 public void actionPerformed(ActionEvent e) { 332 Collection<OsmPrimitive> sel = model.getSelected(); 333 if (sel.isEmpty())return; 334 OsmDataLayer editLayer = Main.main.getEditLayer(); 335 if (editLayer == null) return; 336 editLayer.data.setSelected(sel); 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(), n2=it.next(); 614 // show distance between two selected nodes 615 if (n1 instanceof Node && n2 instanceof Node) { 616 double d = ((Node) n1).getCoor().greatCircleDistance(((Node) n2).getCoor()); 617 Main.map.statusLine.setDist(d); 618 return; 619 } 620 } 621 Main.map.statusLine.setDist(new SubclassFilteredCollection<OsmPrimitive, Way>(selection, OsmPrimitive.wayPredicate)); 622 } 623 } 624 }); 625 } 626 627 /** 628 * Triggers a refresh of the view for all primitives in {@code toUpdate} 629 * which are currently displayed in the view 630 * 631 * @param toUpdate the collection of primitives to update 632 */ 633 public void update(Collection<? extends OsmPrimitive> toUpdate) { 634 if (toUpdate == null) return; 635 if (toUpdate.isEmpty()) return; 636 Collection<OsmPrimitive> sel = getSelected(); 637 for (OsmPrimitive p: toUpdate){ 638 int i = selection.indexOf(p); 639 if (i >= 0) { 640 super.fireContentsChanged(this, i,i); 641 } 642 } 643 setSelected(sel); 644 } 645 646 /** 647 * Sorts the current elements in the selection 648 */ 649 public void sort() { 650 if (this.selection.size() <= Main.pref.getInteger("selection.no_sort_above", 100000)) { 651 boolean quick = this.selection.size() > Main.pref.getInteger("selection.fast_sort_above", 10000); 652 Collections.sort(this.selection, new OsmPrimitiveComparator(quick, false)); 653 } 654 } 655 656 /* ------------------------------------------------------------------------ */ 657 /* interface EditLayerChangeListener */ 658 /* ------------------------------------------------------------------------ */ 659 @Override 660 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 661 if (newLayer == null) { 662 setJOSMSelection(null); 663 history = null; 664 } else { 665 history = newLayer.data.getSelectionHistory(); 666 setJOSMSelection(newLayer.data.getAllSelected()); 667 } 668 } 669 670 /* ------------------------------------------------------------------------ */ 671 /* interface SelectionChangeListener */ 672 /* ------------------------------------------------------------------------ */ 673 @Override 674 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 675 setJOSMSelection(newSelection); 676 } 677 678 /* ------------------------------------------------------------------------ */ 679 /* interface DataSetListener */ 680 /* ------------------------------------------------------------------------ */ 681 @Override 682 public void dataChanged(DataChangedEvent event) { 683 // refresh the whole list 684 fireContentsChanged(this, 0, getSize()); 685 } 686 687 @Override 688 public void nodeMoved(NodeMovedEvent event) { 689 // may influence the display name of primitives, update the data 690 update(event.getPrimitives()); 691 } 692 693 @Override 694 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 695 // may influence the display name of primitives, update the data 696 update(event.getPrimitives()); 697 } 698 699 @Override 700 public void relationMembersChanged(RelationMembersChangedEvent event) { 701 // may influence the display name of primitives, update the data 702 update(event.getPrimitives()); 703 } 704 705 @Override 706 public void tagsChanged(TagsChangedEvent event) { 707 // may influence the display name of primitives, update the data 708 update(event.getPrimitives()); 709 } 710 711 @Override 712 public void wayNodesChanged(WayNodesChangedEvent event) { 713 // may influence the display name of primitives, update the data 714 update(event.getPrimitives()); 715 } 716 717 @Override 718 public void primitivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */} 719 @Override 720 public void primitivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/} 721 } 722 723 /** 724 * A specialized {@link JMenuItem} for presenting one entry of the search history 725 * 726 * @author Jan Peter Stotz 727 */ 728 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 729 protected final SearchSetting s; 730 731 public SearchMenuItem(SearchSetting s) { 732 super(Utils.shortenString(s.toString(), org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY)); 733 this.s = s; 734 addActionListener(this); 735 } 736 737 @Override 738 public void actionPerformed(ActionEvent e) { 739 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 740 } 741 } 742 743 /** 744 * The popup menu for the search history entries 745 * 746 */ 747 protected static class SearchPopupMenu extends JPopupMenu { 748 public static void launch(Component parent) { 749 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 750 return; 751 JPopupMenu menu = new SearchPopupMenu(); 752 Rectangle r = parent.getBounds(); 753 menu.show(parent, r.x, r.y + r.height); 754 } 755 756 public SearchPopupMenu() { 757 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 758 add(new SearchMenuItem(ss)); 759 } 760 } 761 } 762 763 /** 764 * A specialized {@link JMenuItem} for presenting one entry of the selection history 765 * 766 * @author Jan Peter Stotz 767 */ 768 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 769 private final DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 770 protected Collection<? extends OsmPrimitive> sel; 771 772 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 773 super(); 774 this.sel = sel; 775 int ways = 0; 776 int nodes = 0; 777 int relations = 0; 778 for (OsmPrimitive o : sel) { 779 if (! o.isSelectable()) continue; // skip unselectable primitives 780 if (o instanceof Way) { 781 ways++; 782 } else if (o instanceof Node) { 783 nodes++; 784 } else if (o instanceof Relation) { 785 relations++; 786 } 787 } 788 StringBuilder text = new StringBuilder(); 789 if(ways != 0) { 790 text.append(text.length() > 0 ? ", " : "") 791 .append(trn("{0} way", "{0} ways", ways, ways)); 792 } 793 if(nodes != 0) { 794 text.append(text.length() > 0 ? ", " : "") 795 .append(trn("{0} node", "{0} nodes", nodes, nodes)); 796 } 797 if(relations != 0) { 798 text.append(text.length() > 0 ? ", " : "") 799 .append(trn("{0} relation", "{0} relations", relations, relations)); 800 } 801 if(ways + nodes + relations == 0) { 802 text.append(tr("Unselectable now")); 803 this.sel=new ArrayList<>(); // empty selection 804 } 805 if(ways + nodes + relations == 1) 806 { 807 text.append(": "); 808 for(OsmPrimitive o : sel) { 809 text.append(o.getDisplayName(df)); 810 } 811 setText(text.toString()); 812 } else { 813 setText(tr("Selection: {0}", text)); 814 } 815 addActionListener(this); 816 } 817 818 @Override 819 public void actionPerformed(ActionEvent e) { 820 Main.main.getCurrentDataSet().setSelected(sel); 821 } 822 } 823 824 /** 825 * The popup menu for the JOSM selection history entries 826 */ 827 protected static class SelectionHistoryPopup extends JPopupMenu { 828 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) { 829 if (history == null || history.isEmpty()) return; 830 JPopupMenu menu = new SelectionHistoryPopup(history); 831 Rectangle r = parent.getBounds(); 832 menu.show(parent, r.x, r.y + r.height); 833 } 834 835 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) { 836 for (Collection<? extends OsmPrimitive> sel : history) { 837 add(new SelectionMenuItem(sel)); 838 } 839 } 840 } 841}