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