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 transient 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 final boolean highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true); 201 202 MouseEventHandler() { 203 super(popupMenu); 204 } 205 206 @Override 207 public void mouseClicked(MouseEvent e) { 208 int idx = lstPrimitives.locationToIndex(e.getPoint()); 209 if (idx < 0) return; 210 if (isDoubleClick(e)) { 211 OsmDataLayer layer = Main.main.getEditLayer(); 212 if (layer == null) return; 213 OsmPrimitive osm = model.getElementAt(idx); 214 Collection<OsmPrimitive> sel = layer.data.getSelected(); 215 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) { 216 // Select primitive if it's not the whole current selection 217 layer.data.setSelected(Collections.singleton(osm)); 218 } else if (osm instanceof Relation) { 219 // else open relation editor if applicable 220 actEditRelationSelection.actionPerformed(null); 221 } 222 } else if (highlightEnabled && Main.isDisplayingMapView()) { 223 if (helper.highlightOnly(model.getElementAt(idx))) { 224 Main.map.mapView.repaint(); 225 } 226 } 227 } 228 229 @Override 230 public void mouseExited(MouseEvent me) { 231 if (highlightEnabled) helper.clear(); 232 super.mouseExited(me); 233 } 234 } 235 236 private PopupMenuHandler setupPopupMenuHandler() { 237 PopupMenuHandler handler = new PopupMenuHandler(popupMenu); 238 handler.addAction(actZoomToJOSMSelection); 239 handler.addAction(actZoomToListSelection); 240 handler.addSeparator(); 241 handler.addAction(actSetRelationSelection); 242 handler.addAction(actEditRelationSelection); 243 handler.addSeparator(); 244 handler.addAction(actDownloadSelIncompleteMembers); 245 return handler; 246 } 247 248 /** 249 * Replies the popup menu handler. 250 * @return The popup menu handler 251 */ 252 public PopupMenuHandler getPopupMenuHandler() { 253 return popupMenuHandler; 254 } 255 256 /** 257 * Replies the selected OSM primitives. 258 * @return The selected OSM primitives 259 */ 260 public Collection<OsmPrimitive> getSelectedPrimitives() { 261 return model.getSelected(); 262 } 263 264 /** 265 * Updates the dialog title with a summary of the current JOSM selection 266 */ 267 class TitleUpdater implements ListDataListener { 268 protected void updateTitle() { 269 setTitle(model.getJOSMSelectionSummary()); 270 } 271 272 @Override 273 public void contentsChanged(ListDataEvent e) { 274 updateTitle(); 275 } 276 277 @Override 278 public void intervalAdded(ListDataEvent e) { 279 updateTitle(); 280 } 281 282 @Override 283 public void intervalRemoved(ListDataEvent e) { 284 updateTitle(); 285 } 286 } 287 288 /** 289 * Launches the search dialog 290 */ 291 static class SearchAction extends AbstractAction implements EditLayerChangeListener { 292 /** 293 * Constructs a new {@code SearchAction}. 294 */ 295 SearchAction() { 296 putValue(NAME, tr("Search")); 297 putValue(SHORT_DESCRIPTION, tr("Search for objects")); 298 putValue(SMALL_ICON, ImageProvider.get("dialogs", "search")); 299 updateEnabledState(); 300 } 301 302 @Override 303 public void actionPerformed(ActionEvent e) { 304 if (!isEnabled()) return; 305 org.openstreetmap.josm.actions.search.SearchAction.search(); 306 } 307 308 protected void updateEnabledState() { 309 setEnabled(Main.main != null && Main.main.hasEditLayer()); 310 } 311 312 @Override 313 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 314 updateEnabledState(); 315 } 316 } 317 318 /** 319 * Sets the current JOSM selection to the OSM primitives selected in the list 320 * of this dialog 321 */ 322 class SelectAction extends AbstractSelectAction implements ListSelectionListener { 323 /** 324 * Constructs a new {@code SelectAction}. 325 */ 326 SelectAction() { 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 model.selectionModel.setSelectionInterval(0, sel.size()-1); 338 } 339 340 protected void updateEnabledState() { 341 setEnabled(!model.getSelected().isEmpty()); 342 } 343 344 @Override 345 public void valueChanged(ListSelectionEvent e) { 346 updateEnabledState(); 347 } 348 } 349 350 /** 351 * The action for showing history information of the current history item. 352 */ 353 class ShowHistoryAction extends AbstractAction implements ListSelectionListener, SelectionChangedListener { 354 /** 355 * Constructs a new {@code ShowHistoryAction}. 356 */ 357 ShowHistoryAction() { 358 putValue(NAME, tr("History")); 359 putValue(SHORT_DESCRIPTION, tr("Display the history of the selected objects.")); 360 putValue(SMALL_ICON, ImageProvider.get("dialogs", "history")); 361 updateEnabledState(model.getSize()); 362 } 363 364 @Override 365 public void actionPerformed(ActionEvent e) { 366 Collection<OsmPrimitive> sel = model.getSelected(); 367 if (sel.isEmpty() && model.getSize() != 1) { 368 return; 369 } else if (sel.isEmpty()) { 370 sel = Collections.singleton(model.getElementAt(0)); 371 } 372 HistoryBrowserDialogManager.getInstance().showHistory(sel); 373 } 374 375 protected void updateEnabledState(int osmSelectionSize) { 376 // See #10830 - allow to click on history button is a single object is selected, even if not selected again in the list 377 setEnabled(!model.getSelected().isEmpty() || osmSelectionSize == 1); 378 } 379 380 @Override 381 public void valueChanged(ListSelectionEvent e) { 382 updateEnabledState(model.getSize()); 383 } 384 385 @Override 386 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 387 updateEnabledState(newSelection.size()); 388 } 389 } 390 391 /** 392 * The action for zooming to the primitives in the current JOSM selection 393 * 394 */ 395 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener { 396 397 ZoomToJOSMSelectionAction() { 398 putValue(NAME, tr("Zoom to selection")); 399 putValue(SHORT_DESCRIPTION, tr("Zoom to selection")); 400 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 401 updateEnabledState(); 402 } 403 404 @Override 405 public void actionPerformed(ActionEvent e) { 406 AutoScaleAction.autoScale("selection"); 407 } 408 409 public void updateEnabledState() { 410 setEnabled(model.getSize() > 0); 411 } 412 413 @Override 414 public void contentsChanged(ListDataEvent e) { 415 updateEnabledState(); 416 } 417 418 @Override 419 public void intervalAdded(ListDataEvent e) { 420 updateEnabledState(); 421 } 422 423 @Override 424 public void intervalRemoved(ListDataEvent e) { 425 updateEnabledState(); 426 } 427 } 428 429 /** 430 * The action for zooming to the primitives which are currently selected in 431 * the list displaying the JOSM selection 432 * 433 */ 434 class ZoomToListSelection extends AbstractAction implements ListSelectionListener { 435 /** 436 * Constructs a new {@code ZoomToListSelection}. 437 */ 438 ZoomToListSelection() { 439 putValue(NAME, tr("Zoom to selected element(s)")); 440 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)")); 441 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 442 updateEnabledState(); 443 } 444 445 @Override 446 public void actionPerformed(ActionEvent e) { 447 BoundingXYVisitor box = new BoundingXYVisitor(); 448 Collection<OsmPrimitive> sel = model.getSelected(); 449 if (sel.isEmpty()) return; 450 box.computeBoundingBox(sel); 451 if (box.getBounds() == null) 452 return; 453 box.enlargeBoundingBox(); 454 Main.map.mapView.zoomTo(box); 455 } 456 457 protected void updateEnabledState() { 458 setEnabled(!model.getSelected().isEmpty()); 459 } 460 461 @Override 462 public void valueChanged(ListSelectionEvent e) { 463 updateEnabledState(); 464 } 465 } 466 467 /** 468 * The list model for the list of OSM primitives in the current JOSM selection. 469 * 470 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE} 471 * JOSM selection. 472 * 473 */ 474 private static class SelectionListModel extends AbstractListModel<OsmPrimitive> 475 implements EditLayerChangeListener, SelectionChangedListener, DataSetListener { 476 477 private static final int SELECTION_HISTORY_SIZE = 10; 478 479 // Variable to store history from currentDataSet() 480 private LinkedList<Collection<? extends OsmPrimitive>> history; 481 private final transient List<OsmPrimitive> selection = new ArrayList<>(); 482 private final DefaultListSelectionModel selectionModel; 483 484 /** 485 * Constructor 486 * @param selectionModel the selection model used in the list 487 */ 488 SelectionListModel(DefaultListSelectionModel selectionModel) { 489 this.selectionModel = selectionModel; 490 } 491 492 /** 493 * Replies a summary of the current JOSM selection 494 * 495 * @return a summary of the current JOSM selection 496 */ 497 public synchronized String getJOSMSelectionSummary() { 498 if (selection.isEmpty()) return tr("Selection"); 499 int numNodes = 0; 500 int numWays = 0; 501 int numRelations = 0; 502 for (OsmPrimitive p: selection) { 503 switch(p.getType()) { 504 case NODE: numNodes++; break; 505 case WAY: numWays++; break; 506 case RELATION: numRelations++; break; 507 } 508 } 509 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes); 510 } 511 512 /** 513 * Remembers a JOSM selection the history of JOSM selections 514 * 515 * @param selection the JOSM selection. Ignored if null or empty. 516 */ 517 public void remember(Collection<? extends OsmPrimitive> selection) { 518 if (selection == null) return; 519 if (selection.isEmpty()) return; 520 if (history == null) return; 521 if (history.isEmpty()) { 522 history.add(selection); 523 return; 524 } 525 if (history.getFirst().equals(selection)) return; 526 history.addFirst(selection); 527 for (int i = 1; i < history.size(); ++i) { 528 if (history.get(i).equals(selection)) { 529 history.remove(i); 530 break; 531 } 532 } 533 int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE); 534 while (history.size() > maxsize) { 535 history.removeLast(); 536 } 537 } 538 539 /** 540 * Replies the history of JOSM selections 541 * 542 * @return history of JOSM selections 543 */ 544 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() { 545 return history; 546 } 547 548 @Override 549 public synchronized OsmPrimitive getElementAt(int index) { 550 return selection.get(index); 551 } 552 553 @Override 554 public synchronized int getSize() { 555 return selection.size(); 556 } 557 558 /** 559 * Replies the collection of OSM primitives currently selected in the view 560 * of this model 561 * 562 * @return choosen elements in the view 563 */ 564 public synchronized Collection<OsmPrimitive> getSelected() { 565 Set<OsmPrimitive> sel = new HashSet<>(); 566 for (int i = 0; i < getSize(); i++) { 567 if (selectionModel.isSelectedIndex(i)) { 568 sel.add(selection.get(i)); 569 } 570 } 571 return sel; 572 } 573 574 /** 575 * Sets the OSM primitives to be selected in the view of this model 576 * 577 * @param sel the collection of primitives to select 578 */ 579 public synchronized void setSelected(Collection<OsmPrimitive> sel) { 580 selectionModel.clearSelection(); 581 if (sel == null) return; 582 for (OsmPrimitive p: sel) { 583 int i = selection.indexOf(p); 584 if (i >= 0) { 585 selectionModel.addSelectionInterval(i, i); 586 } 587 } 588 } 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 599 * 600 * @param selection the collection of currently selected OSM objects 601 */ 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 } 609 } 610 GuiHelper.runInEDTAndWait(new Runnable() { 611 @Override public void run() { 612 fireContentsChanged(this, 0, getSize()); 613 if (selection != null) { 614 remember(selection); 615 if (selection.size() == 2) { 616 Iterator<? extends OsmPrimitive> it = selection.iterator(); 617 OsmPrimitive n1 = it.next(); 618 OsmPrimitive n2 = it.next(); 619 // show distance between two selected nodes with coordinates 620 if (n1 instanceof Node && n2 instanceof Node) { 621 LatLon c1 = ((Node) n1).getCoor(); 622 LatLon c2 = ((Node) n2).getCoor(); 623 if (c1 != null && c2 != null) { 624 Main.map.statusLine.setDist(c1.greatCircleDistance(c2)); 625 return; 626 } 627 } 628 } 629 Main.map.statusLine.setDist( 630 new SubclassFilteredCollection<OsmPrimitive, Way>(selection, OsmPrimitive.wayPredicate)); 631 } 632 } 633 }); 634 } 635 636 /** 637 * Triggers a refresh of the view for all primitives in {@code toUpdate} 638 * which are currently displayed in the view 639 * 640 * @param toUpdate the collection of primitives to update 641 */ 642 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) { 643 if (toUpdate == null) return; 644 if (toUpdate.isEmpty()) return; 645 Collection<OsmPrimitive> sel = getSelected(); 646 for (OsmPrimitive p: toUpdate) { 647 int i = selection.indexOf(p); 648 if (i >= 0) { 649 super.fireContentsChanged(this, i, i); 650 } 651 } 652 setSelected(sel); 653 } 654 655 /** 656 * Sorts the current elements in the selection 657 */ 658 public synchronized void sort() { 659 if (this.selection.size() <= Main.pref.getInteger("selection.no_sort_above", 100000)) { 660 boolean quick = this.selection.size() > Main.pref.getInteger("selection.fast_sort_above", 10000); 661 Collections.sort(this.selection, new OsmPrimitiveComparator(quick, false)); 662 } 663 } 664 665 /* ------------------------------------------------------------------------ */ 666 /* interface EditLayerChangeListener */ 667 /* ------------------------------------------------------------------------ */ 668 @Override 669 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 670 if (newLayer == null) { 671 setJOSMSelection(null); 672 history = null; 673 } else { 674 history = newLayer.data.getSelectionHistory(); 675 setJOSMSelection(newLayer.data.getAllSelected()); 676 } 677 } 678 679 /* ------------------------------------------------------------------------ */ 680 /* interface SelectionChangeListener */ 681 /* ------------------------------------------------------------------------ */ 682 @Override 683 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 684 setJOSMSelection(newSelection); 685 } 686 687 /* ------------------------------------------------------------------------ */ 688 /* interface DataSetListener */ 689 /* ------------------------------------------------------------------------ */ 690 @Override 691 public void dataChanged(DataChangedEvent event) { 692 // refresh the whole list 693 fireContentsChanged(this, 0, getSize()); 694 } 695 696 @Override 697 public void nodeMoved(NodeMovedEvent event) { 698 // may influence the display name of primitives, update the data 699 update(event.getPrimitives()); 700 } 701 702 @Override 703 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 704 // may influence the display name of primitives, update the data 705 update(event.getPrimitives()); 706 } 707 708 @Override 709 public void relationMembersChanged(RelationMembersChangedEvent event) { 710 // may influence the display name of primitives, update the data 711 update(event.getPrimitives()); 712 } 713 714 @Override 715 public void tagsChanged(TagsChangedEvent event) { 716 // may influence the display name of primitives, update the data 717 update(event.getPrimitives()); 718 } 719 720 @Override 721 public void wayNodesChanged(WayNodesChangedEvent event) { 722 // may influence the display name of primitives, update the data 723 update(event.getPrimitives()); 724 } 725 726 @Override 727 public void primitivesAdded(PrimitivesAddedEvent event) { 728 /* ignored - handled by SelectionChangeListener */ 729 } 730 731 @Override 732 public void primitivesRemoved(PrimitivesRemovedEvent event) { 733 /* ignored - handled by SelectionChangeListener*/ 734 } 735 } 736 737 /** 738 * A specialized {@link JMenuItem} for presenting one entry of the search history 739 * 740 * @author Jan Peter Stotz 741 */ 742 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 743 protected final transient SearchSetting s; 744 745 public SearchMenuItem(SearchSetting s) { 746 super(Utils.shortenString(s.toString(), 747 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY)); 748 this.s = s; 749 addActionListener(this); 750 } 751 752 @Override 753 public void actionPerformed(ActionEvent e) { 754 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 755 } 756 } 757 758 /** 759 * The popup menu for the search history entries 760 * 761 */ 762 protected static class SearchPopupMenu extends JPopupMenu { 763 public static void launch(Component parent) { 764 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 765 return; 766 JPopupMenu menu = new SearchPopupMenu(); 767 Rectangle r = parent.getBounds(); 768 menu.show(parent, r.x, r.y + r.height); 769 } 770 771 /** 772 * Constructs a new {@code SearchPopupMenu}. 773 */ 774 public SearchPopupMenu() { 775 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 776 add(new SearchMenuItem(ss)); 777 } 778 } 779 } 780 781 /** 782 * A specialized {@link JMenuItem} for presenting one entry of the selection history 783 * 784 * @author Jan Peter Stotz 785 */ 786 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 787 protected transient Collection<? extends OsmPrimitive> sel; 788 789 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 790 this.sel = sel; 791 int ways = 0; 792 int nodes = 0; 793 int relations = 0; 794 for (OsmPrimitive o : sel) { 795 if (!o.isSelectable()) continue; // skip unselectable primitives 796 if (o instanceof Way) { 797 ways++; 798 } else if (o instanceof Node) { 799 nodes++; 800 } else if (o instanceof Relation) { 801 relations++; 802 } 803 } 804 StringBuilder text = new StringBuilder(); 805 if (ways != 0) { 806 text.append(text.length() > 0 ? ", " : "") 807 .append(trn("{0} way", "{0} ways", ways, ways)); 808 } 809 if (nodes != 0) { 810 text.append(text.length() > 0 ? ", " : "") 811 .append(trn("{0} node", "{0} nodes", nodes, nodes)); 812 } 813 if (relations != 0) { 814 text.append(text.length() > 0 ? ", " : "") 815 .append(trn("{0} relation", "{0} relations", relations, relations)); 816 } 817 if (ways + nodes + relations == 0) { 818 text.append(tr("Unselectable now")); 819 this.sel = new ArrayList<>(); // empty selection 820 } 821 DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 822 if (ways + nodes + relations == 1) { 823 text.append(": "); 824 for (OsmPrimitive o : sel) { 825 text.append(o.getDisplayName(df)); 826 } 827 setText(text.toString()); 828 } else { 829 setText(tr("Selection: {0}", text)); 830 } 831 addActionListener(this); 832 } 833 834 @Override 835 public void actionPerformed(ActionEvent e) { 836 Main.main.getCurrentDataSet().setSelected(sel); 837 } 838 } 839 840 /** 841 * The popup menu for the JOSM selection history entries 842 */ 843 protected static class SelectionHistoryPopup extends JPopupMenu { 844 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) { 845 if (history == null || history.isEmpty()) return; 846 JPopupMenu menu = new SelectionHistoryPopup(history); 847 Rectangle r = parent.getBounds(); 848 menu.show(parent, r.x, r.y + r.height); 849 } 850 851 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) { 852 for (Collection<? extends OsmPrimitive> sel : history) { 853 add(new SelectionMenuItem(sel)); 854 } 855 } 856 } 857}