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.Comparator; 018import java.util.HashSet; 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.OsmPrimitiveType; 046import org.openstreetmap.josm.data.osm.Relation; 047import org.openstreetmap.josm.data.osm.Way; 048import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 049import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 050import org.openstreetmap.josm.data.osm.event.DataSetListener; 051import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 052import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 053import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 054import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 055import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 056import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 057import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 058import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 059import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 060import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 061import org.openstreetmap.josm.gui.DefaultNameFormatter; 062import org.openstreetmap.josm.gui.MapView; 063import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener; 064import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 065import org.openstreetmap.josm.gui.PopupMenuHandler; 066import org.openstreetmap.josm.gui.SideButton; 067import org.openstreetmap.josm.gui.layer.OsmDataLayer; 068import org.openstreetmap.josm.gui.util.GuiHelper; 069import org.openstreetmap.josm.gui.util.HighlightHelper; 070import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 071import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 072import org.openstreetmap.josm.tools.ImageProvider; 073import org.openstreetmap.josm.tools.InputMapUtils; 074import org.openstreetmap.josm.tools.Shortcut; 075import org.openstreetmap.josm.tools.SubclassFilteredCollection; 076 077/** 078 * A small tool dialog for displaying the current selection. 079 * @since 8 080 */ 081public class SelectionListDialog extends ToggleDialog { 082 private JList 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((OsmPrimitive)model.getElementAt(idx))); 204 } else if (highlightEnabled && Main.isDisplayingMapView()) { 205 if (helper.highlightOnly((OsmPrimitive)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","select")); 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 static private class SelectionListModel extends AbstractListModel 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<OsmPrimitive>(); 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 Object 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<OsmPrimitive>(); 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 Main.map.statusLine.setDist(new SubclassFilteredCollection<OsmPrimitive, Way>(selection, OsmPrimitive.wayPredicate)); 547 } 548 } 549 }); 550 } 551 552 /** 553 * Triggers a refresh of the view for all primitives in {@code toUpdate} 554 * which are currently displayed in the view 555 * 556 * @param toUpdate the collection of primitives to update 557 */ 558 public void update(Collection<? extends OsmPrimitive> toUpdate) { 559 if (toUpdate == null) return; 560 if (toUpdate.isEmpty()) return; 561 Collection<OsmPrimitive> sel = getSelected(); 562 for (OsmPrimitive p: toUpdate){ 563 int i = selection.indexOf(p); 564 if (i >= 0) { 565 super.fireContentsChanged(this, i,i); 566 } 567 } 568 setSelected(sel); 569 } 570 571 /** 572 * Sorts the current elements in the selection 573 */ 574 public void sort() { 575 if (this.selection.size()>Main.pref.getInteger("selection.no_sort_above",100000)) return; 576 if (this.selection.size()>Main.pref.getInteger("selection.fast_sort_above",10000)) { 577 Collections.sort(this.selection, new OsmPrimitiveQuickComparator()); 578 } else { 579 Collections.sort(this.selection, new OsmPrimitiveComparator()); 580 } 581 } 582 583 /* ------------------------------------------------------------------------ */ 584 /* interface EditLayerChangeListener */ 585 /* ------------------------------------------------------------------------ */ 586 @Override 587 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 588 if (newLayer == null) { 589 setJOSMSelection(null); 590 history = null; 591 } else { 592 history = newLayer.data.getSelectionHistory(); 593 setJOSMSelection(newLayer.data.getAllSelected()); 594 } 595 } 596 597 /* ------------------------------------------------------------------------ */ 598 /* interface SelectionChangeListener */ 599 /* ------------------------------------------------------------------------ */ 600 @Override 601 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 602 setJOSMSelection(newSelection); 603 } 604 605 /* ------------------------------------------------------------------------ */ 606 /* interface DataSetListener */ 607 /* ------------------------------------------------------------------------ */ 608 @Override 609 public void dataChanged(DataChangedEvent event) { 610 // refresh the whole list 611 fireContentsChanged(this, 0, getSize()); 612 } 613 614 @Override 615 public void nodeMoved(NodeMovedEvent event) { 616 // may influence the display name of primitives, update the data 617 update(event.getPrimitives()); 618 } 619 620 @Override 621 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 622 // may influence the display name of primitives, update the data 623 update(event.getPrimitives()); 624 } 625 626 @Override 627 public void relationMembersChanged(RelationMembersChangedEvent event) { 628 // may influence the display name of primitives, update the data 629 update(event.getPrimitives()); 630 } 631 632 @Override 633 public void tagsChanged(TagsChangedEvent event) { 634 // may influence the display name of primitives, update the data 635 update(event.getPrimitives()); 636 } 637 638 @Override 639 public void wayNodesChanged(WayNodesChangedEvent event) { 640 // may influence the display name of primitives, update the data 641 update(event.getPrimitives()); 642 } 643 644 @Override 645 public void primitivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */} 646 @Override 647 public void primitivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/} 648 } 649 650 /** 651 * A specialized {@link JMenuItem} for presenting one entry of the search history 652 * 653 * @author Jan Peter Stotz 654 */ 655 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 656 final protected SearchSetting s; 657 658 public SearchMenuItem(SearchSetting s) { 659 super(s.toString()); 660 this.s = s; 661 addActionListener(this); 662 } 663 664 @Override 665 public void actionPerformed(ActionEvent e) { 666 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 667 } 668 } 669 670 /** 671 * The popup menu for the search history entries 672 * 673 */ 674 protected static class SearchPopupMenu extends JPopupMenu { 675 static public void launch(Component parent) { 676 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 677 return; 678 JPopupMenu menu = new SearchPopupMenu(); 679 Rectangle r = parent.getBounds(); 680 menu.show(parent, r.x, r.y + r.height); 681 } 682 683 public SearchPopupMenu() { 684 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 685 add(new SearchMenuItem(ss)); 686 } 687 } 688 } 689 690 /** 691 * A specialized {@link JMenuItem} for presenting one entry of the selection history 692 * 693 * @author Jan Peter Stotz 694 */ 695 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 696 final private DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 697 protected Collection<? extends OsmPrimitive> sel; 698 699 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 700 super(); 701 this.sel = sel; 702 int ways = 0; 703 int nodes = 0; 704 int relations = 0; 705 for (OsmPrimitive o : sel) { 706 if (! o.isSelectable()) continue; // skip unselectable primitives 707 if (o instanceof Way) { 708 ways++; 709 } else if (o instanceof Node) { 710 nodes++; 711 } else if (o instanceof Relation) { 712 relations++; 713 } 714 } 715 StringBuffer text = new StringBuffer(); 716 if(ways != 0) { 717 text.append(text.length() > 0 ? ", " : "") 718 .append(trn("{0} way", "{0} ways", ways, ways)); 719 } 720 if(nodes != 0) { 721 text.append(text.length() > 0 ? ", " : "") 722 .append(trn("{0} node", "{0} nodes", nodes, nodes)); 723 } 724 if(relations != 0) { 725 text.append(text.length() > 0 ? ", " : "") 726 .append(trn("{0} relation", "{0} relations", relations, relations)); 727 } 728 if(ways + nodes + relations == 0) { 729 text.append(tr("Unselectable now")); 730 this.sel=new ArrayList<OsmPrimitive>(); // empty selection 731 } 732 if(ways + nodes + relations == 1) 733 { 734 text.append(": "); 735 for(OsmPrimitive o : sel) { 736 text.append(o.getDisplayName(df)); 737 } 738 setText(text.toString()); 739 } else { 740 setText(tr("Selection: {0}", text)); 741 } 742 addActionListener(this); 743 } 744 745 @Override 746 public void actionPerformed(ActionEvent e) { 747 Main.main.getCurrentDataSet().setSelected(sel); 748 } 749 } 750 751 /** 752 * The popup menu for the JOSM selection history entries 753 */ 754 protected static class SelectionHistoryPopup extends JPopupMenu { 755 static public void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) { 756 if (history == null || history.isEmpty()) return; 757 JPopupMenu menu = new SelectionHistoryPopup(history); 758 Rectangle r = parent.getBounds(); 759 menu.show(parent, r.x, r.y + r.height); 760 } 761 762 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) { 763 for (Collection<? extends OsmPrimitive> sel : history) { 764 add(new SelectionMenuItem(sel)); 765 } 766 } 767 } 768 769 /** Quicker comparator, comparing just by type and ID's */ 770 static private class OsmPrimitiveQuickComparator implements Comparator<OsmPrimitive> { 771 772 private int compareId(OsmPrimitive a, OsmPrimitive b) { 773 long id_a=a.getUniqueId(); 774 long id_b=b.getUniqueId(); 775 if (id_a<id_b) return -1; 776 if (id_a>id_b) return 1; 777 return 0; 778 } 779 780 private int compareType(OsmPrimitive a, OsmPrimitive b) { 781 // show ways before relations, then nodes 782 if (a.getType().equals(OsmPrimitiveType.WAY)) return -1; 783 if (a.getType().equals(OsmPrimitiveType.NODE)) return 1; 784 // a is a relation 785 if (b.getType().equals(OsmPrimitiveType.WAY)) return 1; 786 // b is a node 787 return -1; 788 } 789 790 @Override 791 public int compare(OsmPrimitive a, OsmPrimitive b) { 792 if (a.getType().equals(b.getType())) 793 return compareId(a, b); 794 return compareType(a, b); 795 } 796 } 797 798}