001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Component; 008import java.awt.event.ActionEvent; 009import java.awt.event.KeyEvent; 010import java.awt.event.MouseEvent; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.Collection; 014import java.util.Collections; 015import java.util.EnumSet; 016import java.util.HashSet; 017import java.util.List; 018import java.util.Set; 019 020import javax.swing.AbstractAction; 021import javax.swing.AbstractListModel; 022import javax.swing.DefaultListSelectionModel; 023import javax.swing.FocusManager; 024import javax.swing.JComponent; 025import javax.swing.JList; 026import javax.swing.JMenuItem; 027import javax.swing.JPanel; 028import javax.swing.JPopupMenu; 029import javax.swing.JScrollPane; 030import javax.swing.KeyStroke; 031import javax.swing.ListSelectionModel; 032import javax.swing.event.PopupMenuEvent; 033import javax.swing.event.PopupMenuListener; 034 035import org.openstreetmap.josm.Main; 036import org.openstreetmap.josm.actions.ExpertToggleAction; 037import org.openstreetmap.josm.actions.IPrimitiveAction; 038import org.openstreetmap.josm.actions.relation.AddSelectionToRelations; 039import org.openstreetmap.josm.actions.relation.DeleteRelationsAction; 040import org.openstreetmap.josm.actions.relation.DownloadMembersAction; 041import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 042import org.openstreetmap.josm.actions.relation.DuplicateRelationAction; 043import org.openstreetmap.josm.actions.relation.EditRelationAction; 044import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction; 045import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode; 046import org.openstreetmap.josm.actions.relation.RecentRelationsAction; 047import org.openstreetmap.josm.actions.relation.SelectMembersAction; 048import org.openstreetmap.josm.actions.relation.SelectRelationAction; 049import org.openstreetmap.josm.data.osm.DataSet; 050import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 051import org.openstreetmap.josm.data.osm.IPrimitive; 052import org.openstreetmap.josm.data.osm.IRelation; 053import org.openstreetmap.josm.data.osm.OsmData; 054import org.openstreetmap.josm.data.osm.OsmPrimitive; 055import org.openstreetmap.josm.data.osm.Relation; 056import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 057import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 058import org.openstreetmap.josm.data.osm.event.DataSetListener; 059import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 060import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 061import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 062import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 063import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 064import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 065import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 066import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 067import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 068import org.openstreetmap.josm.data.osm.search.SearchCompiler; 069import org.openstreetmap.josm.gui.MainApplication; 070import org.openstreetmap.josm.gui.MapView; 071import org.openstreetmap.josm.gui.NavigatableComponent; 072import org.openstreetmap.josm.gui.PopupMenuHandler; 073import org.openstreetmap.josm.gui.PrimitiveRenderer; 074import org.openstreetmap.josm.gui.SideButton; 075import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 076import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 077import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 078import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 079import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 080import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 081import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 082import org.openstreetmap.josm.gui.util.HighlightHelper; 083import org.openstreetmap.josm.gui.widgets.CompileSearchTextDecorator; 084import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField; 085import org.openstreetmap.josm.gui.widgets.JosmTextField; 086import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 087import org.openstreetmap.josm.spi.preferences.Config; 088import org.openstreetmap.josm.tools.ImageProvider; 089import org.openstreetmap.josm.tools.InputMapUtils; 090import org.openstreetmap.josm.tools.Shortcut; 091import org.openstreetmap.josm.tools.SubclassFilteredCollection; 092import org.openstreetmap.josm.tools.Utils; 093 094/** 095 * A dialog showing all known relations, with buttons to add, edit, and delete them. 096 * 097 * We don't have such dialogs for nodes, segments, and ways, because those 098 * objects are visible on the map and can be selected there. Relations are not. 099 */ 100public class RelationListDialog extends ToggleDialog 101 implements DataSetListener, NavigatableComponent.ZoomChangeListener, ExpertToggleAction.ExpertModeChangeListener { 102 /** The display list. */ 103 private final JList<IRelation<?>> displaylist; 104 /** the list model used */ 105 private final RelationListModel model; 106 107 private final NewAction newAction; 108 109 /** the popup menu and its handler */ 110 private final JPopupMenu popupMenu = new JPopupMenu(); 111 private final transient PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu); 112 113 private final JosmTextField filter; 114 115 // Actions 116 /** the edit action */ 117 private final EditRelationAction editAction = new EditRelationAction(); 118 /** the delete action */ 119 private final DeleteRelationsAction deleteRelationsAction = new DeleteRelationsAction(); 120 /** the duplicate action */ 121 private final DuplicateRelationAction duplicateAction = new DuplicateRelationAction(); 122 private final DownloadMembersAction downloadMembersAction = new DownloadMembersAction(); 123 private final DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = 124 new DownloadSelectedIncompleteMembersAction(); 125 private final SelectMembersAction selectMembersAction = new SelectMembersAction(false); 126 private final SelectMembersAction addMembersToSelectionAction = new SelectMembersAction(true); 127 private final SelectRelationAction selectRelationAction = new SelectRelationAction(false); 128 private final SelectRelationAction addRelationToSelectionAction = new SelectRelationAction(true); 129 /** add all selected primitives to the given relations */ 130 private final AddSelectionToRelations addSelectionToRelations = new AddSelectionToRelations(); 131 private transient JMenuItem addSelectionToRelationMenuItem; 132 133 /** export relation to GPX track action */ 134 private final ExportRelationToGpxAction exportRelationFromFirstAction = 135 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_FILE)); 136 private final ExportRelationToGpxAction exportRelationFromLastAction = 137 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_FILE)); 138 private final ExportRelationToGpxAction exportRelationFromFirstToLayerAction = 139 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_LAYER)); 140 private final ExportRelationToGpxAction exportRelationFromLastToLayerAction = 141 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_LAYER)); 142 143 private final transient HighlightHelper highlightHelper = new HighlightHelper(); 144 private final boolean highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true); 145 private final transient RecentRelationsAction recentRelationsAction; 146 147 /** 148 * Constructs <code>RelationListDialog</code> 149 */ 150 public RelationListDialog() { 151 super(tr("Relations"), "relationlist", tr("Open a list of all relations."), 152 Shortcut.registerShortcut("subwindow:relations", tr("Toggle: {0}", tr("Relations")), 153 KeyEvent.VK_R, Shortcut.ALT_SHIFT), 150, true); 154 155 // create the list of relations 156 // 157 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 158 model = new RelationListModel(selectionModel); 159 displaylist = new JList<>(model); 160 displaylist.setSelectionModel(selectionModel); 161 displaylist.setCellRenderer(new NoTooltipOsmRenderer()); 162 displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 163 displaylist.addMouseListener(new MouseEventHandler()); 164 165 // the new action 166 // 167 newAction = new NewAction(); 168 169 filter = setupFilter(); 170 171 displaylist.addListSelectionListener(e -> { 172 if (!e.getValueIsAdjusting()) updateActionsRelationLists(); 173 }); 174 175 // Setup popup menu handler 176 setupPopupMenuHandler(); 177 178 JPanel pane = new JPanel(new BorderLayout()); 179 pane.add(filter, BorderLayout.NORTH); 180 pane.add(new JScrollPane(displaylist), BorderLayout.CENTER); 181 182 SideButton editButton = new SideButton(editAction, false); 183 recentRelationsAction = new RecentRelationsAction(editButton); 184 185 createLayout(pane, false, Arrays.asList( 186 new SideButton(newAction, false), 187 editButton, 188 new SideButton(duplicateAction, false), 189 new SideButton(deleteRelationsAction, false), 190 new SideButton(selectRelationAction, false) 191 )); 192 193 InputMapUtils.unassignCtrlShiftUpDown(displaylist, JComponent.WHEN_FOCUSED); 194 195 // Select relation on Enter 196 InputMapUtils.addEnterAction(displaylist, selectRelationAction); 197 198 // Edit relation on Ctrl-Enter 199 displaylist.getActionMap().put("edit", editAction); 200 displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK), "edit"); 201 202 // Do not hide copy action because of default JList override (fix #9815) 203 displaylist.getActionMap().put("copy", MainApplication.getMenu().copy); 204 displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, Main.platform.getMenuShortcutKeyMaskEx()), "copy"); 205 206 updateActionsRelationLists(); 207 } 208 209 @Override 210 public void destroy() { 211 recentRelationsAction.destroy(); 212 model.clear(); 213 super.destroy(); 214 } 215 216 /** 217 * Enable the "recent relations" dropdown menu next to edit button. 218 */ 219 public void enableRecentRelations() { 220 recentRelationsAction.enableArrow(); 221 } 222 223 // inform all actions about list of relations they need 224 private void updateActionsRelationLists() { 225 List<IRelation<?>> sel = model.getSelectedRelations(); 226 popupMenuHandler.setPrimitives(sel); 227 228 Component focused = FocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 229 230 //update highlights 231 if (highlightEnabled && focused == displaylist && MainApplication.isDisplayingMapView() 232 && highlightHelper.highlightOnly(Utils.filteredCollection(sel, Relation.class))) { 233 MainApplication.getMap().mapView.repaint(); 234 } 235 } 236 237 @Override 238 public void showNotify() { 239 MainApplication.getLayerManager().addLayerChangeListener(newAction); 240 MainApplication.getLayerManager().addActiveLayerChangeListener(newAction); 241 MapView.addZoomChangeListener(this); 242 newAction.updateEnabledState(); 243 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT); 244 SelectionEventManager.getInstance().addSelectionListener(addSelectionToRelations); 245 dataChanged(null); 246 ExpertToggleAction.addExpertModeChangeListener(this); 247 expertChanged(ExpertToggleAction.isExpert()); 248 } 249 250 @Override 251 public void hideNotify() { 252 MainApplication.getLayerManager().removeActiveLayerChangeListener(newAction); 253 MainApplication.getLayerManager().removeLayerChangeListener(newAction); 254 MapView.removeZoomChangeListener(this); 255 DatasetEventManager.getInstance().removeDatasetListener(this); 256 SelectionEventManager.getInstance().removeSelectionListener(addSelectionToRelations); 257 ExpertToggleAction.removeExpertModeChangeListener(this); 258 } 259 260 private void resetFilter() { 261 filter.setText(null); 262 } 263 264 /** 265 * Initializes the relation list dialog from a dataset. If <code>data</code> is null 266 * the dialog is reset to an empty dialog. 267 * Otherwise it is initialized with the list of non-deleted and visible relations 268 * in the dataset. 269 * 270 * @param data the dataset. May be null. 271 * @since 13957 272 */ 273 protected void initFromData(OsmData<?, ?, ?, ?> data) { 274 if (data == null) { 275 model.setRelations(null); 276 return; 277 } 278 model.setRelations(data.getRelations()); 279 model.updateTitle(); 280 updateActionsRelationLists(); 281 } 282 283 /** 284 * @return The selected relation in the list 285 */ 286 private IRelation<?> getSelected() { 287 if (model.getSize() == 1) { 288 displaylist.setSelectedIndex(0); 289 } 290 return displaylist.getSelectedValue(); 291 } 292 293 /** 294 * Selects the relation <code>relation</code> in the list of relations. 295 * 296 * @param relation the relation 297 */ 298 public void selectRelation(Relation relation) { 299 selectRelations(Collections.singleton(relation)); 300 } 301 302 /** 303 * Selects the relations in the list of relations. 304 * @param relations the relations to be selected 305 * @since 13957 (signature) 306 */ 307 public void selectRelations(Collection<? extends IRelation<?>> relations) { 308 if (relations == null || relations.isEmpty()) { 309 model.setSelectedRelations(null); 310 } else { 311 model.setSelectedRelations(relations); 312 Integer i = model.getVisibleRelationIndex(relations.iterator().next()); 313 if (i != null) { 314 // Not all relations have to be in the list 315 // (for example when the relation list is hidden, it's not updated with new relations) 316 displaylist.scrollRectToVisible(displaylist.getCellBounds(i, i)); 317 } 318 } 319 } 320 321 private JosmTextField setupFilter() { 322 final JosmTextField f = new DisableShortcutsOnFocusGainedTextField(); 323 f.setToolTipText(tr("Relation list filter")); 324 final CompileSearchTextDecorator decorator = CompileSearchTextDecorator.decorate(f); 325 f.addPropertyChangeListener("filter", evt -> model.setFilter(decorator.getMatch())); 326 return f; 327 } 328 329 static final class NoTooltipOsmRenderer extends PrimitiveRenderer { 330 @Override 331 protected String getComponentToolTipText(IPrimitive value) { 332 // Don't show the default tooltip in the relation list 333 return null; 334 } 335 } 336 337 class MouseEventHandler extends PopupMenuLauncher { 338 339 MouseEventHandler() { 340 super(popupMenu); 341 } 342 343 @Override 344 public void mouseExited(MouseEvent me) { 345 if (highlightEnabled) highlightHelper.clear(); 346 } 347 348 protected void setCurrentRelationAsSelection() { 349 MainApplication.getLayerManager().getActiveData().setSelected(displaylist.getSelectedValue()); 350 } 351 352 protected void editCurrentRelation() { 353 IRelation<?> rel = getSelected(); 354 if (rel instanceof Relation) { 355 EditRelationAction.launchEditor((Relation) rel); 356 } 357 } 358 359 @Override 360 public void mouseClicked(MouseEvent e) { 361 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 362 if (ds != null && isDoubleClick(e)) { 363 if (e.isControlDown() && !ds.isLocked()) { 364 editCurrentRelation(); 365 } else { 366 setCurrentRelationAsSelection(); 367 } 368 } 369 } 370 } 371 372 /** 373 * The action for creating a new relation. 374 */ 375 static class NewAction extends AbstractAction implements LayerChangeListener, ActiveLayerChangeListener { 376 NewAction() { 377 putValue(SHORT_DESCRIPTION, tr("Create a new relation")); 378 putValue(NAME, tr("New")); 379 new ImageProvider("dialogs", "addrelation").getResource().attachImageIcon(this, true); 380 updateEnabledState(); 381 } 382 383 public void run() { 384 RelationEditor.getEditor(MainApplication.getLayerManager().getEditLayer(), null, null).setVisible(true); 385 } 386 387 @Override 388 public void actionPerformed(ActionEvent e) { 389 run(); 390 } 391 392 protected void updateEnabledState() { 393 setEnabled(MainApplication.getLayerManager().getEditLayer() != null); 394 } 395 396 @Override 397 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 398 updateEnabledState(); 399 } 400 401 @Override 402 public void layerAdded(LayerAddEvent e) { 403 updateEnabledState(); 404 } 405 406 @Override 407 public void layerRemoving(LayerRemoveEvent e) { 408 updateEnabledState(); 409 } 410 411 @Override 412 public void layerOrderChanged(LayerOrderChangeEvent e) { 413 // Do nothing 414 } 415 } 416 417 /** 418 * The list model for the list of relations displayed in the relation list dialog. 419 */ 420 private class RelationListModel extends AbstractListModel<IRelation<?>> { 421 private final transient List<IRelation<?>> relations = new ArrayList<>(); 422 private transient List<IRelation<?>> filteredRelations; 423 private final DefaultListSelectionModel selectionModel; 424 private transient SearchCompiler.Match filter; 425 426 RelationListModel(DefaultListSelectionModel selectionModel) { 427 this.selectionModel = selectionModel; 428 } 429 430 /** 431 * Clears the model. 432 */ 433 public void clear() { 434 relations.clear(); 435 if (filteredRelations != null) 436 filteredRelations.clear(); 437 filter = null; 438 } 439 440 /** 441 * Sorts the model using {@link DefaultNameFormatter} relation comparator. 442 */ 443 public void sort() { 444 relations.sort(DefaultNameFormatter.getInstance().getRelationComparator()); 445 } 446 447 private boolean isValid(IRelation<?> r) { 448 return !r.isDeleted() && !r.isIncomplete(); 449 } 450 451 public void setRelations(Collection<? extends IRelation<?>> relations) { 452 List<IRelation<?>> sel = getSelectedRelations(); 453 this.relations.clear(); 454 this.filteredRelations = null; 455 if (relations == null) { 456 selectionModel.clearSelection(); 457 fireContentsChanged(this, 0, getSize()); 458 return; 459 } 460 for (IRelation<?> r: relations) { 461 if (isValid(r)) { 462 this.relations.add(r); 463 } 464 } 465 sort(); 466 updateFilteredRelations(); 467 fireIntervalAdded(this, 0, getSize()); 468 setSelectedRelations(sel); 469 } 470 471 /** 472 * Add all relations in <code>addedPrimitives</code> to the model for the 473 * relation list dialog 474 * 475 * @param addedPrimitives the collection of added primitives. May include nodes, 476 * ways, and relations. 477 */ 478 public void addRelations(Collection<? extends OsmPrimitive> addedPrimitives) { 479 boolean added = false; 480 for (OsmPrimitive p: addedPrimitives) { 481 if (!(p instanceof Relation)) { 482 continue; 483 } 484 485 Relation r = (Relation) p; 486 if (relations.contains(r)) { 487 continue; 488 } 489 if (isValid(r)) { 490 relations.add(r); 491 added = true; 492 } 493 } 494 if (added) { 495 List<IRelation<?>> sel = getSelectedRelations(); 496 sort(); 497 updateFilteredRelations(); 498 fireIntervalAdded(this, 0, getSize()); 499 setSelectedRelations(sel); 500 } 501 } 502 503 /** 504 * Removes all relations in <code>removedPrimitives</code> from the model 505 * 506 * @param removedPrimitives the removed primitives. May include nodes, ways, 507 * and relations 508 */ 509 public void removeRelations(Collection<? extends OsmPrimitive> removedPrimitives) { 510 if (removedPrimitives == null) return; 511 // extract the removed relations 512 // 513 Set<Relation> removedRelations = new HashSet<>(); 514 for (OsmPrimitive p: removedPrimitives) { 515 if (!(p instanceof Relation)) { 516 continue; 517 } 518 removedRelations.add((Relation) p); 519 } 520 if (removedRelations.isEmpty()) 521 return; 522 int size = relations.size(); 523 relations.removeAll(removedRelations); 524 if (filteredRelations != null) { 525 filteredRelations.removeAll(removedRelations); 526 } 527 if (size != relations.size()) { 528 List<IRelation<?>> sel = getSelectedRelations(); 529 sort(); 530 fireContentsChanged(this, 0, getSize()); 531 setSelectedRelations(sel); 532 } 533 } 534 535 private void updateFilteredRelations() { 536 if (filter != null) { 537 filteredRelations = new ArrayList<>(SubclassFilteredCollection.filter(relations, filter::match)); 538 } else if (filteredRelations != null) { 539 filteredRelations = null; 540 } 541 } 542 543 public void setFilter(final SearchCompiler.Match filter) { 544 this.filter = filter; 545 updateFilteredRelations(); 546 List<IRelation<?>> sel = getSelectedRelations(); 547 fireContentsChanged(this, 0, getSize()); 548 setSelectedRelations(sel); 549 updateTitle(); 550 } 551 552 private List<IRelation<?>> getVisibleRelations() { 553 return filteredRelations == null ? relations : filteredRelations; 554 } 555 556 private IRelation<?> getVisibleRelation(int index) { 557 if (index < 0 || index >= getVisibleRelations().size()) return null; 558 return getVisibleRelations().get(index); 559 } 560 561 @Override 562 public IRelation<?> getElementAt(int index) { 563 return getVisibleRelation(index); 564 } 565 566 @Override 567 public int getSize() { 568 return getVisibleRelations().size(); 569 } 570 571 /** 572 * Replies the list of selected relations. Empty list, 573 * if there are no selected relations. 574 * 575 * @return the list of selected, non-new relations. 576 * @since 13957 (signature) 577 */ 578 public List<IRelation<?>> getSelectedRelations() { 579 List<IRelation<?>> ret = new ArrayList<>(); 580 for (int i = 0; i < getSize(); i++) { 581 if (!selectionModel.isSelectedIndex(i)) { 582 continue; 583 } 584 ret.add(getVisibleRelation(i)); 585 } 586 return ret; 587 } 588 589 /** 590 * Sets the selected relations. 591 * 592 * @param sel the list of selected relations 593 * @since 13957 (signature) 594 */ 595 public void setSelectedRelations(Collection<? extends IRelation<?>> sel) { 596 selectionModel.setValueIsAdjusting(true); 597 selectionModel.clearSelection(); 598 if (sel != null && !sel.isEmpty()) { 599 if (!getVisibleRelations().containsAll(sel)) { 600 resetFilter(); 601 } 602 for (IRelation<?> r: sel) { 603 Integer i = getVisibleRelationIndex(r); 604 if (i != null) { 605 selectionModel.addSelectionInterval(i, i); 606 } 607 } 608 } 609 selectionModel.setValueIsAdjusting(false); 610 } 611 612 private Integer getVisibleRelationIndex(IRelation<?> rel) { 613 int i = getVisibleRelations().indexOf(rel); 614 if (i < 0) 615 return null; 616 return i; 617 } 618 619 public void updateTitle() { 620 if (!relations.isEmpty() && relations.size() != getSize()) { 621 RelationListDialog.this.setTitle(tr("Relations: {0}/{1}", getSize(), relations.size())); 622 } else if (getSize() > 0) { 623 RelationListDialog.this.setTitle(tr("Relations: {0}", getSize())); 624 } else { 625 RelationListDialog.this.setTitle(tr("Relations")); 626 } 627 } 628 } 629 630 private void setupPopupMenuHandler() { 631 List<JMenuItem> checkDisabled = new ArrayList<>(); 632 633 // -- select action 634 popupMenuHandler.addAction(selectRelationAction); 635 popupMenuHandler.addAction(addRelationToSelectionAction); 636 637 // -- select members action 638 popupMenuHandler.addAction(selectMembersAction); 639 popupMenuHandler.addAction(addMembersToSelectionAction); 640 641 // -- download members action 642 popupMenuHandler.addSeparator(); 643 popupMenuHandler.addAction(downloadMembersAction); 644 popupMenuHandler.addAction(downloadSelectedIncompleteMembersAction); 645 646 // -- export relation to gpx action 647 popupMenuHandler.addSeparator(); 648 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstAction)); 649 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastAction)); 650 popupMenuHandler.addSeparator(); 651 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstToLayerAction)); 652 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastToLayerAction)); 653 654 popupMenuHandler.addSeparator(); 655 popupMenuHandler.addAction(editAction).setVisible(false); 656 popupMenuHandler.addAction(duplicateAction).setVisible(false); 657 popupMenuHandler.addAction(deleteRelationsAction).setVisible(false); 658 659 addSelectionToRelationMenuItem = popupMenuHandler.addAction(addSelectionToRelations); 660 661 popupMenuHandler.addListener(new PopupMenuListener() { 662 @Override 663 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 664 for (JMenuItem mi: checkDisabled) { 665 mi.setVisible(((IPrimitiveAction) mi.getAction()).isEnabled()); 666 667 Component sep = popupMenu.getComponent( 668 Math.max(0, popupMenu.getComponentIndex(mi)-1)); 669 if (!(sep instanceof JMenuItem)) { 670 sep.setVisible(mi.isVisible()); 671 } 672 } 673 } 674 675 @Override 676 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 677 // Do nothing 678 } 679 680 @Override 681 public void popupMenuCanceled(PopupMenuEvent e) { 682 // Do nothing 683 } 684 }); 685 } 686 687 /* ---------------------------------------------------------------------------------- */ 688 /* Methods that can be called from plugins */ 689 /* ---------------------------------------------------------------------------------- */ 690 691 /** 692 * Replies the popup menu handler. 693 * @return The popup menu handler 694 */ 695 public PopupMenuHandler getPopupMenuHandler() { 696 return popupMenuHandler; 697 } 698 699 /** 700 * Replies the list of selected relations. Empty list, if there are no selected relations. 701 * @return the list of selected, non-new relations. 702 * @since 13957 (signature) 703 */ 704 public Collection<IRelation<?>> getSelectedRelations() { 705 return model.getSelectedRelations(); 706 } 707 708 /* ---------------------------------------------------------------------------------- */ 709 /* DataSetListener */ 710 /* ---------------------------------------------------------------------------------- */ 711 712 @Override 713 public void nodeMoved(NodeMovedEvent event) { 714 /* irrelevant in this context */ 715 } 716 717 @Override 718 public void wayNodesChanged(WayNodesChangedEvent event) { 719 /* irrelevant in this context */ 720 } 721 722 @Override 723 public void primitivesAdded(final PrimitivesAddedEvent event) { 724 model.addRelations(event.getPrimitives()); 725 model.updateTitle(); 726 } 727 728 @Override 729 public void primitivesRemoved(final PrimitivesRemovedEvent event) { 730 model.removeRelations(event.getPrimitives()); 731 model.updateTitle(); 732 } 733 734 @Override 735 public void relationMembersChanged(final RelationMembersChangedEvent event) { 736 List<IRelation<?>> sel = model.getSelectedRelations(); 737 model.sort(); 738 model.setSelectedRelations(sel); 739 displaylist.repaint(); 740 } 741 742 @Override 743 public void tagsChanged(TagsChangedEvent event) { 744 OsmPrimitive prim = event.getPrimitive(); 745 if (!(prim instanceof Relation)) 746 return; 747 // trigger a sort of the relation list because the display name may have changed 748 List<IRelation<?>> sel = model.getSelectedRelations(); 749 model.sort(); 750 model.setSelectedRelations(sel); 751 displaylist.repaint(); 752 } 753 754 @Override 755 public void dataChanged(DataChangedEvent event) { 756 initFromData(MainApplication.getLayerManager().getActiveData()); 757 } 758 759 @Override 760 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 761 /* ignore */ 762 } 763 764 @Override 765 public void zoomChanged() { 766 // re-filter relations 767 if (model.filter != null) { 768 model.setFilter(model.filter); 769 } 770 } 771 772 @Override 773 public void expertChanged(boolean isExpert) { 774 addSelectionToRelationMenuItem.setVisible(isExpert); 775 } 776}