001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.gui.dialogs; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 import static org.openstreetmap.josm.tools.I18n.trn; 006 007 import java.awt.Component; 008 import java.awt.Rectangle; 009 import java.awt.event.ActionEvent; 010 import java.awt.event.ActionListener; 011 import java.awt.event.KeyEvent; 012 import java.awt.event.MouseAdapter; 013 import java.awt.event.MouseEvent; 014 import java.util.ArrayList; 015 import java.util.Arrays; 016 import java.util.Collection; 017 import java.util.Collections; 018 import java.util.Comparator; 019 import java.util.HashSet; 020 import java.util.LinkedList; 021 import java.util.List; 022 import java.util.Set; 023 024 import javax.swing.AbstractAction; 025 import javax.swing.AbstractListModel; 026 import javax.swing.Action; 027 import javax.swing.DefaultListSelectionModel; 028 import javax.swing.JList; 029 import javax.swing.JMenuItem; 030 import javax.swing.JPopupMenu; 031 import javax.swing.ListSelectionModel; 032 import javax.swing.SwingUtilities; 033 import javax.swing.event.ListDataEvent; 034 import javax.swing.event.ListDataListener; 035 import javax.swing.event.ListSelectionEvent; 036 import javax.swing.event.ListSelectionListener; 037 import javax.swing.event.PopupMenuListener; 038 039 import org.openstreetmap.josm.Main; 040 import org.openstreetmap.josm.actions.AutoScaleAction; 041 import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting; 042 import org.openstreetmap.josm.data.SelectionChangedListener; 043 import org.openstreetmap.josm.data.osm.Node; 044 import org.openstreetmap.josm.data.osm.OsmPrimitive; 045 import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 046 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 047 import org.openstreetmap.josm.data.osm.Relation; 048 import org.openstreetmap.josm.data.osm.RelationMember; 049 import org.openstreetmap.josm.data.osm.Way; 050 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 051 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 052 import org.openstreetmap.josm.data.osm.event.DataSetListener; 053 import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 054 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 055 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 056 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 057 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 058 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 059 import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 060 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 061 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 062 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 063 import org.openstreetmap.josm.gui.DefaultNameFormatter; 064 import org.openstreetmap.josm.gui.MapView; 065 import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener; 066 import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 067 import org.openstreetmap.josm.gui.SideButton; 068 import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask; 069 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 070 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 071 import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 072 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 073 import org.openstreetmap.josm.tools.ImageProvider; 074 import org.openstreetmap.josm.tools.InputMapUtils; 075 import org.openstreetmap.josm.tools.Shortcut; 076 077 /** 078 * A small tool dialog for displaying the current selection. 079 * 080 */ 081 public class SelectionListDialog extends ToggleDialog { 082 private JList lstPrimitives; 083 private SelectionListModel model; 084 085 private SelectAction actSelect; 086 private SearchAction actSearch; 087 private ZoomToJOSMSelectionAction actZoomToJOSMSelection; 088 private ZoomToListSelection actZoomToListSelection; 089 private SetRelationSelection actSetRelationSelection; 090 private EditRelationSelection actEditRelationSelection; 091 private DownloadSelectedIncompleteMembersAction actDownloadSelectedIncompleteMembers; 092 093 private SelectionPopup popupMenu; 094 095 /** 096 * Builds the content panel for this dialog 097 */ 098 protected void buildContentPanel() { 099 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 100 model = new SelectionListModel(selectionModel); 101 lstPrimitives = new JList(model); 102 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 103 lstPrimitives.setSelectionModel(selectionModel); 104 lstPrimitives.setCellRenderer(new OsmPrimitivRenderer()); 105 lstPrimitives.setTransferHandler(null); // Fix #6290. Drag & Drop is not supported anyway and Copy/Paste is better propagated to main window 106 107 // the select action 108 final SideButton selectButton = new SideButton(actSelect = new SelectAction()); 109 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect); 110 selectButton.createArrow(new ActionListener() { 111 public void actionPerformed(ActionEvent e) { 112 SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory()); 113 } 114 }); 115 116 // the search button 117 final SideButton searchButton = new SideButton(actSearch = new SearchAction()); 118 searchButton.createArrow(new ActionListener() { 119 public void actionPerformed(ActionEvent e) { 120 SearchPopupMenu.launch(searchButton); 121 } 122 }); 123 124 createLayout(lstPrimitives, true, Arrays.asList(new SideButton[] { 125 selectButton, searchButton 126 })); 127 } 128 129 public SelectionListDialog() { 130 super(tr("Selection"), "selectionlist", tr("Open a selection list window."), 131 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", 132 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT), 133 150, // default height 134 true // default is "show dialog" 135 ); 136 137 buildContentPanel(); 138 model.addListDataListener(new TitleUpdater()); 139 actZoomToJOSMSelection = new ZoomToJOSMSelectionAction(); 140 model.addListDataListener(actZoomToJOSMSelection); 141 142 actZoomToListSelection = new ZoomToListSelection(); 143 actSetRelationSelection = new SetRelationSelection(); 144 actEditRelationSelection = new EditRelationSelection(); 145 actDownloadSelectedIncompleteMembers = new DownloadSelectedIncompleteMembersAction(); 146 147 lstPrimitives.addMouseListener(new SelectionPopupMenuLauncher()); 148 lstPrimitives.addMouseListener(new DblClickHandler()); 149 150 popupMenu = new SelectionPopup(lstPrimitives); 151 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection); 152 } 153 154 @Override 155 public void showNotify() { 156 MapView.addEditLayerChangeListener(model); 157 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED); 158 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT); 159 MapView.addEditLayerChangeListener(actSearch); 160 // editLayerChanged also gets the selection history of the level 161 model.editLayerChanged(null, Main.map.mapView.getEditLayer()); 162 if (Main.map.mapView.getEditLayer() != null) { 163 model.setJOSMSelection(Main.map.mapView.getEditLayer().data.getAllSelected()); 164 } 165 actSearch.updateEnabledState(); 166 } 167 168 @Override 169 public void hideNotify() { 170 MapView.removeEditLayerChangeListener(actSearch); 171 MapView.removeEditLayerChangeListener(model); 172 SelectionEventManager.getInstance().removeSelectionListener(model); 173 DatasetEventManager.getInstance().removeDatasetListener(model); 174 } 175 176 /** 177 * Responds to double clicks on the list of selected objects 178 */ 179 class DblClickHandler extends MouseAdapter { 180 @Override 181 public void mouseClicked(MouseEvent e) { 182 if (e.getClickCount() < 2 || ! SwingUtilities.isLeftMouseButton(e)) return; 183 int idx = lstPrimitives.locationToIndex(e.getPoint()); 184 if (idx < 0) return; 185 OsmDataLayer layer = Main.main.getEditLayer(); 186 if(layer == null) return; 187 layer.data.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx))); 188 } 189 } 190 191 /** 192 * The popup menu launcher 193 */ 194 class SelectionPopupMenuLauncher extends PopupMenuLauncher { 195 196 @Override 197 public void launch(MouseEvent evt) { 198 if (model.getSelected().isEmpty()) { 199 int idx = lstPrimitives.locationToIndex(evt.getPoint()); 200 if (idx < 0) return; 201 model.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx))); 202 } 203 popupMenu.show(lstPrimitives, evt.getX(), evt.getY()); 204 } 205 } 206 207 /** 208 * The popup menu for the selection list 209 */ 210 class SelectionPopup extends ListPopupMenu { 211 public SelectionPopup(JList list) { 212 super(list); 213 add(actZoomToJOSMSelection); 214 add(actZoomToListSelection); 215 addSeparator(); 216 add(actSetRelationSelection); 217 add(actEditRelationSelection); 218 addSeparator(); 219 add(actDownloadSelectedIncompleteMembers); 220 } 221 } 222 223 public void addPopupMenuSeparator() { 224 popupMenu.addSeparator(); 225 } 226 227 public JMenuItem addPopupMenuAction(Action a) { 228 return popupMenu.add(a); 229 } 230 231 public void addPopupMenuListener(PopupMenuListener l) { 232 popupMenu.addPopupMenuListener(l); 233 } 234 235 public void removePopupMenuListener(PopupMenuListener l) { 236 popupMenu.addPopupMenuListener(l); 237 } 238 239 public Collection<OsmPrimitive> getSelectedPrimitives() { 240 return model.getSelected(); 241 } 242 243 /** 244 * Updates the dialog title with a summary of the current JOSM selection 245 */ 246 class TitleUpdater implements ListDataListener { 247 protected void updateTitle() { 248 setTitle(model.getJOSMSelectionSummary()); 249 } 250 251 public void contentsChanged(ListDataEvent e) { 252 updateTitle(); 253 } 254 255 public void intervalAdded(ListDataEvent e) { 256 updateTitle(); 257 } 258 259 public void intervalRemoved(ListDataEvent e) { 260 updateTitle(); 261 } 262 } 263 264 /** 265 * Launches the search dialog 266 */ 267 static class SearchAction extends AbstractAction implements EditLayerChangeListener { 268 public SearchAction() { 269 putValue(NAME, tr("Search")); 270 putValue(SHORT_DESCRIPTION, tr("Search for objects")); 271 putValue(SMALL_ICON, ImageProvider.get("dialogs","select")); 272 updateEnabledState(); 273 } 274 275 public void actionPerformed(ActionEvent e) { 276 if (!isEnabled()) return; 277 org.openstreetmap.josm.actions.search.SearchAction.search(); 278 } 279 280 public void updateEnabledState() { 281 setEnabled(Main.main != null && Main.main.getEditLayer() != null); 282 } 283 284 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 285 updateEnabledState(); 286 } 287 } 288 289 /** 290 * Sets the current JOSM selection to the OSM primitives selected in the list 291 * of this dialog 292 */ 293 class SelectAction extends AbstractAction implements ListSelectionListener { 294 public SelectAction() { 295 putValue(NAME, tr("Select")); 296 putValue(SHORT_DESCRIPTION, tr("Set the selected elements on the map to the selected items in the list above.")); 297 putValue(SMALL_ICON, ImageProvider.get("dialogs","select")); 298 updateEnabledState(); 299 } 300 301 public void actionPerformed(ActionEvent e) { 302 Collection<OsmPrimitive> sel = model.getSelected(); 303 if (sel.isEmpty())return; 304 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return; 305 Main.map.mapView.getEditLayer().data.setSelected(sel); 306 } 307 308 public void updateEnabledState() { 309 setEnabled(!model.getSelected().isEmpty()); 310 } 311 312 public void valueChanged(ListSelectionEvent e) { 313 updateEnabledState(); 314 } 315 } 316 317 /** 318 * The action for zooming to the primitives in the current JOSM selection 319 * 320 */ 321 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener { 322 323 public ZoomToJOSMSelectionAction() { 324 putValue(NAME,tr("Zoom to selection")); 325 putValue(SHORT_DESCRIPTION, tr("Zoom to selection")); 326 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 327 updateEnabledState(); 328 } 329 330 public void actionPerformed(ActionEvent e) { 331 AutoScaleAction.autoScale("selection"); 332 } 333 334 public void updateEnabledState() { 335 setEnabled(model.getSize() > 0); 336 } 337 338 public void contentsChanged(ListDataEvent e) { 339 updateEnabledState(); 340 } 341 342 public void intervalAdded(ListDataEvent e) { 343 updateEnabledState(); 344 } 345 346 public void intervalRemoved(ListDataEvent e) { 347 updateEnabledState(); 348 } 349 } 350 351 /** 352 * The action for zooming to the primitives which are currently selected in 353 * the list displaying the JOSM selection 354 * 355 */ 356 class ZoomToListSelection extends AbstractAction implements ListSelectionListener{ 357 public ZoomToListSelection() { 358 putValue(NAME, tr("Zoom to selected element(s)")); 359 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)")); 360 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 361 updateEnabledState(); 362 } 363 364 public void actionPerformed(ActionEvent e) { 365 BoundingXYVisitor box = new BoundingXYVisitor(); 366 Collection<OsmPrimitive> sel = model.getSelected(); 367 if (sel.isEmpty()) return; 368 box.computeBoundingBox(sel); 369 if (box.getBounds() == null) 370 return; 371 box.enlargeBoundingBox(); 372 Main.map.mapView.recalculateCenterScale(box); 373 } 374 375 public void updateEnabledState() { 376 setEnabled(!model.getSelected().isEmpty()); 377 } 378 379 public void valueChanged(ListSelectionEvent e) { 380 updateEnabledState(); 381 } 382 } 383 384 /** 385 * The action for setting and editing a relation in relation list dialog 386 * 387 */ 388 class EditRelationSelection extends SetRelationSelection { 389 public EditRelationSelection() { 390 putValue(NAME, tr("Call editor for relation")); 391 putValue(SHORT_DESCRIPTION, tr("Call relation editor for selected relation")); 392 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit")); 393 updateEnabledState(); 394 } 395 396 @Override 397 public void actionPerformed(ActionEvent e) { 398 Relation relation = (Relation)model.getSelected().toArray()[0]; 399 Collection<RelationMember> members = new HashSet<RelationMember>(); 400 Collection<OsmPrimitive> selection = model.getAllElements(); 401 for (RelationMember member: relation.getMembers()) { 402 if (selection.contains(member.getMember())) { 403 members.add(member); 404 } 405 } 406 Main.map.relationListDialog.selectRelation(relation); 407 RelationEditor.getEditor(Main.map.mapView.getEditLayer(), relation, 408 members).setVisible(true); 409 } 410 } 411 412 /** 413 * The action for setting a relation in relation list dialog 414 * 415 */ 416 class SetRelationSelection extends AbstractAction implements ListSelectionListener{ 417 public SetRelationSelection() { 418 putValue(NAME, tr("Select in relation list")); 419 putValue(SHORT_DESCRIPTION, tr("Select relation in relation list.")); 420 putValue(SMALL_ICON, ImageProvider.get("dialogs", "selectionlist")); 421 updateEnabledState(); 422 } 423 424 public void actionPerformed(ActionEvent e) { 425 Relation relation = (Relation)model.getSelected().toArray()[0]; 426 Main.map.relationListDialog.selectRelation(relation); 427 } 428 429 public void updateEnabledState() { 430 Object[] sel = model.getSelected().toArray(); 431 setEnabled(sel.length == 1 && sel[0] instanceof Relation); 432 } 433 434 public void valueChanged(ListSelectionEvent e) { 435 updateEnabledState(); 436 } 437 } 438 439 /** 440 * The list model for the list of OSM primitives in the current JOSM selection. 441 * 442 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE} 443 * JOSM selection. 444 * 445 */ 446 static private class SelectionListModel extends AbstractListModel implements EditLayerChangeListener, SelectionChangedListener, DataSetListener{ 447 448 private static final int SELECTION_HISTORY_SIZE = 10; 449 450 // Variable to store history from currentDataSet() 451 private LinkedList<Collection<? extends OsmPrimitive>> history; 452 private final List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(); 453 private DefaultListSelectionModel selectionModel; 454 455 /** 456 * Constructor 457 * @param selectionModel the selection model used in the list 458 */ 459 public SelectionListModel(DefaultListSelectionModel selectionModel) { 460 this.selectionModel = selectionModel; 461 } 462 463 /** 464 * Replies a summary of the current JOSM selection 465 * 466 * @return a summary of the current JOSM selection 467 */ 468 public String getJOSMSelectionSummary() { 469 if (selection.isEmpty()) return tr("Selection"); 470 int numNodes = 0; 471 int numWays = 0; 472 int numRelations = 0; 473 for (OsmPrimitive p: selection) { 474 switch(p.getType()) { 475 case NODE: numNodes++; break; 476 case WAY: numWays++; break; 477 case RELATION: numRelations++; break; 478 } 479 } 480 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes); 481 } 482 483 /** 484 * Remembers a JOSM selection the history of JOSM selections 485 * 486 * @param selection the JOSM selection. Ignored if null or empty. 487 */ 488 public void remember(Collection<? extends OsmPrimitive> selection) { 489 if (selection == null)return; 490 if (selection.isEmpty())return; 491 if (history == null) return; 492 if (history.isEmpty()) { 493 history.add(selection); 494 return; 495 } 496 if (history.getFirst().equals(selection)) return; 497 history.addFirst(selection); 498 for(int i = 1; i < history.size(); ++i) { 499 if(history.get(i).equals(selection)) { 500 history.remove(i); 501 break; 502 } 503 } 504 int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE); 505 while (history.size() > maxsize) { 506 history.removeLast(); 507 } 508 } 509 510 /** 511 * Replies the history of JOSM selections 512 * 513 * @return 514 */ 515 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() { 516 return history; 517 } 518 519 public Object getElementAt(int index) { 520 return selection.get(index); 521 } 522 523 public int getSize() { 524 return selection.size(); 525 } 526 527 /** 528 * Replies the collection of OSM primitives currently selected in the view 529 * of this model 530 * 531 * @return 532 */ 533 public Collection<OsmPrimitive> getSelected() { 534 Set<OsmPrimitive> sel = new HashSet<OsmPrimitive>(); 535 for(int i=0; i< getSize();i++) { 536 if (selectionModel.isSelectedIndex(i)) { 537 sel.add(selection.get(i)); 538 } 539 } 540 return sel; 541 } 542 543 /** 544 * Replies the collection of OSM primitives in the view 545 * of this model 546 * 547 * @return 548 */ 549 public Collection<OsmPrimitive> getAllElements() { 550 return selection; 551 } 552 553 /** 554 * Sets the OSM primitives to be selected in the view of this model 555 * 556 * @param sel the collection of primitives to select 557 */ 558 public void setSelected(Collection<OsmPrimitive> sel) { 559 selectionModel.clearSelection(); 560 if (sel == null) return; 561 for (OsmPrimitive p: sel){ 562 int i = selection.indexOf(p); 563 if (i >= 0){ 564 selectionModel.addSelectionInterval(i, i); 565 } 566 } 567 } 568 569 @Override 570 protected void fireContentsChanged(Object source, int index0, int index1) { 571 Collection<OsmPrimitive> sel = getSelected(); 572 super.fireContentsChanged(source, index0, index1); 573 setSelected(sel); 574 } 575 576 /** 577 * Sets the collection of currently selected OSM objects 578 * 579 * @param selection the collection of currently selected OSM objects 580 */ 581 public void setJOSMSelection(Collection<? extends OsmPrimitive> selection) { 582 this.selection.clear(); 583 if (selection == null) { 584 fireContentsChanged(this, 0, getSize()); 585 return; 586 } 587 this.selection.addAll(selection); 588 sort(); 589 fireContentsChanged(this, 0, getSize()); 590 remember(selection); 591 double dist = -1; 592 if(this.selection.size() == 1) { 593 OsmPrimitive o = this.selection.get(0); 594 if(o instanceof Way) 595 dist = ((Way)o).getLength(); 596 } 597 Main.map.statusLine.setDist(dist); 598 } 599 600 /** 601 * Triggers a refresh of the view for all primitives in {@code toUpdate} 602 * which are currently displayed in the view 603 * 604 * @param toUpdate the collection of primitives to update 605 */ 606 public void update(Collection<? extends OsmPrimitive> toUpdate) { 607 if (toUpdate == null) return; 608 if (toUpdate.isEmpty()) return; 609 Collection<OsmPrimitive> sel = getSelected(); 610 for (OsmPrimitive p: toUpdate){ 611 int i = selection.indexOf(p); 612 if (i >= 0) { 613 super.fireContentsChanged(this, i,i); 614 } 615 } 616 setSelected(sel); 617 } 618 619 /** 620 * Replies the list of selected relations with incomplete members 621 * 622 * @return the list of selected relations with incomplete members 623 */ 624 public List<Relation> getSelectedRelationsWithIncompleteMembers() { 625 List<Relation> ret = new LinkedList<Relation>(); 626 for(int i=0; i<getSize(); i++) { 627 if (!selectionModel.isSelectedIndex(i)) { 628 continue; 629 } 630 OsmPrimitive p = selection.get(i); 631 if (! (p instanceof Relation)) { 632 continue; 633 } 634 if (p.isNew()) { 635 continue; 636 } 637 Relation r = (Relation)p; 638 if (r.hasIncompleteMembers()) { 639 ret.add(r); 640 } 641 } 642 return ret; 643 } 644 645 /** 646 * Sorts the current elements in the selection 647 */ 648 public void sort() { 649 if (this.selection.size()>Main.pref.getInteger("selection.no_sort_above",100000)) return; 650 if (this.selection.size()>Main.pref.getInteger("selection.fast_sort_above",10000)) { 651 Collections.sort(this.selection, new OsmPrimitiveQuickComparator()); 652 } else { 653 Collections.sort(this.selection, new OsmPrimitiveComparator()); 654 } 655 } 656 657 /* ------------------------------------------------------------------------ */ 658 /* interface EditLayerChangeListener */ 659 /* ------------------------------------------------------------------------ */ 660 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 661 if (newLayer == null) { 662 setJOSMSelection(null); 663 history = null; 664 } else { 665 history = newLayer.data.getSelectionHistory(); 666 setJOSMSelection(newLayer.data.getAllSelected()); 667 } 668 } 669 670 /* ------------------------------------------------------------------------ */ 671 /* interface SelectionChangeListener */ 672 /* ------------------------------------------------------------------------ */ 673 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 674 setJOSMSelection(newSelection); 675 } 676 677 /* ------------------------------------------------------------------------ */ 678 /* interface DataSetListener */ 679 /* ------------------------------------------------------------------------ */ 680 public void dataChanged(DataChangedEvent event) { 681 // refresh the whole list 682 fireContentsChanged(this, 0, getSize()); 683 } 684 685 public void nodeMoved(NodeMovedEvent event) { 686 // may influence the display name of primitives, update the data 687 update(event.getPrimitives()); 688 } 689 690 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 691 // may influence the display name of primitives, update the data 692 update(event.getPrimitives()); 693 } 694 695 public void relationMembersChanged(RelationMembersChangedEvent event) { 696 // may influence the display name of primitives, update the data 697 update(event.getPrimitives()); 698 } 699 700 public void tagsChanged(TagsChangedEvent event) { 701 // may influence the display name of primitives, update the data 702 update(event.getPrimitives()); 703 } 704 705 public void wayNodesChanged(WayNodesChangedEvent event) { 706 // may influence the display name of primitives, update the data 707 update(event.getPrimitives()); 708 } 709 710 public void primitivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */} 711 public void primitivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/} 712 } 713 714 /** 715 * A specialized {@link JMenuItem} for presenting one entry of the search history 716 * 717 * @author Jan Peter Stotz 718 */ 719 protected static class SearchMenuItem extends JMenuItem implements ActionListener { 720 final protected SearchSetting s; 721 722 public SearchMenuItem(SearchSetting s) { 723 super(s.toString()); 724 this.s = s; 725 addActionListener(this); 726 } 727 728 public void actionPerformed(ActionEvent e) { 729 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s); 730 } 731 } 732 733 /** 734 * The popup menu for the search history entries 735 * 736 */ 737 protected static class SearchPopupMenu extends JPopupMenu { 738 static public void launch(Component parent) { 739 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty()) 740 return; 741 JPopupMenu menu = new SearchPopupMenu(); 742 Rectangle r = parent.getBounds(); 743 menu.show(parent, r.x, r.y + r.height); 744 } 745 746 public SearchPopupMenu() { 747 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) { 748 add(new SearchMenuItem(ss)); 749 } 750 } 751 } 752 753 /** 754 * A specialized {@link JMenuItem} for presenting one entry of the selection history 755 * 756 * @author Jan Peter Stotz 757 */ 758 protected static class SelectionMenuItem extends JMenuItem implements ActionListener { 759 final private DefaultNameFormatter df = DefaultNameFormatter.getInstance(); 760 protected Collection<? extends OsmPrimitive> sel; 761 762 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) { 763 super(); 764 this.sel = sel; 765 int ways = 0; 766 int nodes = 0; 767 int relations = 0; 768 for (OsmPrimitive o : sel) { 769 if (! o.isSelectable()) continue; // skip unselectable primitives 770 if (o instanceof Way) { 771 ways++; 772 } else if (o instanceof Node) { 773 nodes++; 774 } else if (o instanceof Relation) { 775 relations++; 776 } 777 } 778 StringBuffer text = new StringBuffer(); 779 if(ways != 0) { 780 text.append(text.length() > 0 ? ", " : "") 781 .append(trn("{0} way", "{0} ways", ways, ways)); 782 } 783 if(nodes != 0) { 784 text.append(text.length() > 0 ? ", " : "") 785 .append(trn("{0} node", "{0} nodes", nodes, nodes)); 786 } 787 if(relations != 0) { 788 text.append(text.length() > 0 ? ", " : "") 789 .append(trn("{0} relation", "{0} relations", relations, relations)); 790 } 791 if(ways + nodes + relations == 0) { 792 text.append(tr("Unselectable now")); 793 this.sel=new ArrayList<OsmPrimitive>(); // empty selection 794 } 795 if(ways + nodes + relations == 1) 796 { 797 text.append(": "); 798 for(OsmPrimitive o : sel) { 799 text.append(o.getDisplayName(df)); 800 } 801 setText(text.toString()); 802 } else { 803 setText(tr("Selection: {0}", text)); 804 } 805 addActionListener(this); 806 } 807 808 public void actionPerformed(ActionEvent e) { 809 Main.main.getCurrentDataSet().setSelected(sel); 810 } 811 } 812 813 /** 814 * The popup menue for the JOSM selection history entries 815 * 816 */ 817 protected static class SelectionHistoryPopup extends JPopupMenu { 818 static public void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) { 819 if (history == null || history.isEmpty()) return; 820 JPopupMenu menu = new SelectionHistoryPopup(history); 821 Rectangle r = parent.getBounds(); 822 menu.show(parent, r.x, r.y + r.height); 823 } 824 825 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) { 826 for (Collection<? extends OsmPrimitive> sel : history) { 827 add(new SelectionMenuItem(sel)); 828 } 829 } 830 } 831 832 /** 833 * Action for downloading incomplete members of selected relations 834 * 835 */ 836 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener { 837 public DownloadSelectedIncompleteMembersAction() { 838 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations")); 839 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected")); 840 putValue(NAME, tr("Download incomplete members")); 841 updateEnabledState(); 842 } 843 844 public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) { 845 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 846 for(Relation r: rels) { 847 ret.addAll(r.getIncompleteMembers()); 848 } 849 return ret; 850 } 851 852 public void actionPerformed(ActionEvent e) { 853 if (!isEnabled()) 854 return; 855 List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers(); 856 if (rels.isEmpty()) return; 857 Main.worker.submit(new DownloadRelationMemberTask( 858 rels, 859 buildSetOfIncompleteMembers(rels), 860 Main.map.mapView.getEditLayer() 861 )); 862 } 863 864 protected void updateEnabledState() { 865 setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty()); 866 } 867 868 public void valueChanged(ListSelectionEvent e) { 869 updateEnabledState(); 870 } 871 } 872 873 /** Quicker comparator, comparing just by type and ID's */ 874 static private class OsmPrimitiveQuickComparator implements Comparator<OsmPrimitive> { 875 876 private int compareId(OsmPrimitive a, OsmPrimitive b) { 877 long id_a=a.getUniqueId(); 878 long id_b=b.getUniqueId(); 879 if (id_a<id_b) return -1; 880 if (id_a>id_b) return 1; 881 return 0; 882 } 883 884 private int compareType(OsmPrimitive a, OsmPrimitive b) { 885 // show ways before relations, then nodes 886 if (a.getType().equals(OsmPrimitiveType.WAY)) return -1; 887 if (a.getType().equals(OsmPrimitiveType.NODE)) return 1; 888 // a is a relation 889 if (b.getType().equals(OsmPrimitiveType.WAY)) return 1; 890 // b is a node 891 return -1; 892 } 893 894 public int compare(OsmPrimitive a, OsmPrimitive b) { 895 if (a.getType().equals(b.getType())) 896 return compareId(a, b); 897 return compareType(a, b); 898 } 899 } 900 901 }