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