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