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.Comparator; 020import java.util.HashSet; 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.actions.AbstractSelectAction; 040import org.openstreetmap.josm.actions.AutoScaleAction; 041import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 042import org.openstreetmap.josm.actions.relation.EditRelationAction; 043import org.openstreetmap.josm.actions.relation.SelectInRelationListAction; 044import org.openstreetmap.josm.data.SelectionChangedListener; 045import org.openstreetmap.josm.data.osm.DataSet; 046import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 047import org.openstreetmap.josm.data.osm.Node; 048import org.openstreetmap.josm.data.osm.OsmPrimitive; 049import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 050import org.openstreetmap.josm.data.osm.Relation; 051import org.openstreetmap.josm.data.osm.Way; 052import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 053import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 054import org.openstreetmap.josm.data.osm.event.DataSetListener; 055import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 056import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 057import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 058import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 059import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 060import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 061import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 062import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 063import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 064import org.openstreetmap.josm.data.osm.search.SearchSetting; 065import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 066import org.openstreetmap.josm.gui.MainApplication; 067import org.openstreetmap.josm.gui.PrimitiveRenderer; 068import org.openstreetmap.josm.gui.PopupMenuHandler; 069import org.openstreetmap.josm.gui.SideButton; 070import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable; 071import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData; 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.util.GuiHelper; 076import org.openstreetmap.josm.gui.util.HighlightHelper; 077import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 078import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 079import org.openstreetmap.josm.spi.preferences.Config; 080import org.openstreetmap.josm.tools.ImageProvider; 081import org.openstreetmap.josm.tools.InputMapUtils; 082import org.openstreetmap.josm.tools.Shortcut; 083import org.openstreetmap.josm.tools.Utils; 084import org.openstreetmap.josm.tools.bugreport.BugReport; 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 PrimitiveRenderer()); 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(e -> SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory())); 127 128 // the search button 129 final SideButton searchButton = new SideButton(actSearch); 130 searchButton.createArrow(e -> SearchPopupMenu.launch(searchButton), true); 131 132 createLayout(lstPrimitives, true, Arrays.asList( 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(e -> { 156 actZoomToListSelection.valueChanged(e); 157 popupMenuHandler.setPrimitives(model.getSelected()); 158 }); 159 160 lstPrimitives.addMouseListener(new MouseEventHandler()); 161 162 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection); 163 } 164 165 @Override 166 public void showNotify() { 167 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED); 168 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED); 169 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT); 170 MainApplication.getLayerManager().addActiveLayerChangeListener(actSearch); 171 // editLayerChanged also gets the selection history of the level. Listener calls setJOSMSelection when fired. 172 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(model); 173 actSearch.updateEnabledState(); 174 } 175 176 @Override 177 public void hideNotify() { 178 MainApplication.getLayerManager().removeActiveLayerChangeListener(actSearch); 179 MainApplication.getLayerManager().removeActiveLayerChangeListener(model); 180 SelectionEventManager.getInstance().removeSelectionListener(actShowHistory); 181 SelectionEventManager.getInstance().removeSelectionListener(model); 182 DatasetEventManager.getInstance().removeDatasetListener(model); 183 } 184 185 /** 186 * Responds to double clicks on the list of selected objects and launches the popup menu 187 */ 188 class MouseEventHandler extends PopupMenuLauncher { 189 private final HighlightHelper helper = new HighlightHelper(); 190 private final boolean highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true); 191 192 MouseEventHandler() { 193 super(popupMenu); 194 } 195 196 @Override 197 public void mouseClicked(MouseEvent e) { 198 int idx = lstPrimitives.locationToIndex(e.getPoint()); 199 if (idx < 0) return; 200 if (isDoubleClick(e)) { 201 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 202 if (ds == null) return; 203 OsmPrimitive osm = model.getElementAt(idx); 204 Collection<OsmPrimitive> sel = ds.getSelected(); 205 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) { 206 // Select primitive if it's not the whole current selection 207 ds.setSelected(Collections.singleton(osm)); 208 } else if (osm instanceof Relation) { 209 // else open relation editor if applicable 210 actEditRelationSelection.actionPerformed(null); 211 } 212 } else if (highlightEnabled && MainApplication.isDisplayingMapView() && helper.highlightOnly(model.getElementAt(idx))) { 213 MainApplication.getMap().mapView.repaint(); 214 } 215 } 216 217 @Override 218 public void mouseExited(MouseEvent me) { 219 if (highlightEnabled) helper.clear(); 220 super.mouseExited(me); 221 } 222 } 223 224 private PopupMenuHandler setupPopupMenuHandler() { 225 PopupMenuHandler handler = new PopupMenuHandler(popupMenu); 226 handler.addAction(actZoomToJOSMSelection); 227 handler.addAction(actZoomToListSelection); 228 handler.addSeparator(); 229 handler.addAction(actSetRelationSelection); 230 handler.addAction(actEditRelationSelection); 231 handler.addSeparator(); 232 handler.addAction(actDownloadSelIncompleteMembers); 233 return handler; 234 } 235 236 /** 237 * Replies the popup menu handler. 238 * @return The popup menu handler 239 */ 240 public PopupMenuHandler getPopupMenuHandler() { 241 return popupMenuHandler; 242 } 243 244 /** 245 * Replies the selected OSM primitives. 246 * @return The selected OSM primitives 247 */ 248 public Collection<OsmPrimitive> getSelectedPrimitives() { 249 return model.getSelected(); 250 } 251 252 /** 253 * Updates the dialog title with a summary of the current JOSM selection 254 */ 255 class TitleUpdater implements ListDataListener { 256 protected void updateTitle() { 257 setTitle(model.getJOSMSelectionSummary()); 258 } 259 260 @Override 261 public void contentsChanged(ListDataEvent e) { 262 updateTitle(); 263 } 264 265 @Override 266 public void intervalAdded(ListDataEvent e) { 267 updateTitle(); 268 } 269 270 @Override 271 public void intervalRemoved(ListDataEvent e) { 272 updateTitle(); 273 } 274 } 275 276 /** 277 * Launches the search dialog 278 */ 279 static class SearchAction extends AbstractAction implements ActiveLayerChangeListener { 280 /** 281 * Constructs a new {@code SearchAction}. 282 */ 283 SearchAction() { 284 putValue(NAME, tr("Search")); 285 putValue(SHORT_DESCRIPTION, tr("Search for objects")); 286 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true); 287 updateEnabledState(); 288 } 289 290 @Override 291 public void actionPerformed(ActionEvent e) { 292 if (!isEnabled()) return; 293 org.openstreetmap.josm.actions.search.SearchAction.search(); 294 } 295 296 protected void updateEnabledState() { 297 setEnabled(MainApplication.getLayerManager().getActiveDataSet() != null); 298 } 299 300 @Override 301 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 302 updateEnabledState(); 303 } 304 } 305 306 /** 307 * Sets the current JOSM selection to the OSM primitives selected in the list 308 * of this dialog 309 */ 310 class SelectAction extends AbstractSelectAction implements ListSelectionListener { 311 /** 312 * Constructs a new {@code SelectAction}. 313 */ 314 SelectAction() { 315 updateEnabledState(); 316 } 317 318 @Override 319 public void actionPerformed(ActionEvent e) { 320 Collection<OsmPrimitive> sel = model.getSelected(); 321 if (sel.isEmpty()) return; 322 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 323 if (ds == null) return; 324 ds.setSelected(sel); 325 model.selectionModel.setSelectionInterval(0, sel.size()-1); 326 } 327 328 protected void updateEnabledState() { 329 setEnabled(!model.isSelectionEmpty()); 330 } 331 332 @Override 333 public void valueChanged(ListSelectionEvent e) { 334 updateEnabledState(); 335 } 336 } 337 338 /** 339 * The action for showing history information of the current history item. 340 */ 341 class ShowHistoryAction extends AbstractAction implements ListSelectionListener, SelectionChangedListener { 342 /** 343 * Constructs a new {@code ShowHistoryAction}. 344 */ 345 ShowHistoryAction() { 346 putValue(NAME, tr("History")); 347 putValue(SHORT_DESCRIPTION, tr("Display the history of the selected objects.")); 348 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true); 349 updateEnabledState(model.getSize()); 350 } 351 352 @Override 353 public void actionPerformed(ActionEvent e) { 354 Collection<OsmPrimitive> sel = model.getSelected(); 355 if (sel.isEmpty() && model.getSize() != 1) { 356 return; 357 } else if (sel.isEmpty()) { 358 sel = Collections.singleton(model.getElementAt(0)); 359 } 360 HistoryBrowserDialogManager.getInstance().showHistory(sel); 361 } 362 363 protected void updateEnabledState(int osmSelectionSize) { 364 // See #10830 - allow to click on history button is a single object is selected, even if not selected again in the list 365 setEnabled(!model.isSelectionEmpty() || osmSelectionSize == 1); 366 } 367 368 @Override 369 public void valueChanged(ListSelectionEvent e) { 370 updateEnabledState(model.getSize()); 371 } 372 373 @Override 374 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 375 updateEnabledState(newSelection.size()); 376 } 377 } 378 379 /** 380 * The action for zooming to the primitives in the current JOSM selection 381 * 382 */ 383 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener { 384 385 ZoomToJOSMSelectionAction() { 386 putValue(NAME, tr("Zoom to selection")); 387 putValue(SHORT_DESCRIPTION, tr("Zoom to selection")); 388 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); 389 updateEnabledState(); 390 } 391 392 @Override 393 public void actionPerformed(ActionEvent e) { 394 AutoScaleAction.autoScale("selection"); 395 } 396 397 public void updateEnabledState() { 398 setEnabled(model.getSize() > 0); 399 } 400 401 @Override 402 public void contentsChanged(ListDataEvent e) { 403 updateEnabledState(); 404 } 405 406 @Override 407 public void intervalAdded(ListDataEvent e) { 408 updateEnabledState(); 409 } 410 411 @Override 412 public void intervalRemoved(ListDataEvent e) { 413 updateEnabledState(); 414 } 415 } 416 417 /** 418 * The action for zooming to the primitives which are currently selected in 419 * the list displaying the JOSM selection 420 * 421 */ 422 class ZoomToListSelection extends AbstractAction implements ListSelectionListener { 423 /** 424 * Constructs a new {@code ZoomToListSelection}. 425 */ 426 ZoomToListSelection() { 427 putValue(NAME, tr("Zoom to selected element(s)")); 428 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)")); 429 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); 430 updateEnabledState(); 431 } 432 433 @Override 434 public void actionPerformed(ActionEvent e) { 435 BoundingXYVisitor box = new BoundingXYVisitor(); 436 Collection<OsmPrimitive> sel = model.getSelected(); 437 if (sel.isEmpty()) return; 438 box.computeBoundingBox(sel); 439 if (box.getBounds() == null) 440 return; 441 box.enlargeBoundingBox(); 442 MainApplication.getMap().mapView.zoomTo(box); 443 } 444 445 protected void updateEnabledState() { 446 setEnabled(!model.isSelectionEmpty()); 447 } 448 449 @Override 450 public void valueChanged(ListSelectionEvent e) { 451 updateEnabledState(); 452 } 453 } 454 455 /** 456 * The list model for the list of OSM primitives in the current JOSM selection. 457 * 458 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE} 459 * JOSM selection. 460 * 461 */ 462 static class SelectionListModel extends AbstractListModel<OsmPrimitive> 463 implements ActiveLayerChangeListener, SelectionChangedListener, DataSetListener { 464 465 private static final int SELECTION_HISTORY_SIZE = 10; 466 467 // Variable to store history from currentDataSet() 468 private LinkedList<Collection<? extends OsmPrimitive>> history; 469 private final transient List<OsmPrimitive> selection = new ArrayList<>(); 470 private final DefaultListSelectionModel selectionModel; 471 472 /** 473 * Constructor 474 * @param selectionModel the selection model used in the list 475 */ 476 SelectionListModel(DefaultListSelectionModel selectionModel) { 477 this.selectionModel = selectionModel; 478 } 479 480 /** 481 * Replies a summary of the current JOSM selection 482 * 483 * @return a summary of the current JOSM selection 484 */ 485 public synchronized String getJOSMSelectionSummary() { 486 if (selection.isEmpty()) return tr("Selection"); 487 int numNodes = 0; 488 int numWays = 0; 489 int numRelations = 0; 490 for (OsmPrimitive p: selection) { 491 switch(p.getType()) { 492 case NODE: numNodes++; break; 493 case WAY: numWays++; break; 494 case RELATION: numRelations++; break; 495 default: throw new AssertionError(); 496 } 497 } 498 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes); 499 } 500 501 /** 502 * Remembers a JOSM selection the history of JOSM selections 503 * 504 * @param selection the JOSM selection. Ignored if null or empty. 505 */ 506 public void remember(Collection<? extends OsmPrimitive> selection) { 507 if (selection == null) return; 508 if (selection.isEmpty()) return; 509 if (history == null) return; 510 if (history.isEmpty()) { 511 history.add(selection); 512 return; 513 } 514 if (history.getFirst().equals(selection)) return; 515 history.addFirst(selection); 516 for (int i = 1; i < history.size(); ++i) { 517 if (history.get(i).equals(selection)) { 518 history.remove(i); 519 break; 520 } 521 } 522 int maxsize = Config.getPref().getInt("select.history-size", SELECTION_HISTORY_SIZE); 523 while (history.size() > maxsize) { 524 history.removeLast(); 525 } 526 } 527 528 /** 529 * Replies the history of JOSM selections 530 * 531 * @return history of JOSM selections 532 */ 533 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() { 534 return history; 535 } 536 537 @Override 538 public synchronized OsmPrimitive getElementAt(int index) { 539 return selection.get(index); 540 } 541 542 @Override 543 public synchronized int getSize() { 544 return selection.size(); 545 } 546 547 /** 548 * Determines if no OSM primitives are currently selected. 549 * @return {@code true} if no OSM primitives are currently selected 550 * @since 10383 551 */ 552 public boolean isSelectionEmpty() { 553 return selectionModel.isSelectionEmpty(); 554 } 555 556 /** 557 * Replies the collection of OSM primitives currently selected in the view of this model 558 * 559 * @return choosen elements in the view 560 */ 561 public synchronized Collection<OsmPrimitive> getSelected() { 562 Set<OsmPrimitive> sel = new HashSet<>(); 563 for (int i = 0; i < getSize(); i++) { 564 if (selectionModel.isSelectedIndex(i)) { 565 sel.add(selection.get(i)); 566 } 567 } 568 return sel; 569 } 570 571 /** 572 * Sets the OSM primitives to be selected in the view of this model 573 * 574 * @param sel the collection of primitives to select 575 */ 576 public synchronized void setSelected(Collection<OsmPrimitive> sel) { 577 selectionModel.setValueIsAdjusting(true); 578 selectionModel.clearSelection(); 579 if (sel != null) { 580 for (OsmPrimitive p: sel) { 581 int i = selection.indexOf(p); 582 if (i >= 0) { 583 selectionModel.addSelectionInterval(i, i); 584 } 585 } 586 } 587 selectionModel.setValueIsAdjusting(false); 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 } 616 } 617 }); 618 } 619 620 /** 621 * Triggers a refresh of the view for all primitives in {@code toUpdate} 622 * which are currently displayed in the view 623 * 624 * @param toUpdate the collection of primitives to update 625 */ 626 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) { 627 if (toUpdate == null) return; 628 if (toUpdate.isEmpty()) return; 629 Collection<OsmPrimitive> sel = getSelected(); 630 for (OsmPrimitive p: toUpdate) { 631 int i = selection.indexOf(p); 632 if (i >= 0) { 633 super.fireContentsChanged(this, i, i); 634 } 635 } 636 setSelected(sel); 637 } 638 639 /** 640 * Sorts the current elements in the selection 641 */ 642 public synchronized void sort() { 643 int size = selection.size(); 644 if (size > 1 && size <= Config.getPref().getInt("selection.no_sort_above", 100_000)) { 645 boolean quick = size > Config.getPref().getInt("selection.fast_sort_above", 10_000); 646 Comparator<OsmPrimitive> c = Config.getPref().getBoolean("selection.sort_relations_before_ways", true) 647 ? OsmPrimitiveComparator.orderingRelationsWaysNodes() 648 : OsmPrimitiveComparator.orderingWaysRelationsNodes(); 649 try { 650 selection.sort(c.thenComparing(quick 651 ? OsmPrimitiveComparator.comparingUniqueId() 652 : OsmPrimitiveComparator.comparingNames())); 653 } catch (IllegalArgumentException e) { 654 throw BugReport.intercept(e).put("size", size).put("quick", quick).put("selection", selection); 655 } 656 } 657 } 658 659 /* ------------------------------------------------------------------------ */ 660 /* interface ActiveLayerChangeListener */ 661 /* ------------------------------------------------------------------------ */ 662 @Override 663 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 664 DataSet newData = e.getSource().getEditDataSet(); 665 if (newData == null) { 666 setJOSMSelection(null); 667 history = null; 668 } else { 669 history = newData.getSelectionHistory(); 670 setJOSMSelection(newData.getAllSelected()); 671 } 672 } 673 674 /* ------------------------------------------------------------------------ */ 675 /* interface SelectionChangedListener */ 676 /* ------------------------------------------------------------------------ */ 677 @Override 678 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 679 setJOSMSelection(newSelection); 680 } 681 682 /* ------------------------------------------------------------------------ */ 683 /* interface DataSetListener */ 684 /* ------------------------------------------------------------------------ */ 685 @Override 686 public void dataChanged(DataChangedEvent event) { 687 // refresh the whole list 688 fireContentsChanged(this, 0, getSize()); 689 } 690 691 @Override 692 public void nodeMoved(NodeMovedEvent event) { 693 // may influence the display name of primitives, update the data 694 update(event.getPrimitives()); 695 } 696 697 @Override 698 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 699 // may influence the display name of primitives, update the data 700 update(event.getPrimitives()); 701 } 702 703 @Override 704 public void relationMembersChanged(RelationMembersChangedEvent event) { 705 // may influence the display name of primitives, update the data 706 update(event.getPrimitives()); 707 } 708 709 @Override 710 public void tagsChanged(TagsChangedEvent event) { 711 // may influence the display name of primitives, update the data 712 update(event.getPrimitives()); 713 } 714 715 @Override 716 public void wayNodesChanged(WayNodesChangedEvent event) { 717 // may influence the display name of primitives, update the data 718 update(event.getPrimitives()); 719 } 720 721 @Override 722 public void primitivesAdded(PrimitivesAddedEvent event) { 723 /* ignored - handled by SelectionChangeListener */ 724 } 725 726 @Override 727 public void primitivesRemoved(PrimitivesRemovedEvent event) { 728 /* ignored - handled by SelectionChangeListener*/ 729 } 730 } 731 732 /** 733 * A specialized {@link JMenuItem} for presenting one entry of the search history 734 * 735 * @author Jan Peter Stotz 736 */ 737 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 738 protected final transient SearchSetting s; 739 740 public SearchMenuItem(SearchSetting s) { 741 super(Utils.shortenString(s.toString(), 742 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY)); 743 this.s = s; 744 addActionListener(this); 745 } 746 747 @Override 748 public void actionPerformed(ActionEvent e) { 749 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 750 } 751 } 752 753 /** 754 * The popup menu for the search history entries 755 * 756 */ 757 protected static class SearchPopupMenu extends JPopupMenu { 758 public static void launch(Component parent) { 759 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 760 return; 761 JPopupMenu menu = new SearchPopupMenu(); 762 Rectangle r = parent.getBounds(); 763 menu.show(parent, r.x, r.y + r.height); 764 } 765 766 /** 767 * Constructs a new {@code SearchPopupMenu}. 768 */ 769 public SearchPopupMenu() { 770 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 771 add(new SearchMenuItem(ss)); 772 } 773 } 774 } 775 776 /** 777 * A specialized {@link JMenuItem} for presenting one entry of the selection history 778 * 779 * @author Jan Peter Stotz 780 */ 781 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 782 protected transient Collection<? extends OsmPrimitive> sel; 783 784 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 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 DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 817 if (ways + nodes + relations == 1) { 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 MainApplication.getLayerManager().getActiveDataSet().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 853 /** 854 * A transfer handler class for drag-and-drop support. 855 */ 856 protected class SelectionTransferHandler extends TransferHandler { 857 858 @Override 859 public int getSourceActions(JComponent c) { 860 return COPY; 861 } 862 863 @Override 864 protected Transferable createTransferable(JComponent c) { 865 return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives())); 866 } 867 } 868}