001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.dialogs; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 import static org.openstreetmap.josm.tools.I18n.trn; 007 008 import java.awt.Point; 009 import java.awt.event.ActionEvent; 010 import java.awt.event.KeyEvent; 011 import java.awt.event.MouseAdapter; 012 import java.awt.event.MouseEvent; 013 import java.util.ArrayList; 014 import java.util.Arrays; 015 import java.util.Collection; 016 import java.util.Collections; 017 import java.util.HashSet; 018 import java.util.Iterator; 019 import java.util.LinkedList; 020 import java.util.List; 021 import java.util.Set; 022 023 import javax.swing.AbstractAction; 024 import javax.swing.AbstractListModel; 025 import javax.swing.Action; 026 import javax.swing.DefaultListSelectionModel; 027 import javax.swing.JComponent; 028 import javax.swing.JList; 029 import javax.swing.JMenuItem; 030 import javax.swing.KeyStroke; 031 import javax.swing.ListSelectionModel; 032 import javax.swing.SwingUtilities; 033 import javax.swing.event.ListSelectionEvent; 034 import javax.swing.event.ListSelectionListener; 035 import javax.swing.event.PopupMenuListener; 036 037 import org.openstreetmap.josm.Main; 038 import org.openstreetmap.josm.command.Command; 039 import org.openstreetmap.josm.command.SequenceCommand; 040 import org.openstreetmap.josm.data.SelectionChangedListener; 041 import org.openstreetmap.josm.data.osm.DataSet; 042 import org.openstreetmap.josm.data.osm.OsmPrimitive; 043 import org.openstreetmap.josm.data.osm.Relation; 044 import org.openstreetmap.josm.data.osm.RelationMember; 045 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 046 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 047 import org.openstreetmap.josm.data.osm.event.DataSetListener; 048 import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 049 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 050 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 051 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 052 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 053 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 054 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 055 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 056 import org.openstreetmap.josm.gui.DefaultNameFormatter; 057 import org.openstreetmap.josm.gui.MapView; 058 import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 059 import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 060 import org.openstreetmap.josm.gui.SideButton; 061 import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask; 062 import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationTask; 063 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor; 064 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 065 import org.openstreetmap.josm.gui.layer.Layer; 066 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 067 import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 068 import org.openstreetmap.josm.tools.ImageProvider; 069 import org.openstreetmap.josm.tools.InputMapUtils; 070 import org.openstreetmap.josm.tools.Shortcut; 071 072 /** 073 * A dialog showing all known relations, with buttons to add, edit, and 074 * delete them. 075 * 076 * We don't have such dialogs for nodes, segments, and ways, because those 077 * objects are visible on the map and can be selected there. Relations are not. 078 */ 079 public class RelationListDialog extends ToggleDialog implements DataSetListener { 080 /** The display list. */ 081 private JList displaylist; 082 /** the list model used */ 083 private RelationListModel model; 084 085 /** the edit action */ 086 private EditAction editAction; 087 /** the delete action */ 088 private DeleteAction deleteAction; 089 private NewAction newAction; 090 private AddToRelation addToRelation; 091 /** the popup menu */ 092 private RelationDialogPopupMenu popupMenu; 093 094 /** 095 * constructor 096 */ 097 public RelationListDialog() { 098 super(tr("Relations"), "relationlist", tr("Open a list of all relations."), 099 Shortcut.registerShortcut("subwindow:relations", tr("Toggle: {0}", tr("Relations")), 100 KeyEvent.VK_R, Shortcut.ALT_SHIFT), 150); 101 102 // create the list of relations 103 // 104 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 105 model = new RelationListModel(selectionModel); 106 displaylist = new JList(model); 107 displaylist.setSelectionModel(selectionModel); 108 displaylist.setCellRenderer(new OsmPrimitivRenderer() { 109 /** 110 * Don't show the default tooltip in the relation list. 111 */ 112 @Override 113 protected String getComponentToolTipText(OsmPrimitive value) { 114 return null; 115 } 116 }); 117 displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 118 displaylist.addMouseListener(new MouseEventHandler()); 119 120 // the new action 121 // 122 newAction = new NewAction(); 123 124 // the edit action 125 // 126 editAction = new EditAction(); 127 displaylist.addListSelectionListener(editAction); 128 129 // the duplicate action 130 // 131 DuplicateAction duplicateAction = new DuplicateAction(); 132 displaylist.addListSelectionListener(duplicateAction); 133 134 // the delete action 135 // 136 deleteAction = new DeleteAction(); 137 displaylist.addListSelectionListener(deleteAction); 138 139 // the select action 140 // 141 SelectAction selectAction = new SelectAction(false); 142 displaylist.addListSelectionListener(selectAction); 143 144 createLayout(displaylist, true, Arrays.asList(new SideButton[] { 145 new SideButton(newAction, false), 146 new SideButton(editAction, false), 147 new SideButton(duplicateAction, false), 148 new SideButton(deleteAction, false), 149 new SideButton(selectAction, false) 150 })); 151 152 // activate DEL in the list of relations 153 //displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "deleteRelation"); 154 //displaylist.getActionMap().put("deleteRelation", deleteAction); 155 156 InputMapUtils.unassignCtrlShiftUpDown(displaylist, JComponent.WHEN_FOCUSED); 157 158 // Select relation on Ctrl-Enter 159 InputMapUtils.addEnterAction(displaylist, selectAction); 160 161 addToRelation = new AddToRelation(); 162 popupMenu = new RelationDialogPopupMenu(displaylist); 163 164 // Edit relation on Ctrl-Enter 165 displaylist.getActionMap().put("edit", editAction); 166 displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK), "edit"); 167 } 168 169 @Override public void showNotify() { 170 MapView.addLayerChangeListener(newAction); 171 newAction.updateEnabledState(); 172 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT); 173 DataSet.addSelectionListener(addToRelation); 174 dataChanged(null); 175 } 176 177 @Override public void hideNotify() { 178 MapView.removeLayerChangeListener(newAction); 179 DatasetEventManager.getInstance().removeDatasetListener(this); 180 DataSet.removeSelectionListener(addToRelation); 181 } 182 183 /** 184 * Initializes the relation list dialog from a layer. If <code>layer</code> is null 185 * or if it isn't an {@link OsmDataLayer} the dialog is reset to an empty dialog. 186 * Otherwise it is initialized with the list of non-deleted and visible relations 187 * in the layer's dataset. 188 * 189 * @param layer the layer. May be null. 190 */ 191 protected void initFromLayer(Layer layer) { 192 if (layer == null || ! (layer instanceof OsmDataLayer)) { 193 model.setRelations(null); 194 return; 195 } 196 OsmDataLayer l = (OsmDataLayer)layer; 197 model.setRelations(l.data.getRelations()); 198 model.updateTitle(); 199 } 200 201 /** 202 * Adds a selection listener to the relation list. 203 * 204 * @param listener the listener to add 205 */ 206 public void addListSelectionListener(ListSelectionListener listener) { 207 displaylist.addListSelectionListener(listener); 208 } 209 210 /** 211 * Removes a selection listener from the relation list. 212 * 213 * @param listener the listener to remove 214 */ 215 public void removeListSelectionListener(ListSelectionListener listener) { 216 displaylist.removeListSelectionListener(listener); 217 } 218 219 /** 220 * @return The selected relation in the list 221 */ 222 private Relation getSelected() { 223 if(model.getSize() == 1) { 224 displaylist.setSelectedIndex(0); 225 } 226 return (Relation) displaylist.getSelectedValue(); 227 } 228 229 /** 230 * Selects the relation <code>relation</code> in the list of relations. 231 * 232 * @param relation the relation 233 */ 234 public void selectRelation(Relation relation) { 235 selectRelations(Collections.singleton(relation)); 236 } 237 238 /** 239 * Selects the relations in the list of relations. 240 * @param relations the relations to be selected 241 */ 242 public void selectRelations(Collection<Relation> relations) { 243 if (relations == null || relations.isEmpty()) { 244 model.setSelectedRelations(null); 245 } else { 246 model.setSelectedRelations(relations); 247 Integer i = model.getRelationIndex(relations.iterator().next()); 248 if (i != null) { // Not all relations have to be in the list (for example when the relation list is hidden, it's not updated with new relations) 249 displaylist.scrollRectToVisible(displaylist.getCellBounds(i, i)); 250 } 251 } 252 } 253 254 class MouseEventHandler extends MouseAdapter { 255 protected void setCurrentRelationAsSelection() { 256 Main.main.getCurrentDataSet().setSelected((Relation)displaylist.getSelectedValue()); 257 } 258 259 protected void editCurrentRelation() { 260 new EditAction().launchEditor(getSelected()); 261 } 262 263 @Override public void mouseClicked(MouseEvent e) { 264 if (Main.main.getEditLayer() == null) return; 265 if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) { 266 if (e.isControlDown()) { 267 editCurrentRelation(); 268 } else { 269 setCurrentRelationAsSelection(); 270 } 271 } 272 } 273 private void openPopup(MouseEvent e) { 274 Point p = e.getPoint(); 275 int index = displaylist.locationToIndex(p); 276 if (index < 0) return; 277 if (!displaylist.getCellBounds(index, index).contains(e.getPoint())) 278 return; 279 if (! displaylist.isSelectedIndex(index)) { 280 displaylist.setSelectedIndex(index); 281 } 282 popupMenu.show(displaylist, p.x, p.y-3); 283 } 284 @Override public void mousePressed(MouseEvent e) { 285 if (Main.main.getEditLayer() == null) return; 286 if (e.isPopupTrigger()) { 287 openPopup(e); 288 } 289 } 290 @Override public void mouseReleased(MouseEvent e) { 291 if (Main.main.getEditLayer() == null) return; 292 if (e.isPopupTrigger()) { 293 openPopup(e); 294 } 295 } 296 } 297 298 /** 299 * The edit action 300 * 301 */ 302 class EditAction extends AbstractAction implements ListSelectionListener{ 303 public EditAction() { 304 putValue(SHORT_DESCRIPTION,tr( "Open an editor for the selected relation")); 305 putValue(NAME, tr("Edit")); 306 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit")); 307 setEnabled(false); 308 } 309 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) { 310 Collection<RelationMember> members = new HashSet<RelationMember>(); 311 Collection<OsmPrimitive> selection = Main.map.mapView.getEditLayer().data.getSelected(); 312 for (RelationMember member: r.getMembers()) { 313 if (selection.contains(member.getMember())) { 314 members.add(member); 315 } 316 } 317 return members; 318 } 319 320 public void launchEditor(Relation toEdit) { 321 if (toEdit == null) 322 return; 323 RelationEditor.getEditor(Main.map.mapView.getEditLayer(),toEdit, getMembersForCurrentSelection(toEdit)).setVisible(true); 324 } 325 326 public void actionPerformed(ActionEvent e) { 327 if (!isEnabled()) 328 return; 329 launchEditor(getSelected()); 330 } 331 332 public void valueChanged(ListSelectionEvent e) { 333 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1); 334 } 335 } 336 337 /** 338 * The delete action 339 * 340 */ 341 class DeleteAction extends AbstractAction implements ListSelectionListener { 342 class AbortException extends Exception {} 343 344 public DeleteAction() { 345 putValue(SHORT_DESCRIPTION,tr("Delete the selected relation")); 346 putValue(NAME, tr("Delete")); 347 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 348 setEnabled(false); 349 } 350 351 protected void deleteRelation(Relation toDelete) { 352 if (toDelete == null) 353 return; 354 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation( 355 Main.main.getEditLayer(), 356 toDelete 357 ); 358 } 359 360 public void actionPerformed(ActionEvent e) { 361 if (!isEnabled()) 362 return; 363 List<Relation> toDelete = new LinkedList<Relation>(); 364 for (int i : displaylist.getSelectedIndices()) { 365 toDelete.add(model.getRelation(i)); 366 } 367 for (Relation r : toDelete) { 368 deleteRelation(r); 369 } 370 displaylist.clearSelection(); 371 } 372 373 public void valueChanged(ListSelectionEvent e) { 374 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0); 375 } 376 } 377 378 /** 379 * The action for creating a new relation 380 * 381 */ 382 static class NewAction extends AbstractAction implements LayerChangeListener{ 383 public NewAction() { 384 putValue(SHORT_DESCRIPTION,tr("Create a new relation")); 385 putValue(NAME, tr("New")); 386 putValue(SMALL_ICON, ImageProvider.get("dialogs", "addrelation")); 387 updateEnabledState(); 388 } 389 390 public void run() { 391 RelationEditor.getEditor(Main.main.getEditLayer(),null, null).setVisible(true); 392 } 393 394 public void actionPerformed(ActionEvent e) { 395 run(); 396 } 397 398 protected void updateEnabledState() { 399 setEnabled(Main.main != null && Main.main.getEditLayer() != null); 400 } 401 402 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 403 updateEnabledState(); 404 } 405 406 public void layerAdded(Layer newLayer) { 407 updateEnabledState(); 408 } 409 410 public void layerRemoved(Layer oldLayer) { 411 updateEnabledState(); 412 } 413 } 414 415 /** 416 * Creates a new relation with a copy of the current editor state 417 * 418 */ 419 class DuplicateAction extends AbstractAction implements ListSelectionListener { 420 public DuplicateAction() { 421 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window")); 422 putValue(SMALL_ICON, ImageProvider.get("duplicate")); 423 putValue(NAME, tr("Duplicate")); 424 updateEnabledState(); 425 } 426 427 public void launchEditorForDuplicate(Relation original) { 428 Relation copy = new Relation(original, true); 429 copy.setModified(true); 430 RelationEditor editor = RelationEditor.getEditor( 431 Main.main.getEditLayer(), 432 copy, 433 null /* no selected members */ 434 ); 435 editor.setVisible(true); 436 } 437 438 public void actionPerformed(ActionEvent e) { 439 if (!isEnabled()) 440 return; 441 launchEditorForDuplicate(getSelected()); 442 } 443 444 protected void updateEnabledState() { 445 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1); 446 } 447 448 public void valueChanged(ListSelectionEvent e) { 449 updateEnabledState(); 450 } 451 } 452 453 /** 454 * Sets the current selection to the list of relations selected in this dialog 455 * 456 */ 457 class SelectAction extends AbstractAction implements ListSelectionListener{ 458 boolean add; 459 public SelectAction(boolean add) { 460 putValue(SHORT_DESCRIPTION, add ? tr("Add the selected relations to the current selection") 461 : tr("Set the current selection to the list of selected relations")); 462 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); 463 putValue(NAME, add ? tr("Select relation (add)") : tr("Select relation")); 464 this.add = add; 465 updateEnabledState(); 466 } 467 468 public void actionPerformed(ActionEvent e) { 469 if (!isEnabled()) return; 470 int [] idx = displaylist.getSelectedIndices(); 471 if (idx == null || idx.length == 0) return; 472 ArrayList<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(idx.length); 473 for (int i: idx) { 474 selection.add(model.getRelation(i)); 475 } 476 if(add) { 477 Main.map.mapView.getEditLayer().data.addSelected(selection); 478 } else { 479 Main.map.mapView.getEditLayer().data.setSelected(selection); 480 } 481 } 482 483 protected void updateEnabledState() { 484 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0); 485 } 486 487 public void valueChanged(ListSelectionEvent e) { 488 updateEnabledState(); 489 } 490 } 491 492 /** 493 * Sets the current selection to the list of relations selected in this dialog 494 * 495 */ 496 class SelectMembersAction extends AbstractAction implements ListSelectionListener{ 497 boolean add; 498 public SelectMembersAction(boolean add) { 499 putValue(SHORT_DESCRIPTION,add ? tr("Add the members of all selected relations to current selection") 500 : tr("Select the members of all selected relations")); 501 putValue(SMALL_ICON, ImageProvider.get("selectall")); 502 putValue(NAME, add ? tr("Select members (add)") : tr("Select members")); 503 this.add = add; 504 updateEnabledState(); 505 } 506 507 public void actionPerformed(ActionEvent e) { 508 if (!isEnabled()) return; 509 List<Relation> relations = model.getSelectedRelations(); 510 HashSet<OsmPrimitive> members = new HashSet<OsmPrimitive>(); 511 for(Relation r: relations) { 512 members.addAll(r.getMemberPrimitives()); 513 } 514 if(add) { 515 Main.map.mapView.getEditLayer().data.addSelected(members); 516 } else { 517 Main.map.mapView.getEditLayer().data.setSelected(members); 518 } 519 } 520 521 protected void updateEnabledState() { 522 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0); 523 } 524 525 public void valueChanged(ListSelectionEvent e) { 526 updateEnabledState(); 527 } 528 } 529 530 /** 531 * The action for downloading members of all selected relations 532 * 533 */ 534 class DownloadMembersAction extends AbstractAction implements ListSelectionListener{ 535 536 public DownloadMembersAction() { 537 putValue(SHORT_DESCRIPTION,tr("Download all members of the selected relations")); 538 putValue(NAME, tr("Download members")); 539 putValue(SMALL_ICON, ImageProvider.get("dialogs", "downloadincomplete")); 540 putValue("help", ht("/Dialog/RelationList#DownloadMembers")); 541 updateEnabledState(); 542 } 543 544 protected void updateEnabledState() { 545 setEnabled(! model.getSelectedNonNewRelations().isEmpty()); 546 } 547 548 public void valueChanged(ListSelectionEvent e) { 549 updateEnabledState(); 550 } 551 552 public void actionPerformed(ActionEvent e) { 553 List<Relation> relations = model.getSelectedNonNewRelations(); 554 if (relations.isEmpty()) 555 return; 556 Main.worker.submit(new DownloadRelationTask( 557 model.getSelectedNonNewRelations(), 558 Main.map.mapView.getEditLayer()) 559 ); 560 } 561 } 562 563 /** 564 * Action for downloading incomplete members of selected relations 565 * 566 */ 567 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener{ 568 public DownloadSelectedIncompleteMembersAction() { 569 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations")); 570 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected")); 571 putValue(NAME, tr("Download incomplete members")); 572 updateEnabledState(); 573 } 574 575 public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) { 576 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 577 for(Relation r: rels) { 578 ret.addAll(r.getIncompleteMembers()); 579 } 580 return ret; 581 } 582 583 public void actionPerformed(ActionEvent e) { 584 if (!isEnabled()) 585 return; 586 List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers(); 587 if (rels.isEmpty()) return; 588 Main.worker.submit(new DownloadRelationMemberTask( 589 rels, 590 buildSetOfIncompleteMembers(rels), 591 Main.map.mapView.getEditLayer() 592 )); 593 } 594 595 protected void updateEnabledState() { 596 setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty()); 597 } 598 599 public void valueChanged(ListSelectionEvent e) { 600 updateEnabledState(); 601 } 602 } 603 604 class AddToRelation extends AbstractAction implements ListSelectionListener, SelectionChangedListener { 605 606 public AddToRelation() { 607 super("", ImageProvider.get("dialogs/conflict", "copyendright")); 608 putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member")); 609 setEnabled(false); 610 } 611 612 @Override 613 public void actionPerformed(ActionEvent e) { 614 Collection<Command> cmds = new LinkedList<Command>(); 615 for (Relation orig : getSelectedRelations()) { 616 Command c = GenericRelationEditor.addPrimitivesToRelation(orig, Main.main.getCurrentDataSet().getSelected()); 617 if (c != null) { 618 cmds.add(c); 619 } 620 } 621 if (!cmds.isEmpty()) { 622 Main.main.undoRedo.add(new SequenceCommand(tr("Add selection to relation"), cmds)); 623 } 624 } 625 626 @Override 627 public void valueChanged(ListSelectionEvent e) { 628 putValue(NAME, trn("Add selection to {0} relation", "Add selection to {0} relations", 629 getSelectedRelations().size(), getSelectedRelations().size())); 630 } 631 632 @Override 633 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 634 setEnabled(newSelection != null && !newSelection.isEmpty()); 635 } 636 } 637 638 /** 639 * The list model for the list of relations displayed in the relation list 640 * dialog. 641 * 642 */ 643 private class RelationListModel extends AbstractListModel { 644 private final ArrayList<Relation> relations = new ArrayList<Relation>(); 645 private DefaultListSelectionModel selectionModel; 646 647 public RelationListModel(DefaultListSelectionModel selectionModel) { 648 this.selectionModel = selectionModel; 649 } 650 651 public Relation getRelation(int idx) { 652 return relations.get(idx); 653 } 654 655 public void sort() { 656 Collections.sort( 657 relations, 658 DefaultNameFormatter.getInstance().getRelationComparator() 659 ); 660 } 661 662 private boolean isValid(Relation r) { 663 return !r.isDeleted() && r.isVisible() && !r.isIncomplete(); 664 } 665 666 public void setRelations(Collection<Relation> relations) { 667 List<Relation> sel = getSelectedRelations(); 668 this.relations.clear(); 669 if (relations == null) { 670 selectionModel.clearSelection(); 671 fireContentsChanged(this,0,getSize()); 672 return; 673 674 } 675 for (Relation r: relations) { 676 if (isValid(r)) { 677 this.relations.add(r); 678 } 679 } 680 sort(); 681 fireIntervalAdded(this, 0, getSize()); 682 setSelectedRelations(sel); 683 } 684 685 /** 686 * Add all relations in <code>addedPrimitives</code> to the model for the 687 * relation list dialog 688 * 689 * @param addedPrimitives the collection of added primitives. May include nodes, 690 * ways, and relations. 691 */ 692 public void addRelations(Collection<? extends OsmPrimitive> addedPrimitives) { 693 boolean added = false; 694 for (OsmPrimitive p: addedPrimitives) { 695 if (! (p instanceof Relation)) { 696 continue; 697 } 698 699 Relation r = (Relation)p; 700 if (relations.contains(r)) { 701 continue; 702 } 703 if (isValid(r)) { 704 relations.add(r); 705 added = true; 706 } 707 } 708 if (added) { 709 List<Relation> sel = getSelectedRelations(); 710 sort(); 711 fireIntervalAdded(this, 0, getSize()); 712 setSelectedRelations(sel); 713 } 714 } 715 716 /** 717 * Removes all relations in <code>removedPrimitives</code> from the model 718 * 719 * @param removedPrimitives the removed primitives. May include nodes, ways, 720 * and relations 721 */ 722 public void removeRelations(Collection<? extends OsmPrimitive> removedPrimitives) { 723 if (removedPrimitives == null) return; 724 // extract the removed relations 725 // 726 Set<Relation> removedRelations = new HashSet<Relation>(); 727 for (OsmPrimitive p: removedPrimitives) { 728 if (! (p instanceof Relation)) { 729 continue; 730 } 731 removedRelations.add((Relation)p); 732 } 733 if (removedRelations.isEmpty()) 734 return; 735 int size = relations.size(); 736 relations.removeAll(removedRelations); 737 if (size != relations.size()) { 738 List<Relation> sel = getSelectedRelations(); 739 sort(); 740 fireContentsChanged(this, 0, getSize()); 741 setSelectedRelations(sel); 742 } 743 } 744 745 /** 746 * Replies the list of selected relations with incomplete members 747 * 748 * @return the list of selected relations with incomplete members 749 */ 750 public List<Relation> getSelectedRelationsWithIncompleteMembers() { 751 List<Relation> ret = getSelectedNonNewRelations(); 752 Iterator<Relation> it = ret.iterator(); 753 while(it.hasNext()) { 754 Relation r = it.next(); 755 if (!r.hasIncompleteMembers()) { 756 it.remove(); 757 } 758 } 759 return ret; 760 } 761 762 public Object getElementAt(int index) { 763 return relations.get(index); 764 } 765 766 public int getSize() { 767 return relations.size(); 768 } 769 770 /** 771 * Replies the list of selected, non-new relations. Empty list, 772 * if there are no selected, non-new relations. 773 * 774 * @return the list of selected, non-new relations. 775 */ 776 public List<Relation> getSelectedNonNewRelations() { 777 ArrayList<Relation> ret = new ArrayList<Relation>(); 778 for (int i=0; i<getSize();i++) { 779 if (!selectionModel.isSelectedIndex(i)) { 780 continue; 781 } 782 if (relations.get(i).isNew()) { 783 continue; 784 } 785 ret.add(relations.get(i)); 786 } 787 return ret; 788 } 789 790 /** 791 * Replies the list of selected relations. Empty list, 792 * if there are no selected relations. 793 * 794 * @return the list of selected, non-new relations. 795 */ 796 public List<Relation> getSelectedRelations() { 797 ArrayList<Relation> ret = new ArrayList<Relation>(); 798 for (int i=0; i<getSize();i++) { 799 if (!selectionModel.isSelectedIndex(i)) { 800 continue; 801 } 802 ret.add(relations.get(i)); 803 } 804 return ret; 805 } 806 807 /** 808 * Sets the selected relations. 809 * 810 * @return sel the list of selected relations 811 */ 812 public void setSelectedRelations(Collection<Relation> sel) { 813 selectionModel.clearSelection(); 814 if (sel == null || sel.isEmpty()) 815 return; 816 for (Relation r: sel) { 817 int i = relations.indexOf(r); 818 if (i<0) { 819 continue; 820 } 821 selectionModel.addSelectionInterval(i,i); 822 } 823 } 824 825 /** 826 * Returns the index of the relation 827 * 828 * @return index of relation (null if it cannot be found) 829 */ 830 public Integer getRelationIndex(Relation rel) { 831 int i = relations.indexOf(rel); 832 if (i<0) 833 return null; 834 return i; 835 } 836 837 public void updateTitle() { 838 if (getSize() > 0) { 839 RelationListDialog.this.setTitle(tr("Relations: {0}", getSize())); 840 } else { 841 RelationListDialog.this.setTitle(tr("Relations")); 842 } 843 } 844 } 845 846 class RelationDialogPopupMenu extends ListPopupMenu { 847 848 public RelationDialogPopupMenu(JList list) { 849 super(list); 850 851 // -- download members action 852 add(new DownloadMembersAction()); 853 854 // -- download incomplete members action 855 add(new DownloadSelectedIncompleteMembersAction()); 856 857 addSeparator(); 858 859 // -- select members action 860 add(new SelectMembersAction(false)); 861 add(new SelectMembersAction(true)); 862 863 // -- select action 864 add(new SelectAction(false)); 865 add(new SelectAction(true)); 866 867 addSeparator(); 868 869 add(addToRelation); 870 } 871 } 872 873 public void addPopupMenuSeparator() { 874 popupMenu.addSeparator(); 875 } 876 877 public JMenuItem addPopupMenuAction(Action a) { 878 return popupMenu.add(a); 879 } 880 881 public void addPopupMenuListener(PopupMenuListener l) { 882 popupMenu.addPopupMenuListener(l); 883 } 884 885 public void removePopupMenuListener(PopupMenuListener l) { 886 popupMenu.addPopupMenuListener(l); 887 } 888 889 public Collection<Relation> getSelectedRelations() { 890 return model.getSelectedRelations(); 891 } 892 893 /* ---------------------------------------------------------------------------------- */ 894 /* DataSetListener */ 895 /* ---------------------------------------------------------------------------------- */ 896 897 public void nodeMoved(NodeMovedEvent event) {/* irrelevant in this context */} 898 899 public void wayNodesChanged(WayNodesChangedEvent event) {/* irrelevant in this context */} 900 901 public void primitivesAdded(final PrimitivesAddedEvent event) { 902 model.addRelations(event.getPrimitives()); 903 model.updateTitle(); 904 } 905 906 public void primitivesRemoved(final PrimitivesRemovedEvent event) { 907 model.removeRelations(event.getPrimitives()); 908 model.updateTitle(); 909 } 910 911 public void relationMembersChanged(final RelationMembersChangedEvent event) { 912 List<Relation> sel = model.getSelectedRelations(); 913 model.sort(); 914 model.setSelectedRelations(sel); 915 displaylist.repaint(); 916 } 917 918 public void tagsChanged(TagsChangedEvent event) { 919 OsmPrimitive prim = event.getPrimitive(); 920 if (prim == null || ! (prim instanceof Relation)) 921 return; 922 // trigger a sort of the relation list because the display name may 923 // have changed 924 // 925 List<Relation> sel = model.getSelectedRelations(); 926 model.sort(); 927 model.setSelectedRelations(sel); 928 displaylist.repaint(); 929 } 930 931 public void dataChanged(DataChangedEvent event) { 932 initFromLayer(Main.main.getEditLayer()); 933 } 934 935 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */} 936 }