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