001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.dialogs.relation; 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.BorderLayout; 009 import java.awt.Dimension; 010 import java.awt.FlowLayout; 011 import java.awt.GridBagConstraints; 012 import java.awt.GridBagLayout; 013 import java.awt.event.ActionEvent; 014 import java.awt.event.FocusAdapter; 015 import java.awt.event.FocusEvent; 016 import java.awt.event.KeyEvent; 017 import java.awt.event.MouseAdapter; 018 import java.awt.event.MouseEvent; 019 import java.awt.event.WindowAdapter; 020 import java.awt.event.WindowEvent; 021 import java.beans.PropertyChangeEvent; 022 import java.beans.PropertyChangeListener; 023 import java.util.ArrayList; 024 import java.util.Collection; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.HashSet; 028 import java.util.Iterator; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.Set; 032 033 import javax.swing.*; 034 import javax.swing.event.ChangeEvent; 035 import javax.swing.event.ChangeListener; 036 import javax.swing.event.DocumentEvent; 037 import javax.swing.event.DocumentListener; 038 import javax.swing.event.ListSelectionEvent; 039 import javax.swing.event.ListSelectionListener; 040 import javax.swing.event.TableModelEvent; 041 import javax.swing.event.TableModelListener; 042 043 import org.openstreetmap.josm.Main; 044 import org.openstreetmap.josm.actions.CopyAction; 045 import org.openstreetmap.josm.actions.JosmAction; 046 import org.openstreetmap.josm.actions.PasteTagsAction.TagPaster; 047 import org.openstreetmap.josm.command.AddCommand; 048 import org.openstreetmap.josm.command.ChangeCommand; 049 import org.openstreetmap.josm.command.Command; 050 import org.openstreetmap.josm.command.ConflictAddCommand; 051 import org.openstreetmap.josm.data.conflict.Conflict; 052 import org.openstreetmap.josm.data.osm.DataSet; 053 import org.openstreetmap.josm.data.osm.OsmPrimitive; 054 import org.openstreetmap.josm.data.osm.PrimitiveData; 055 import org.openstreetmap.josm.data.osm.Relation; 056 import org.openstreetmap.josm.data.osm.RelationMember; 057 import org.openstreetmap.josm.data.osm.Tag; 058 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 059 import org.openstreetmap.josm.gui.DefaultNameFormatter; 060 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 061 import org.openstreetmap.josm.gui.MainMenu; 062 import org.openstreetmap.josm.gui.SideButton; 063 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 064 import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel.PresetHandler; 065 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 066 import org.openstreetmap.josm.gui.help.HelpUtil; 067 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 068 import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 069 import org.openstreetmap.josm.gui.tagging.TagModel; 070 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 071 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 072 import org.openstreetmap.josm.tools.ImageProvider; 073 import org.openstreetmap.josm.tools.Shortcut; 074 import org.openstreetmap.josm.tools.WindowGeometry; 075 076 /** 077 * This dialog is for editing relations. 078 * 079 */ 080 public class GenericRelationEditor extends RelationEditor { 081 /** the tag table and its model */ 082 private TagEditorPanel tagEditorPanel; 083 private ReferringRelationsBrowser referrerBrowser; 084 private ReferringRelationsBrowserModel referrerModel; 085 086 /** the member table */ 087 private MemberTable memberTable; 088 private MemberTableModel memberTableModel; 089 090 /** the model for the selection table */ 091 private SelectionTable selectionTable; 092 private SelectionTableModel selectionTableModel; 093 094 private AutoCompletingTextField tfRole; 095 096 /** the menu item in the windows menu. Required to properly 097 * hide on dialog close. 098 */ 099 private JMenuItem windowMenuItem; 100 101 /** 102 * Creates a new relation editor for the given relation. The relation will be saved if the user 103 * selects "ok" in the editor. 104 * 105 * If no relation is given, will create an editor for a new relation. 106 * 107 * @param layer the {@link OsmDataLayer} the new or edited relation belongs to 108 * @param relation relation to edit, or null to create a new one. 109 * @param selectedMembers a collection of members which shall be selected initially 110 */ 111 public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) { 112 super(layer, relation, selectedMembers); 113 114 setRememberWindowGeometry(getClass().getName() + ".geometry", 115 WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650))); 116 117 // init the various models 118 // 119 memberTableModel = new MemberTableModel(getLayer()); 120 memberTableModel.register(); 121 selectionTableModel = new SelectionTableModel(getLayer()); 122 selectionTableModel.register(); 123 referrerModel = new ReferringRelationsBrowserModel(relation); 124 125 tagEditorPanel = new TagEditorPanel(new PresetHandler() { 126 127 @Override 128 public void updateTags(List<Tag> tags) { 129 GenericRelationEditor.this.updateTags(tags); 130 } 131 132 @Override 133 public Collection<OsmPrimitive> getSelection() { 134 Relation relation = new Relation(); 135 tagEditorPanel.getModel().applyToPrimitive(relation); 136 return Collections.<OsmPrimitive>singletonList(relation); 137 } 138 }); 139 140 // populate the models 141 // 142 if (relation != null) { 143 tagEditorPanel.getModel().initFromPrimitive(relation); 144 this.memberTableModel.populate(relation); 145 if (!getLayer().data.getRelations().contains(relation)) { 146 // treat it as a new relation if it doesn't exist in the 147 // data set yet. 148 setRelation(null); 149 } 150 } else { 151 tagEditorPanel.getModel().clear(); 152 this.memberTableModel.populate(null); 153 } 154 tagEditorPanel.getModel().ensureOneTag(); 155 156 JSplitPane pane = buildSplitPane(); 157 pane.setPreferredSize(new Dimension(100, 100)); 158 159 JPanel pnl = new JPanel(); 160 pnl.setLayout(new BorderLayout()); 161 pnl.add(pane, BorderLayout.CENTER); 162 pnl.setBorder(BorderFactory.createRaisedBevelBorder()); 163 164 getContentPane().setLayout(new BorderLayout()); 165 JTabbedPane tabbedPane = new JTabbedPane(); 166 tabbedPane.add(tr("Tags and Members"), pnl); 167 referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel, this); 168 tabbedPane.add(tr("Parent Relations"), referrerBrowser); 169 tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation)); 170 tabbedPane.addChangeListener( 171 new ChangeListener() { 172 public void stateChanged(ChangeEvent e) { 173 JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource(); 174 int index = sourceTabbedPane.getSelectedIndex(); 175 String title = sourceTabbedPane.getTitleAt(index); 176 if (title.equals(tr("Parent Relations"))) { 177 referrerBrowser.init(); 178 } 179 } 180 } 181 ); 182 183 getContentPane().add(buildToolBar(), BorderLayout.NORTH); 184 getContentPane().add(tabbedPane, BorderLayout.CENTER); 185 getContentPane().add(buildOkCancelButtonPanel(), BorderLayout.SOUTH); 186 187 setSize(findMaxDialogSize()); 188 189 addWindowListener( 190 new WindowAdapter() { 191 @Override 192 public void windowOpened(WindowEvent e) { 193 cleanSelfReferences(); 194 } 195 } 196 ); 197 198 memberTableModel.setSelectedMembers(selectedMembers); 199 HelpUtil.setHelpContext(getRootPane(),ht("/Dialog/RelationEditor")); 200 } 201 202 /** 203 * Creates the toolbar 204 * 205 * @return the toolbar 206 */ 207 protected JToolBar buildToolBar() { 208 JToolBar tb = new JToolBar(); 209 tb.setFloatable(false); 210 tb.add(new ApplyAction()); 211 tb.add(new DuplicateRelationAction()); 212 DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction(); 213 addPropertyChangeListener(deleteAction); 214 tb.add(deleteAction); 215 return tb; 216 } 217 218 /** 219 * builds the panel with the OK and the Cancel button 220 * 221 * @return the panel with the OK and the Cancel button 222 */ 223 protected JPanel buildOkCancelButtonPanel() { 224 JPanel pnl = new JPanel(); 225 pnl.setLayout(new FlowLayout(FlowLayout.CENTER)); 226 227 pnl.add(new SideButton(new OKAction())); 228 pnl.add(new SideButton(new CancelAction())); 229 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor")))); 230 return pnl; 231 } 232 233 /** 234 * builds the panel with the tag editor 235 * 236 * @return the panel with the tag editor 237 */ 238 protected JPanel buildTagEditorPanel() { 239 JPanel pnl = new JPanel(); 240 pnl.setLayout(new GridBagLayout()); 241 242 GridBagConstraints gc = new GridBagConstraints(); 243 gc.gridx = 0; 244 gc.gridy = 0; 245 gc.gridheight = 1; 246 gc.gridwidth = 1; 247 gc.fill = GridBagConstraints.HORIZONTAL; 248 gc.anchor = GridBagConstraints.FIRST_LINE_START; 249 gc.weightx = 1.0; 250 gc.weighty = 0.0; 251 pnl.add(new JLabel(tr("Tags")), gc); 252 253 gc.gridx = 0; 254 gc.gridy = 1; 255 gc.fill = GridBagConstraints.BOTH; 256 gc.anchor = GridBagConstraints.CENTER; 257 gc.weightx = 1.0; 258 gc.weighty = 1.0; 259 pnl.add(tagEditorPanel, gc); 260 return pnl; 261 } 262 263 /** 264 * builds the panel for the relation member editor 265 * 266 * @return the panel for the relation member editor 267 */ 268 protected JPanel buildMemberEditorPanel() { 269 final JPanel pnl = new JPanel(); 270 pnl.setLayout(new GridBagLayout()); 271 // setting up the member table 272 memberTable = new MemberTable(getLayer(),memberTableModel); 273 memberTable.addMouseListener(new MemberTableDblClickAdapter()); 274 memberTableModel.addMemberModelListener(memberTable); 275 276 final JScrollPane scrollPane = new JScrollPane(memberTable); 277 278 GridBagConstraints gc = new GridBagConstraints(); 279 gc.gridx = 0; 280 gc.gridy = 0; 281 gc.gridheight = 1; 282 gc.gridwidth = 3; 283 gc.fill = GridBagConstraints.HORIZONTAL; 284 gc.anchor = GridBagConstraints.FIRST_LINE_START; 285 gc.weightx = 1.0; 286 gc.weighty = 0.0; 287 pnl.add(new JLabel(tr("Members")), gc); 288 289 gc.gridx = 0; 290 gc.gridy = 1; 291 gc.gridheight = 1; 292 gc.gridwidth = 1; 293 gc.fill = GridBagConstraints.VERTICAL; 294 gc.anchor = GridBagConstraints.NORTHWEST; 295 gc.weightx = 0.0; 296 gc.weighty = 1.0; 297 pnl.add(buildLeftButtonPanel(), gc); 298 299 gc.gridx = 1; 300 gc.gridy = 1; 301 gc.fill = GridBagConstraints.BOTH; 302 gc.anchor = GridBagConstraints.CENTER; 303 gc.weightx = 0.6; 304 gc.weighty = 1.0; 305 pnl.add(scrollPane, gc); 306 307 // --- role editing 308 JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT)); 309 p3.add(new JLabel(tr("Apply Role:"))); 310 tfRole = new AutoCompletingTextField(10); 311 tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members")); 312 tfRole.addFocusListener(new FocusAdapter() { 313 @Override 314 public void focusGained(FocusEvent e) { 315 tfRole.selectAll(); 316 } 317 }); 318 tfRole.setAutoCompletionList(new AutoCompletionList()); 319 tfRole.addFocusListener( 320 new FocusAdapter() { 321 @Override 322 public void focusGained(FocusEvent e) { 323 AutoCompletionList list = tfRole.getAutoCompletionList(); 324 if (list != null) { 325 list.clear(); 326 getLayer().data.getAutoCompletionManager().populateWithMemberRoles(list); 327 } 328 } 329 } 330 ); 331 tfRole.setText(Main.pref.get("relation.editor.generic.lastrole", "")); 332 p3.add(tfRole); 333 SetRoleAction setRoleAction = new SetRoleAction(); 334 memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction); 335 tfRole.getDocument().addDocumentListener(setRoleAction); 336 tfRole.addActionListener(setRoleAction); 337 memberTableModel.getSelectionModel().addListSelectionListener( 338 new ListSelectionListener() { 339 public void valueChanged(ListSelectionEvent e) { 340 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0); 341 } 342 } 343 ); 344 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0); 345 SideButton btnApply = new SideButton(setRoleAction); 346 btnApply.setPreferredSize(new Dimension(20,20)); 347 btnApply.setText(""); 348 p3.add(btnApply); 349 350 gc.gridx = 1; 351 gc.gridy = 2; 352 gc.fill = GridBagConstraints.BOTH; 353 gc.anchor = GridBagConstraints.CENTER; 354 gc.weightx = 1.0; 355 gc.weighty = 0.0; 356 pnl.add(p3, gc); 357 358 JPanel pnl2 = new JPanel(); 359 pnl2.setLayout(new GridBagLayout()); 360 361 gc.gridx = 0; 362 gc.gridy = 0; 363 gc.gridheight = 1; 364 gc.gridwidth = 3; 365 gc.fill = GridBagConstraints.HORIZONTAL; 366 gc.anchor = GridBagConstraints.FIRST_LINE_START; 367 gc.weightx = 1.0; 368 gc.weighty = 0.0; 369 pnl2.add(new JLabel(tr("Selection")), gc); 370 371 gc.gridx = 0; 372 gc.gridy = 1; 373 gc.gridheight = 1; 374 gc.gridwidth = 1; 375 gc.fill = GridBagConstraints.VERTICAL; 376 gc.anchor = GridBagConstraints.NORTHWEST; 377 gc.weightx = 0.0; 378 gc.weighty = 1.0; 379 pnl2.add(buildSelectionControlButtonPanel(), gc); 380 381 gc.gridx = 1; 382 gc.gridy = 1; 383 gc.weightx = 1.0; 384 gc.weighty = 1.0; 385 gc.fill = GridBagConstraints.BOTH; 386 pnl2.add(buildSelectionTablePanel(), gc); 387 388 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 389 splitPane.setLeftComponent(pnl); 390 splitPane.setRightComponent(pnl2); 391 splitPane.setOneTouchExpandable(false); 392 addWindowListener(new WindowAdapter() { 393 @Override 394 public void windowOpened(WindowEvent e) { 395 // has to be called when the window is visible, otherwise 396 // no effect 397 splitPane.setDividerLocation(0.6); 398 } 399 }); 400 401 JPanel pnl3 = new JPanel(); 402 pnl3.setLayout(new BorderLayout()); 403 pnl3.add(splitPane, BorderLayout.CENTER); 404 405 new PasteMembersAction(); 406 new CopyMembersAction(); 407 new PasteTagsAction(); 408 409 return pnl3; 410 } 411 412 /** 413 * builds the panel with the table displaying the currently selected primitives 414 * 415 * @return 416 */ 417 protected JPanel buildSelectionTablePanel() { 418 JPanel pnl = new JPanel(); 419 pnl.setLayout(new BorderLayout()); 420 selectionTable = new SelectionTable(selectionTableModel, new SelectionTableColumnModel(memberTableModel)); 421 selectionTable.setMemberTableModel(memberTableModel); 422 selectionTable.setRowHeight(tfRole.getPreferredSize().height); 423 JScrollPane pane = new JScrollPane(selectionTable); 424 pnl.add(pane, BorderLayout.CENTER); 425 return pnl; 426 } 427 428 /** 429 * builds the {@link JSplitPane} which divides the editor in an upper and a lower half 430 * 431 * @return the split panel 432 */ 433 protected JSplitPane buildSplitPane() { 434 final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 435 pane.setTopComponent(buildTagEditorPanel()); 436 pane.setBottomComponent(buildMemberEditorPanel()); 437 pane.setOneTouchExpandable(true); 438 addWindowListener(new WindowAdapter() { 439 @Override 440 public void windowOpened(WindowEvent e) { 441 // has to be called when the window is visible, otherwise 442 // no effect 443 pane.setDividerLocation(0.3); 444 } 445 }); 446 return pane; 447 } 448 449 /** 450 * build the panel with the buttons on the left 451 * 452 * @return 453 */ 454 protected JToolBar buildLeftButtonPanel() { 455 JToolBar tb = new JToolBar(); 456 tb.setOrientation(JToolBar.VERTICAL); 457 tb.setFloatable(false); 458 459 // -- move up action 460 MoveUpAction moveUpAction = new MoveUpAction(); 461 memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction); 462 tb.add(moveUpAction); 463 memberTable.getActionMap().put("moveUp", moveUpAction); 464 465 // -- move down action 466 MoveDownAction moveDownAction = new MoveDownAction(); 467 memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction); 468 tb.add(moveDownAction); 469 memberTable.getActionMap().put("moveDown", moveDownAction); 470 471 tb.addSeparator(); 472 473 // -- edit action 474 EditAction editAction = new EditAction(); 475 memberTableModel.getSelectionModel().addListSelectionListener(editAction); 476 tb.add(editAction); 477 478 // -- delete action 479 RemoveAction removeSelectedAction = new RemoveAction(); 480 memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction); 481 tb.add(removeSelectedAction); 482 memberTable.getActionMap().put("removeSelected", removeSelectedAction); 483 484 tb.addSeparator(); 485 // -- sort action 486 SortAction sortAction = new SortAction(); 487 memberTableModel.addTableModelListener(sortAction); 488 tb.add(sortAction); 489 490 // -- reverse action 491 ReverseAction reverseAction = new ReverseAction(); 492 memberTableModel.addTableModelListener(reverseAction); 493 tb.add(reverseAction); 494 495 tb.addSeparator(); 496 497 // -- download action 498 DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction(); 499 memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction); 500 tb.add(downloadIncompleteMembersAction); 501 memberTable.getActionMap().put("downloadIncomplete", downloadIncompleteMembersAction); 502 503 // -- download selected action 504 DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction(); 505 memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction); 506 memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction); 507 tb.add(downloadSelectedIncompleteMembersAction); 508 509 InputMap inputMap = memberTable.getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 510 inputMap.put((KeyStroke) removeSelectedAction.getValue(AbstractAction.ACCELERATOR_KEY),"removeSelected"); 511 inputMap.put((KeyStroke) moveUpAction.getValue(AbstractAction.ACCELERATOR_KEY),"moveUp"); 512 inputMap.put((KeyStroke) moveDownAction.getValue(AbstractAction.ACCELERATOR_KEY),"moveDown"); 513 inputMap.put((KeyStroke) downloadIncompleteMembersAction.getValue(AbstractAction.ACCELERATOR_KEY),"downloadIncomplete"); 514 515 return tb; 516 } 517 518 /** 519 * build the panel with the buttons for adding or removing the current selection 520 * 521 * @return 522 */ 523 protected JToolBar buildSelectionControlButtonPanel() { 524 JToolBar tb = new JToolBar(JToolBar.VERTICAL); 525 tb.setFloatable(false); 526 527 // -- add at start action 528 AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction(); 529 selectionTableModel.addTableModelListener(addSelectionAction); 530 tb.add(addSelectionAction); 531 532 // -- add before selected action 533 AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection(); 534 selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction); 535 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction); 536 tb.add(addSelectedBeforeSelectionAction); 537 538 // -- add after selected action 539 AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection(); 540 selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction); 541 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction); 542 tb.add(addSelectedAfterSelectionAction); 543 544 // -- add at end action 545 AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction(); 546 selectionTableModel.addTableModelListener(addSelectedAtEndAction); 547 tb.add(addSelectedAtEndAction); 548 549 tb.addSeparator(); 550 551 // -- select members action 552 SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction(); 553 selectionTableModel.addTableModelListener(selectMembersForSelectionAction); 554 memberTableModel.addTableModelListener(selectMembersForSelectionAction); 555 tb.add(selectMembersForSelectionAction); 556 557 // -- select action 558 SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction(); 559 memberTable.getSelectionModel().addListSelectionListener(selectAction); 560 tb.add(selectAction); 561 562 tb.addSeparator(); 563 564 // -- remove selected action 565 RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction(); 566 selectionTableModel.addTableModelListener(removeSelectedAction); 567 tb.add(removeSelectedAction); 568 569 return tb; 570 } 571 572 @Override 573 protected Dimension findMaxDialogSize() { 574 return new Dimension(700, 650); 575 } 576 577 @Override 578 public void setVisible(boolean visible) { 579 if (visible) { 580 tagEditorPanel.initAutoCompletion(getLayer()); 581 } 582 super.setVisible(visible); 583 if (visible) { 584 RelationDialogManager.getRelationDialogManager().positionOnScreen(this); 585 if(windowMenuItem == null) { 586 addToWindowMenu(); 587 } 588 } else { 589 // make sure all registered listeners are unregistered 590 // 591 selectionTableModel.unregister(); 592 memberTableModel.unregister(); 593 memberTable.unlinkAsListener(); 594 if(windowMenuItem != null) { 595 Main.main.menu.windowMenu.remove(windowMenuItem); 596 windowMenuItem = null; 597 } 598 dispose(); 599 } 600 } 601 602 /** adds current relation editor to the windows menu (in the "volatile" group) o*/ 603 protected void addToWindowMenu() { 604 String name = getRelation() == null ? tr("New Relation") : getRelation().getLocalName(); 605 final String tt = tr("Focus Relation Editor with relation ''{0}'' in layer ''{1}''", 606 name, getLayer().getName()); 607 name = tr("Relation Editor: {0}", name == null ? getRelation().getId() : name); 608 final JMenu wm = Main.main.menu.windowMenu; 609 final JosmAction focusAction = new JosmAction(name, "dialogs/relationlist", tt, null, false, false) { 610 @Override 611 public void actionPerformed(ActionEvent e) { 612 final RelationEditor r = (RelationEditor) getValue("relationEditor"); 613 r.setVisible(true); 614 } 615 }; 616 focusAction.putValue("relationEditor", this); 617 windowMenuItem = MainMenu.add(wm, focusAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE); 618 } 619 620 /** 621 * checks whether the current relation has members referring to itself. If so, 622 * warns the users and provides an option for removing these members. 623 * 624 */ 625 protected void cleanSelfReferences() { 626 ArrayList<OsmPrimitive> toCheck = new ArrayList<OsmPrimitive>(); 627 toCheck.add(getRelation()); 628 if (memberTableModel.hasMembersReferringTo(toCheck)) { 629 int ret = ConditionalOptionPaneUtil.showOptionDialog( 630 "clean_relation_self_references", 631 Main.parent, 632 tr("<html>There is at least one member in this relation referring<br>" 633 + "to the relation itself.<br>" 634 + "This creates circular dependencies and is discouraged.<br>" 635 + "How do you want to proceed with circular dependencies?</html>"), 636 tr("Warning"), 637 JOptionPane.YES_NO_OPTION, 638 JOptionPane.WARNING_MESSAGE, 639 new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")}, 640 tr("Remove them, clean up relation") 641 ); 642 switch(ret) { 643 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return; 644 case JOptionPane.CLOSED_OPTION: return; 645 case JOptionPane.NO_OPTION: return; 646 case JOptionPane.YES_OPTION: 647 memberTableModel.removeMembersReferringTo(toCheck); 648 break; 649 } 650 } 651 } 652 653 private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut) { 654 getRootPane().getActionMap().put(actionName, action); 655 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 656 // Assign also to JTables because they have their own Copy&Paste implementation (which is disabled in this case but eats key shortcuts anyway) 657 memberTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName); 658 memberTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName); 659 memberTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 660 selectionTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName); 661 selectionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName); 662 selectionTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 663 } 664 665 protected void updateTags(List<Tag> tags) { 666 667 if (tags.isEmpty()) 668 return; 669 670 Map<String, TagModel> modelTags = new HashMap<String, TagModel>(); 671 for (int i=0; i<tagEditorPanel.getModel().getRowCount(); i++) { 672 TagModel tagModel = tagEditorPanel.getModel().get(i); 673 modelTags.put(tagModel.getName(), tagModel); 674 } 675 for (Tag tag: tags) { 676 TagModel existing = modelTags.get(tag.getKey()); 677 678 if (tag.getValue().isEmpty()) { 679 if (existing != null) { 680 tagEditorPanel.getModel().delete(tag.getKey()); 681 } 682 } else { 683 if (existing != null) { 684 tagEditorPanel.getModel().updateTagValue(existing, tag.getValue()); 685 } else { 686 tagEditorPanel.getModel().add(tag.getKey(), tag.getValue()); 687 } 688 } 689 690 } 691 } 692 693 static class AddAbortException extends Exception { 694 } 695 696 static boolean confirmAddingPrimtive(OsmPrimitive primitive) throws AddAbortException { 697 String msg = tr("<html>This relation already has one or more members referring to<br>" 698 + "the object ''{0}''<br>" 699 + "<br>" 700 + "Do you really want to add another relation member?</html>", 701 primitive.getDisplayName(DefaultNameFormatter.getInstance()) 702 ); 703 int ret = ConditionalOptionPaneUtil.showOptionDialog( 704 "add_primitive_to_relation", 705 Main.parent, 706 msg, 707 tr("Multiple members referring to same object."), 708 JOptionPane.YES_NO_CANCEL_OPTION, 709 JOptionPane.WARNING_MESSAGE, 710 null, 711 null 712 ); 713 switch(ret) { 714 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION : return true; 715 case JOptionPane.YES_OPTION: return true; 716 case JOptionPane.NO_OPTION: return false; 717 case JOptionPane.CLOSED_OPTION: return false; 718 case JOptionPane.CANCEL_OPTION: throw new AddAbortException(); 719 } 720 // should not happen 721 return false; 722 } 723 724 static void warnOfCircularReferences(OsmPrimitive primitive) { 725 String msg = tr("<html>You are trying to add a relation to itself.<br>" 726 + "<br>" 727 + "This creates circular references and is therefore discouraged.<br>" 728 + "Skipping relation ''{0}''.</html>", 729 primitive.getDisplayName(DefaultNameFormatter.getInstance())); 730 JOptionPane.showMessageDialog( 731 Main.parent, 732 msg, 733 tr("Warning"), 734 JOptionPane.WARNING_MESSAGE); 735 } 736 737 public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) { 738 try { 739 Relation relation = new Relation(orig); 740 boolean modified = false; 741 for (OsmPrimitive p : primitivesToAdd) { 742 if (p instanceof Relation && orig != null && orig.equals(p)) { 743 warnOfCircularReferences(p); 744 continue; 745 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p)) 746 && !confirmAddingPrimtive(p)) { 747 continue; 748 } 749 relation.addMember(new RelationMember("", p)); 750 modified = true; 751 } 752 return modified ? new ChangeCommand(orig, relation) : null; 753 } catch (AddAbortException ign) { 754 return null; 755 } 756 } 757 758 abstract class AddFromSelectionAction extends AbstractAction { 759 protected boolean isPotentialDuplicate(OsmPrimitive primitive) { 760 return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive)); 761 } 762 763 protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException { 764 if (primitives == null || primitives.isEmpty()) 765 return primitives; 766 ArrayList<OsmPrimitive> ret = new ArrayList<OsmPrimitive>(); 767 Iterator<OsmPrimitive> it = primitives.iterator(); 768 while(it.hasNext()) { 769 OsmPrimitive primitive = it.next(); 770 if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) { 771 warnOfCircularReferences(primitive); 772 continue; 773 } 774 if (isPotentialDuplicate(primitive)) { 775 if (confirmAddingPrimtive(primitive)) { 776 ret.add(primitive); 777 } 778 continue; 779 } else { 780 ret.add(primitive); 781 } 782 } 783 return ret; 784 } 785 } 786 787 class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener { 788 public AddSelectedAtStartAction() { 789 putValue(SHORT_DESCRIPTION, 790 tr("Add all objects selected in the current dataset before the first member")); 791 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright")); 792 // putValue(NAME, tr("Add Selected")); 793 refreshEnabled(); 794 } 795 796 protected void refreshEnabled() { 797 setEnabled(selectionTableModel.getRowCount() > 0); 798 } 799 800 public void actionPerformed(ActionEvent e) { 801 try { 802 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection()); 803 memberTableModel.addMembersAtBeginning(toAdd); 804 } catch(AddAbortException ex) { 805 // do nothing 806 } 807 } 808 809 public void tableChanged(TableModelEvent e) { 810 refreshEnabled(); 811 } 812 } 813 814 class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener { 815 public AddSelectedAtEndAction() { 816 putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member")); 817 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright")); 818 // putValue(NAME, tr("Add Selected")); 819 refreshEnabled(); 820 } 821 822 protected void refreshEnabled() { 823 setEnabled(selectionTableModel.getRowCount() > 0); 824 } 825 826 public void actionPerformed(ActionEvent e) { 827 try { 828 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection()); 829 memberTableModel.addMembersAtEnd(toAdd); 830 } catch(AddAbortException ex) { 831 // do nothing 832 } 833 } 834 835 public void tableChanged(TableModelEvent e) { 836 refreshEnabled(); 837 } 838 } 839 840 class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener { 841 public AddSelectedBeforeSelection() { 842 putValue(SHORT_DESCRIPTION, 843 tr("Add all objects selected in the current dataset before the first selected member")); 844 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright")); 845 // putValue(NAME, tr("Add Selected")); 846 refreshEnabled(); 847 } 848 849 protected void refreshEnabled() { 850 setEnabled(selectionTableModel.getRowCount() > 0 851 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0); 852 } 853 854 public void actionPerformed(ActionEvent e) { 855 try { 856 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection()); 857 memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel 858 .getSelectionModel().getMinSelectionIndex()); 859 } catch(AddAbortException ex) { 860 // do nothing 861 } 862 863 } 864 865 public void tableChanged(TableModelEvent e) { 866 refreshEnabled(); 867 } 868 869 public void valueChanged(ListSelectionEvent e) { 870 refreshEnabled(); 871 } 872 } 873 874 class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener { 875 public AddSelectedAfterSelection() { 876 putValue(SHORT_DESCRIPTION, 877 tr("Add all objects selected in the current dataset after the last selected member")); 878 putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright")); 879 // putValue(NAME, tr("Add Selected")); 880 refreshEnabled(); 881 } 882 883 protected void refreshEnabled() { 884 setEnabled(selectionTableModel.getRowCount() > 0 885 && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0); 886 } 887 888 public void actionPerformed(ActionEvent e) { 889 try { 890 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection()); 891 memberTableModel.addMembersAfterIdx(toAdd, memberTableModel 892 .getSelectionModel().getMaxSelectionIndex()); 893 } catch(AddAbortException ex) { 894 // do nothing 895 } 896 } 897 898 public void tableChanged(TableModelEvent e) { 899 refreshEnabled(); 900 } 901 902 public void valueChanged(ListSelectionEvent e) { 903 refreshEnabled(); 904 } 905 } 906 907 class RemoveSelectedAction extends AbstractAction implements TableModelListener { 908 public RemoveSelectedAction() { 909 putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects")); 910 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers")); 911 // putValue(NAME, tr("Remove Selected")); 912 updateEnabledState(); 913 } 914 915 protected void updateEnabledState() { 916 DataSet ds = getLayer().data; 917 if (ds == null || ds.getSelected().isEmpty()) { 918 setEnabled(false); 919 return; 920 } 921 // only enable the action if we have members referring to the 922 // selected primitives 923 // 924 setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected())); 925 } 926 927 public void actionPerformed(ActionEvent e) { 928 memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection()); 929 } 930 931 public void tableChanged(TableModelEvent e) { 932 updateEnabledState(); 933 } 934 } 935 936 /** 937 * Selects members in the relation editor which refer to primitives in the current 938 * selection of the context layer. 939 * 940 */ 941 class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener { 942 public SelectedMembersForSelectionAction() { 943 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection")); 944 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers")); 945 updateEnabledState(); 946 } 947 948 protected void updateEnabledState() { 949 boolean enabled = selectionTableModel.getRowCount() > 0 950 && !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty(); 951 952 if (enabled) { 953 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size())); 954 } else { 955 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection")); 956 } 957 setEnabled(enabled); 958 } 959 960 public void actionPerformed(ActionEvent e) { 961 memberTableModel.selectMembersReferringTo(getLayer().data.getSelected()); 962 } 963 964 public void tableChanged(TableModelEvent e) { 965 updateEnabledState(); 966 967 } 968 } 969 970 /** 971 * Selects primitives in the layer this editor belongs to. The selected primitives are 972 * equal to the set of primitives the currently selected relation members refer to. 973 * 974 */ 975 class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener { 976 public SelectPrimitivesForSelectedMembersAction() { 977 putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members")); 978 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives")); 979 updateEnabledState(); 980 } 981 982 protected void updateEnabledState() { 983 setEnabled(memberTable.getSelectedRowCount() > 0); 984 } 985 986 public void actionPerformed(ActionEvent e) { 987 getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives()); 988 } 989 990 public void valueChanged(ListSelectionEvent e) { 991 updateEnabledState(); 992 } 993 } 994 995 class SortAction extends AbstractAction implements TableModelListener { 996 public SortAction() { 997 String tooltip = tr("Sort the relation members"); 998 putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort")); 999 putValue(NAME, tr("Sort")); 1000 Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"), 1001 KeyEvent.VK_END, Shortcut.ALT); 1002 sc.setAccelerator(this); 1003 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 1004 updateEnabledState(); 1005 } 1006 1007 public void actionPerformed(ActionEvent e) { 1008 memberTableModel.sort(); 1009 } 1010 1011 protected void updateEnabledState() { 1012 setEnabled(memberTableModel.getRowCount() > 0); 1013 } 1014 1015 public void tableChanged(TableModelEvent e) { 1016 updateEnabledState(); 1017 } 1018 } 1019 1020 class ReverseAction extends AbstractAction implements TableModelListener { 1021 public ReverseAction() { 1022 putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members")); 1023 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse")); 1024 putValue(NAME, tr("Reverse")); 1025 // Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"), 1026 // KeyEvent.VK_END, Shortcut.ALT) 1027 updateEnabledState(); 1028 } 1029 1030 public void actionPerformed(ActionEvent e) { 1031 memberTableModel.reverse(); 1032 } 1033 1034 protected void updateEnabledState() { 1035 setEnabled(memberTableModel.getRowCount() > 0); 1036 } 1037 1038 public void tableChanged(TableModelEvent e) { 1039 updateEnabledState(); 1040 } 1041 } 1042 1043 class MoveUpAction extends AbstractAction implements ListSelectionListener { 1044 public MoveUpAction() { 1045 String tooltip = tr("Move the currently selected members up"); 1046 putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup")); 1047 // putValue(NAME, tr("Move Up")); 1048 Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"), 1049 KeyEvent.VK_UP, Shortcut.ALT); 1050 sc.setAccelerator(this); 1051 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 1052 setEnabled(false); 1053 } 1054 1055 public void actionPerformed(ActionEvent e) { 1056 memberTableModel.moveUp(memberTable.getSelectedRows()); 1057 } 1058 1059 public void valueChanged(ListSelectionEvent e) { 1060 setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows())); 1061 } 1062 } 1063 1064 class MoveDownAction extends AbstractAction implements ListSelectionListener { 1065 public MoveDownAction() { 1066 String tooltip = tr("Move the currently selected members down"); 1067 putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown")); 1068 // putValue(NAME, tr("Move Down")); 1069 Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"), 1070 KeyEvent.VK_DOWN, Shortcut.ALT); 1071 sc.setAccelerator(this); 1072 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 1073 setEnabled(false); 1074 } 1075 1076 public void actionPerformed(ActionEvent e) { 1077 memberTableModel.moveDown(memberTable.getSelectedRows()); 1078 } 1079 1080 public void valueChanged(ListSelectionEvent e) { 1081 setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows())); 1082 } 1083 } 1084 1085 class RemoveAction extends AbstractAction implements ListSelectionListener { 1086 public RemoveAction() { 1087 String tooltip = tr("Remove the currently selected members from this relation"); 1088 putValue(SMALL_ICON, ImageProvider.get("dialogs", "remove")); 1089 putValue(NAME, tr("Remove")); 1090 Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"), 1091 KeyEvent.VK_DELETE, Shortcut.ALT); 1092 sc.setAccelerator(this); 1093 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 1094 setEnabled(false); 1095 } 1096 1097 public void actionPerformed(ActionEvent e) { 1098 memberTableModel.remove(memberTable.getSelectedRows()); 1099 } 1100 1101 public void valueChanged(ListSelectionEvent e) { 1102 setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows())); 1103 } 1104 } 1105 1106 class DeleteCurrentRelationAction extends AbstractAction implements PropertyChangeListener{ 1107 public DeleteCurrentRelationAction() { 1108 putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation")); 1109 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 1110 putValue(NAME, tr("Delete")); 1111 updateEnabledState(); 1112 } 1113 1114 public void run() { 1115 Relation toDelete = getRelation(); 1116 if (toDelete == null) 1117 return; 1118 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation( 1119 getLayer(), 1120 toDelete 1121 ); 1122 } 1123 1124 public void actionPerformed(ActionEvent e) { 1125 run(); 1126 } 1127 1128 protected void updateEnabledState() { 1129 setEnabled(getRelationSnapshot() != null); 1130 } 1131 1132 public void propertyChange(PropertyChangeEvent evt) { 1133 if (evt.getPropertyName().equals(RELATION_SNAPSHOT_PROP)) { 1134 updateEnabledState(); 1135 } 1136 } 1137 } 1138 1139 abstract class SavingAction extends AbstractAction { 1140 /** 1141 * apply updates to a new relation 1142 */ 1143 protected void applyNewRelation() { 1144 final Relation newRelation = new Relation(); 1145 tagEditorPanel.getModel().applyToPrimitive(newRelation); 1146 memberTableModel.applyToRelation(newRelation); 1147 List<RelationMember> newMembers = new ArrayList<RelationMember>(); 1148 for (RelationMember rm: newRelation.getMembers()) { 1149 if (!rm.getMember().isDeleted()) { 1150 newMembers.add(rm); 1151 } 1152 } 1153 if (newRelation.getMembersCount() != newMembers.size()) { 1154 newRelation.setMembers(newMembers); 1155 String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" + 1156 "was open. They have been removed from the relation members list."); 1157 JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE); 1158 } 1159 // If the user wanted to create a new relation, but hasn't added any members or 1160 // tags, don't add an empty relation 1161 if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys()) 1162 return; 1163 Main.main.undoRedo.add(new AddCommand(getLayer(),newRelation)); 1164 1165 // make sure everybody is notified about the changes 1166 // 1167 getLayer().data.fireSelectionChanged(); 1168 GenericRelationEditor.this.setRelation(newRelation); 1169 RelationDialogManager.getRelationDialogManager().updateContext( 1170 getLayer(), 1171 getRelation(), 1172 GenericRelationEditor.this 1173 ); 1174 SwingUtilities.invokeLater(new Runnable() { 1175 @Override 1176 public void run() { 1177 // Relation list gets update in EDT so selecting my be postponed to following EDT run 1178 Main.map.relationListDialog.selectRelation(newRelation); 1179 } 1180 }); 1181 } 1182 1183 /** 1184 * Apply the updates for an existing relation which has been changed 1185 * outside of the relation editor. 1186 * 1187 */ 1188 protected void applyExistingConflictingRelation() { 1189 Relation editedRelation = new Relation(getRelation()); 1190 tagEditorPanel.getModel().applyToPrimitive(editedRelation); 1191 memberTableModel.applyToRelation(editedRelation); 1192 Conflict<Relation> conflict = new Conflict<Relation>(getRelation(), editedRelation); 1193 Main.main.undoRedo.add(new ConflictAddCommand(getLayer(),conflict)); 1194 } 1195 1196 /** 1197 * Apply the updates for an existing relation which has not been changed 1198 * outside of the relation editor. 1199 * 1200 */ 1201 protected void applyExistingNonConflictingRelation() { 1202 Relation editedRelation = new Relation(getRelation()); 1203 tagEditorPanel.getModel().applyToPrimitive(editedRelation); 1204 memberTableModel.applyToRelation(editedRelation); 1205 Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation)); 1206 getLayer().data.fireSelectionChanged(); 1207 // this will refresh the snapshot and update the dialog title 1208 // 1209 setRelation(getRelation()); 1210 } 1211 1212 protected boolean confirmClosingBecauseOfDirtyState() { 1213 ButtonSpec [] options = new ButtonSpec[] { 1214 new ButtonSpec( 1215 tr("Yes, create a conflict and close"), 1216 ImageProvider.get("ok"), 1217 tr("Click to create a conflict and close this relation editor") , 1218 null /* no specific help topic */ 1219 ), 1220 new ButtonSpec( 1221 tr("No, continue editing"), 1222 ImageProvider.get("cancel"), 1223 tr("Click to return to the relation editor and to resume relation editing") , 1224 null /* no specific help topic */ 1225 ) 1226 }; 1227 1228 int ret = HelpAwareOptionPane.showOptionDialog( 1229 Main.parent, 1230 tr("<html>This relation has been changed outside of the editor.<br>" 1231 + "You cannot apply your changes and continue editing.<br>" 1232 + "<br>" 1233 + "Do you want to create a conflict and close the editor?</html>"), 1234 tr("Conflict in data"), 1235 JOptionPane.WARNING_MESSAGE, 1236 null, 1237 options, 1238 options[0], // OK is default 1239 "/Dialog/RelationEditor#RelationChangedOutsideOfEditor" 1240 ); 1241 return ret == 0; 1242 } 1243 1244 protected void warnDoubleConflict() { 1245 JOptionPane.showMessageDialog( 1246 Main.parent, 1247 tr("<html>Layer ''{0}'' already has a conflict for object<br>" 1248 + "''{1}''.<br>" 1249 + "Please resolve this conflict first, then try again.</html>", 1250 getLayer().getName(), 1251 getRelation().getDisplayName(DefaultNameFormatter.getInstance()) 1252 ), 1253 tr("Double conflict"), 1254 JOptionPane.WARNING_MESSAGE 1255 ); 1256 } 1257 } 1258 1259 class ApplyAction extends SavingAction { 1260 public ApplyAction() { 1261 putValue(SHORT_DESCRIPTION, tr("Apply the current updates")); 1262 putValue(SMALL_ICON, ImageProvider.get("save")); 1263 putValue(NAME, tr("Apply")); 1264 setEnabled(true); 1265 } 1266 1267 public void run() { 1268 if (getRelation() == null) { 1269 applyNewRelation(); 1270 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot()) 1271 || tagEditorPanel.getModel().isDirty()) { 1272 if (isDirtyRelation()) { 1273 if (confirmClosingBecauseOfDirtyState()) { 1274 if (getLayer().getConflicts().hasConflictForMy(getRelation())) { 1275 warnDoubleConflict(); 1276 return; 1277 } 1278 applyExistingConflictingRelation(); 1279 setVisible(false); 1280 } 1281 } else { 1282 applyExistingNonConflictingRelation(); 1283 } 1284 } 1285 } 1286 1287 public void actionPerformed(ActionEvent e) { 1288 run(); 1289 } 1290 } 1291 1292 class OKAction extends SavingAction { 1293 public OKAction() { 1294 putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog")); 1295 putValue(SMALL_ICON, ImageProvider.get("ok")); 1296 putValue(NAME, tr("OK")); 1297 setEnabled(true); 1298 } 1299 1300 public void run() { 1301 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText()); 1302 if (getRelation() == null) { 1303 applyNewRelation(); 1304 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot()) 1305 || tagEditorPanel.getModel().isDirty()) { 1306 if (isDirtyRelation()) { 1307 if (confirmClosingBecauseOfDirtyState()) { 1308 if (getLayer().getConflicts().hasConflictForMy(getRelation())) { 1309 warnDoubleConflict(); 1310 return; 1311 } 1312 applyExistingConflictingRelation(); 1313 } else 1314 return; 1315 } else { 1316 applyExistingNonConflictingRelation(); 1317 } 1318 } 1319 setVisible(false); 1320 } 1321 1322 public void actionPerformed(ActionEvent e) { 1323 run(); 1324 } 1325 } 1326 1327 class CancelAction extends SavingAction { 1328 public CancelAction() { 1329 putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog")); 1330 putValue(SMALL_ICON, ImageProvider.get("cancel")); 1331 putValue(NAME, tr("Cancel")); 1332 1333 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) 1334 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE"); 1335 getRootPane().getActionMap().put("ESCAPE", this); 1336 setEnabled(true); 1337 } 1338 1339 public void actionPerformed(ActionEvent e) { 1340 if (!memberTableModel.hasSameMembersAs(getRelationSnapshot()) || tagEditorPanel.getModel().isDirty()) { 1341 //give the user a chance to save the changes 1342 int ret = confirmClosingByCancel(); 1343 if (ret == 0) { //Yes, save the changes 1344 //copied from OKAction.run() 1345 Main.pref.put("relation.editor.generic.lastrole", tfRole.getText()); 1346 if (getRelation() == null) { 1347 applyNewRelation(); 1348 } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot()) 1349 || tagEditorPanel.getModel().isDirty()) { 1350 if (isDirtyRelation()) { 1351 if (confirmClosingBecauseOfDirtyState()) { 1352 if (getLayer().getConflicts().hasConflictForMy(getRelation())) { 1353 warnDoubleConflict(); 1354 return; 1355 } 1356 applyExistingConflictingRelation(); 1357 } else 1358 return; 1359 } else { 1360 applyExistingNonConflictingRelation(); 1361 } 1362 } 1363 } 1364 else if (ret == 2) //Cancel, continue editing 1365 return; 1366 //in case of "No, discard", there is no extra action to be performed here. 1367 } 1368 setVisible(false); 1369 } 1370 1371 protected int confirmClosingByCancel() { 1372 ButtonSpec [] options = new ButtonSpec[] { 1373 new ButtonSpec( 1374 tr("Yes, save the changes and close"), 1375 ImageProvider.get("ok"), 1376 tr("Click to save the changes and close this relation editor") , 1377 null /* no specific help topic */ 1378 ), 1379 new ButtonSpec( 1380 tr("No, discard the changes and close"), 1381 ImageProvider.get("cancel"), 1382 tr("Click to discard the changes and close this relation editor") , 1383 null /* no specific help topic */ 1384 ), 1385 new ButtonSpec( 1386 tr("Cancel, continue editing"), 1387 ImageProvider.get("cancel"), 1388 tr("Click to return to the relation editor and to resume relation editing") , 1389 null /* no specific help topic */ 1390 ) 1391 }; 1392 1393 int ret = HelpAwareOptionPane.showOptionDialog( 1394 Main.parent, 1395 tr("<html>The relation has been changed.<br>" 1396 + "<br>" 1397 + "Do you want to save your changes?</html>"), 1398 tr("Unsaved changes"), 1399 JOptionPane.WARNING_MESSAGE, 1400 null, 1401 options, 1402 options[0], // OK is default, 1403 "/Dialog/RelationEditor#DiscardChanges" 1404 ); 1405 return ret; 1406 } 1407 } 1408 1409 class AddTagAction extends AbstractAction { 1410 public AddTagAction() { 1411 putValue(SHORT_DESCRIPTION, tr("Add an empty tag")); 1412 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add")); 1413 // putValue(NAME, tr("Cancel")); 1414 setEnabled(true); 1415 } 1416 1417 public void actionPerformed(ActionEvent e) { 1418 tagEditorPanel.getModel().appendNewTag(); 1419 } 1420 } 1421 1422 class DownloadIncompleteMembersAction extends AbstractAction implements TableModelListener { 1423 public DownloadIncompleteMembersAction() { 1424 String tooltip = tr("Download all incomplete members"); 1425 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete")); 1426 putValue(NAME, tr("Download Members")); 1427 Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"), 1428 KeyEvent.VK_HOME, Shortcut.ALT); 1429 sc.setAccelerator(this); 1430 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 1431 updateEnabledState(); 1432 } 1433 1434 public void actionPerformed(ActionEvent e) { 1435 if (!isEnabled()) 1436 return; 1437 Main.worker.submit(new DownloadRelationMemberTask( 1438 getRelation(), 1439 memberTableModel.getIncompleteMemberPrimitives(), 1440 getLayer(), 1441 GenericRelationEditor.this) 1442 ); 1443 } 1444 1445 protected void updateEnabledState() { 1446 setEnabled(memberTableModel.hasIncompleteMembers()); 1447 } 1448 1449 public void tableChanged(TableModelEvent e) { 1450 updateEnabledState(); 1451 } 1452 } 1453 1454 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener, TableModelListener{ 1455 public DownloadSelectedIncompleteMembersAction() { 1456 putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members")); 1457 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected")); 1458 putValue(NAME, tr("Download Members")); 1459 // Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"), 1460 // KeyEvent.VK_K, Shortcut.ALT) 1461 updateEnabledState(); 1462 } 1463 1464 public void actionPerformed(ActionEvent e) { 1465 if (!isEnabled()) 1466 return; 1467 Main.worker.submit(new DownloadRelationMemberTask( 1468 getRelation(), 1469 memberTableModel.getSelectedIncompleteMemberPrimitives(), 1470 getLayer(), 1471 GenericRelationEditor.this) 1472 ); 1473 } 1474 1475 protected void updateEnabledState() { 1476 setEnabled(memberTableModel.hasIncompleteSelectedMembers()); 1477 } 1478 1479 public void valueChanged(ListSelectionEvent e) { 1480 updateEnabledState(); 1481 } 1482 1483 public void tableChanged(TableModelEvent e) { 1484 updateEnabledState(); 1485 } 1486 } 1487 1488 class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener { 1489 public SetRoleAction() { 1490 putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members")); 1491 putValue(SMALL_ICON, ImageProvider.get("apply")); 1492 putValue(NAME, tr("Apply Role")); 1493 refreshEnabled(); 1494 } 1495 1496 protected void refreshEnabled() { 1497 setEnabled(memberTable.getSelectedRowCount() > 0); 1498 } 1499 1500 protected boolean isEmptyRole() { 1501 return tfRole.getText() == null || tfRole.getText().trim().equals(""); 1502 } 1503 1504 protected boolean confirmSettingEmptyRole(int onNumMembers) { 1505 String message = "<html>" 1506 + trn("You are setting an empty role on {0} object.", 1507 "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers) 1508 + "<br>" 1509 + tr("This is equal to deleting the roles of these objects.") + 1510 "<br>" 1511 + tr("Do you really want to apply the new role?") + "</html>"; 1512 String [] options = new String[] { 1513 tr("Yes, apply it"), 1514 tr("No, do not apply") 1515 }; 1516 int ret = ConditionalOptionPaneUtil.showOptionDialog( 1517 "relation_editor.confirm_applying_empty_role", 1518 Main.parent, 1519 message, 1520 tr("Confirm empty role"), 1521 JOptionPane.YES_NO_OPTION, 1522 JOptionPane.WARNING_MESSAGE, 1523 options, 1524 options[0] 1525 ); 1526 switch(ret) { 1527 case JOptionPane.YES_OPTION: return true; 1528 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return true; 1529 default: 1530 return false; 1531 } 1532 } 1533 1534 public void actionPerformed(ActionEvent e) { 1535 if (isEmptyRole()) { 1536 if (! confirmSettingEmptyRole(memberTable.getSelectedRowCount())) 1537 return; 1538 } 1539 memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText()); 1540 } 1541 1542 public void valueChanged(ListSelectionEvent e) { 1543 refreshEnabled(); 1544 } 1545 1546 public void changedUpdate(DocumentEvent e) { 1547 refreshEnabled(); 1548 } 1549 1550 public void insertUpdate(DocumentEvent e) { 1551 refreshEnabled(); 1552 } 1553 1554 public void removeUpdate(DocumentEvent e) { 1555 refreshEnabled(); 1556 } 1557 } 1558 1559 /** 1560 * Creates a new relation with a copy of the current editor state 1561 * 1562 */ 1563 class DuplicateRelationAction extends AbstractAction { 1564 public DuplicateRelationAction() { 1565 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window")); 1566 // FIXME provide an icon 1567 putValue(SMALL_ICON, ImageProvider.get("duplicate")); 1568 putValue(NAME, tr("Duplicate")); 1569 setEnabled(true); 1570 } 1571 1572 public void actionPerformed(ActionEvent e) { 1573 Relation copy = new Relation(); 1574 tagEditorPanel.getModel().applyToPrimitive(copy); 1575 memberTableModel.applyToRelation(copy); 1576 RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers()); 1577 editor.setVisible(true); 1578 } 1579 } 1580 1581 /** 1582 * Action for editing the currently selected relation 1583 * 1584 * 1585 */ 1586 class EditAction extends AbstractAction implements ListSelectionListener { 1587 public EditAction() { 1588 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to")); 1589 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit")); 1590 //putValue(NAME, tr("Edit")); 1591 refreshEnabled(); 1592 } 1593 1594 protected void refreshEnabled() { 1595 setEnabled(memberTable.getSelectedRowCount() == 1 1596 && memberTableModel.isEditableRelation(memberTable.getSelectedRow())); 1597 } 1598 1599 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) { 1600 Collection<RelationMember> members = new HashSet<RelationMember>(); 1601 Collection<OsmPrimitive> selection = getLayer().data.getSelected(); 1602 for (RelationMember member: r.getMembers()) { 1603 if (selection.contains(member.getMember())) { 1604 members.add(member); 1605 } 1606 } 1607 return members; 1608 } 1609 1610 public void run() { 1611 int idx = memberTable.getSelectedRow(); 1612 if (idx < 0) 1613 return; 1614 OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx); 1615 if (!(primitive instanceof Relation)) 1616 return; 1617 Relation r = (Relation) primitive; 1618 if (r.isIncomplete()) 1619 return; 1620 1621 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r)); 1622 editor.setVisible(true); 1623 } 1624 1625 public void actionPerformed(ActionEvent e) { 1626 if (!isEnabled()) 1627 return; 1628 run(); 1629 } 1630 1631 public void valueChanged(ListSelectionEvent e) { 1632 refreshEnabled(); 1633 } 1634 } 1635 1636 class PasteMembersAction extends AddFromSelectionAction { 1637 1638 public PasteMembersAction() { 1639 registerCopyPasteAction(this, "PASTE_MEMBERS", Shortcut.getPasteKeyStroke()); 1640 } 1641 1642 @Override 1643 public void actionPerformed(ActionEvent e) { 1644 try { 1645 List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded(); 1646 DataSet ds = getLayer().data; 1647 List<OsmPrimitive> toAdd = new ArrayList<OsmPrimitive>(); 1648 boolean hasNewInOtherLayer = false; 1649 1650 for (PrimitiveData primitive: primitives) { 1651 OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive); 1652 if (primitiveInDs != null) { 1653 toAdd.add(primitiveInDs); 1654 } else if (!primitive.isNew()) { 1655 OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true); 1656 ds.addPrimitive(p); 1657 toAdd.add(p); 1658 } else { 1659 hasNewInOtherLayer = true; 1660 break; 1661 } 1662 } 1663 1664 if (hasNewInOtherLayer) { 1665 JOptionPane.showMessageDialog(Main.parent, tr("Members from paste buffer cannot be added because they are not included in current layer")); 1666 return; 1667 } 1668 1669 toAdd = filterConfirmedPrimitives(toAdd); 1670 int index = memberTableModel.getSelectionModel().getMaxSelectionIndex(); 1671 if (index == -1) { 1672 index = memberTableModel.getRowCount() - 1; 1673 } 1674 memberTableModel.addMembersAfterIdx(toAdd, index); 1675 1676 tfRole.requestFocusInWindow(); 1677 1678 } catch (AddAbortException ex) { 1679 // Do nothing 1680 } 1681 } 1682 } 1683 1684 class CopyMembersAction extends AbstractAction { 1685 1686 public CopyMembersAction() { 1687 registerCopyPasteAction(this, "COPY_MEMBERS", Shortcut.getCopyKeyStroke()); 1688 } 1689 1690 @Override 1691 public void actionPerformed(ActionEvent e) { 1692 Set<OsmPrimitive> primitives = new HashSet<OsmPrimitive>(); 1693 for (RelationMember rm: memberTableModel.getSelectedMembers()) { 1694 primitives.add(rm.getMember()); 1695 } 1696 if (!primitives.isEmpty()) { 1697 CopyAction.copy(getLayer(), primitives); 1698 } 1699 } 1700 1701 } 1702 1703 class PasteTagsAction extends AbstractAction { 1704 1705 public PasteTagsAction() { 1706 registerCopyPasteAction(this, "PASTE_TAGS", Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke()); 1707 } 1708 1709 @Override 1710 public void actionPerformed(ActionEvent e) { 1711 Relation relation = new Relation(); 1712 tagEditorPanel.getModel().applyToPrimitive(relation); 1713 TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), Collections.<OsmPrimitive>singletonList(relation)); 1714 updateTags(tagPaster.execute()); 1715 } 1716 1717 } 1718 1719 class MemberTableDblClickAdapter extends MouseAdapter { 1720 @Override 1721 public void mouseClicked(MouseEvent e) { 1722 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { 1723 new EditAction().run(); 1724 } 1725 } 1726 } 1727 }