001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.Dimension; 009import java.awt.FlowLayout; 010import java.awt.GraphicsEnvironment; 011import java.awt.GridBagConstraints; 012import java.awt.GridBagLayout; 013import java.awt.event.ActionEvent; 014import java.awt.event.FocusAdapter; 015import java.awt.event.FocusEvent; 016import java.awt.event.InputEvent; 017import java.awt.event.KeyEvent; 018import java.awt.event.MouseAdapter; 019import java.awt.event.MouseEvent; 020import java.awt.event.WindowAdapter; 021import java.awt.event.WindowEvent; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.EnumSet; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029 030import javax.swing.AbstractAction; 031import javax.swing.BorderFactory; 032import javax.swing.InputMap; 033import javax.swing.JButton; 034import javax.swing.JComponent; 035import javax.swing.JLabel; 036import javax.swing.JMenu; 037import javax.swing.JMenuItem; 038import javax.swing.JOptionPane; 039import javax.swing.JPanel; 040import javax.swing.JScrollPane; 041import javax.swing.JSplitPane; 042import javax.swing.JTabbedPane; 043import javax.swing.JToolBar; 044import javax.swing.KeyStroke; 045import javax.swing.event.ChangeEvent; 046import javax.swing.event.ChangeListener; 047import javax.swing.event.ListSelectionEvent; 048import javax.swing.event.ListSelectionListener; 049 050import org.openstreetmap.josm.Main; 051import org.openstreetmap.josm.actions.ExpertToggleAction; 052import org.openstreetmap.josm.actions.JosmAction; 053import org.openstreetmap.josm.command.ChangeCommand; 054import org.openstreetmap.josm.command.Command; 055import org.openstreetmap.josm.data.osm.OsmPrimitive; 056import org.openstreetmap.josm.data.osm.Relation; 057import org.openstreetmap.josm.data.osm.RelationMember; 058import org.openstreetmap.josm.data.osm.Tag; 059import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 060import org.openstreetmap.josm.gui.DefaultNameFormatter; 061import org.openstreetmap.josm.gui.MainMenu; 062import org.openstreetmap.josm.gui.SideButton; 063import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection; 064import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction; 065import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction; 066import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedBeforeSelection; 067import org.openstreetmap.josm.gui.dialogs.relation.actions.ApplyAction; 068import org.openstreetmap.josm.gui.dialogs.relation.actions.CancelAction; 069import org.openstreetmap.josm.gui.dialogs.relation.actions.CopyMembersAction; 070import org.openstreetmap.josm.gui.dialogs.relation.actions.DeleteCurrentRelationAction; 071import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadIncompleteMembersAction; 072import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadSelectedIncompleteMembersAction; 073import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction; 074import org.openstreetmap.josm.gui.dialogs.relation.actions.EditAction; 075import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveDownAction; 076import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveUpAction; 077import org.openstreetmap.josm.gui.dialogs.relation.actions.OKAction; 078import org.openstreetmap.josm.gui.dialogs.relation.actions.PasteMembersAction; 079import org.openstreetmap.josm.gui.dialogs.relation.actions.RefreshAction; 080import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction; 081import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction; 082import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction; 083import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction; 084import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectedMembersForSelectionAction; 085import org.openstreetmap.josm.gui.dialogs.relation.actions.SetRoleAction; 086import org.openstreetmap.josm.gui.dialogs.relation.actions.SortAction; 087import org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction; 088import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 089import org.openstreetmap.josm.gui.help.HelpUtil; 090import org.openstreetmap.josm.gui.layer.OsmDataLayer; 091import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 092import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 093import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 094import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 095import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 096import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 097import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 098import org.openstreetmap.josm.tools.CheckParameterUtil; 099import org.openstreetmap.josm.tools.Shortcut; 100import org.openstreetmap.josm.tools.WindowGeometry; 101 102/** 103 * This dialog is for editing relations. 104 * @since 343 105 */ 106public class GenericRelationEditor extends RelationEditor { 107 /** the tag table and its model */ 108 private final TagEditorPanel tagEditorPanel; 109 private final ReferringRelationsBrowser referrerBrowser; 110 private final ReferringRelationsBrowserModel referrerModel; 111 112 /** the member table */ 113 private MemberTable memberTable; 114 private final MemberTableModel memberTableModel; 115 116 /** the model for the selection table */ 117 private SelectionTable selectionTable; 118 private final SelectionTableModel selectionTableModel; 119 120 private AutoCompletingTextField tfRole; 121 122 /** the menu item in the windows menu. Required to properly 123 * hide on dialog close. 124 */ 125 private JMenuItem windowMenuItem; 126 /** 127 * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction}. 128 */ 129 private JButton sortBelowButton; 130 /** 131 * Action for performing the {@link RefreshAction} 132 */ 133 private RefreshAction refreshAction; 134 /** 135 * Action for performing the {@link ApplyAction} 136 */ 137 private ApplyAction applyAction; 138 /** 139 * Action for performing the {@link CancelAction} 140 */ 141 private CancelAction cancelAction; 142 143 /** 144 * Creates a new relation editor for the given relation. The relation will be saved if the user 145 * selects "ok" in the editor. 146 * 147 * If no relation is given, will create an editor for a new relation. 148 * 149 * @param layer the {@link OsmDataLayer} the new or edited relation belongs to 150 * @param relation relation to edit, or null to create a new one. 151 * @param selectedMembers a collection of members which shall be selected initially 152 */ 153 public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) { 154 super(layer, relation); 155 156 setRememberWindowGeometry(getClass().getName() + ".geometry", 157 WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650))); 158 159 final TaggingPresetHandler presetHandler = new TaggingPresetHandler() { 160 161 @Override 162 public void updateTags(List<Tag> tags) { 163 tagEditorPanel.getModel().updateTags(tags); 164 } 165 166 @Override 167 public Collection<OsmPrimitive> getSelection() { 168 Relation relation = new Relation(); 169 tagEditorPanel.getModel().applyToPrimitive(relation); 170 return Collections.<OsmPrimitive>singletonList(relation); 171 } 172 }; 173 174 // init the various models 175 // 176 memberTableModel = new MemberTableModel(relation, getLayer(), presetHandler); 177 memberTableModel.register(); 178 selectionTableModel = new SelectionTableModel(getLayer()); 179 selectionTableModel.register(); 180 referrerModel = new ReferringRelationsBrowserModel(relation); 181 182 tagEditorPanel = new TagEditorPanel(relation, presetHandler); 183 populateModels(relation); 184 tagEditorPanel.getModel().ensureOneTag(); 185 186 JSplitPane pane = buildSplitPane(); 187 pane.setPreferredSize(new Dimension(100, 100)); 188 189 JPanel pnl = new JPanel(new BorderLayout()); 190 pnl.add(pane, BorderLayout.CENTER); 191 pnl.setBorder(BorderFactory.createRaisedBevelBorder()); 192 193 getContentPane().setLayout(new BorderLayout()); 194 JTabbedPane tabbedPane = new JTabbedPane(); 195 tabbedPane.add(tr("Tags and Members"), pnl); 196 referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel); 197 tabbedPane.add(tr("Parent Relations"), referrerBrowser); 198 tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation)); 199 tabbedPane.addChangeListener( 200 new ChangeListener() { 201 @Override 202 public void stateChanged(ChangeEvent e) { 203 JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource(); 204 int index = sourceTabbedPane.getSelectedIndex(); 205 String title = sourceTabbedPane.getTitleAt(index); 206 if (title.equals(tr("Parent Relations"))) { 207 referrerBrowser.init(); 208 } 209 } 210 } 211 ); 212 213 getContentPane().add(buildToolBar(), BorderLayout.NORTH); 214 getContentPane().add(tabbedPane, BorderLayout.CENTER); 215 getContentPane().add(buildOkCancelButtonPanel(), BorderLayout.SOUTH); 216 217 setSize(findMaxDialogSize()); 218 219 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 220 addWindowListener( 221 new WindowAdapter() { 222 @Override 223 public void windowOpened(WindowEvent e) { 224 cleanSelfReferences(); 225 } 226 227 @Override 228 public void windowClosing(WindowEvent e) { 229 cancel(); 230 } 231 } 232 ); 233 registerCopyPasteAction(tagEditorPanel.getPasteAction(), 234 "PASTE_TAGS", 235 // CHECKSTYLE.OFF: LineLength 236 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke()); 237 // CHECKSTYLE.ON: LineLength 238 239 registerCopyPasteAction(new PasteMembersAction(memberTableModel, getLayer(), this) { 240 @Override 241 public void actionPerformed(ActionEvent e) { 242 super.actionPerformed(e); 243 tfRole.requestFocusInWindow(); 244 } 245 }, "PASTE_MEMBERS", Shortcut.getPasteKeyStroke()); 246 247 registerCopyPasteAction(new CopyMembersAction(memberTableModel, getLayer(), this), 248 "COPY_MEMBERS", Shortcut.getCopyKeyStroke()); 249 250 tagEditorPanel.setNextFocusComponent(memberTable); 251 selectionTable.setFocusable(false); 252 memberTableModel.setSelectedMembers(selectedMembers); 253 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/RelationEditor")); 254 } 255 256 @Override 257 public void reloadDataFromRelation() { 258 setRelation(getRelation()); 259 populateModels(getRelation()); 260 refreshAction.updateEnabledState(); 261 } 262 263 private void populateModels(Relation relation) { 264 if (relation != null) { 265 tagEditorPanel.getModel().initFromPrimitive(relation); 266 memberTableModel.populate(relation); 267 if (!getLayer().data.getRelations().contains(relation)) { 268 // treat it as a new relation if it doesn't exist in the data set yet. 269 setRelation(null); 270 } 271 } else { 272 tagEditorPanel.getModel().clear(); 273 memberTableModel.populate(null); 274 } 275 } 276 277 /** 278 * Apply changes. 279 * @see ApplyAction 280 */ 281 public void apply() { 282 applyAction.actionPerformed(null); 283 } 284 285 /** 286 * Cancel changes. 287 * @see CancelAction 288 */ 289 public void cancel() { 290 cancelAction.actionPerformed(null); 291 } 292 293 /** 294 * Creates the toolbar 295 * 296 * @return the toolbar 297 */ 298 protected JToolBar buildToolBar() { 299 JToolBar tb = new JToolBar(); 300 tb.setFloatable(false); 301 refreshAction = new RefreshAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this); 302 applyAction = new ApplyAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this); 303 tb.add(refreshAction); 304 tb.add(applyAction); 305 tb.add(new DuplicateRelationAction(memberTableModel, tagEditorPanel.getModel(), getLayer())); 306 DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction(getLayer(), this); 307 addPropertyChangeListener(deleteAction); 308 tb.add(deleteAction); 309 return tb; 310 } 311 312 /** 313 * builds the panel with the OK and the Cancel button 314 * 315 * @return the panel with the OK and the Cancel button 316 */ 317 protected JPanel buildOkCancelButtonPanel() { 318 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); 319 pnl.add(new SideButton(new OKAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole))); 320 cancelAction = new CancelAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole); 321 pnl.add(new SideButton(cancelAction)); 322 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor")))); 323 return pnl; 324 } 325 326 /** 327 * builds the panel with the tag editor 328 * 329 * @return the panel with the tag editor 330 */ 331 protected JPanel buildTagEditorPanel() { 332 JPanel pnl = new JPanel(new GridBagLayout()); 333 334 GridBagConstraints gc = new GridBagConstraints(); 335 gc.gridx = 0; 336 gc.gridy = 0; 337 gc.gridheight = 1; 338 gc.gridwidth = 1; 339 gc.fill = GridBagConstraints.HORIZONTAL; 340 gc.anchor = GridBagConstraints.FIRST_LINE_START; 341 gc.weightx = 1.0; 342 gc.weighty = 0.0; 343 pnl.add(new JLabel(tr("Tags")), gc); 344 345 gc.gridx = 0; 346 gc.gridy = 1; 347 gc.fill = GridBagConstraints.BOTH; 348 gc.anchor = GridBagConstraints.CENTER; 349 gc.weightx = 1.0; 350 gc.weighty = 1.0; 351 pnl.add(tagEditorPanel, gc); 352 return pnl; 353 } 354 355 /** 356 * builds the panel for the relation member editor 357 * 358 * @return the panel for the relation member editor 359 */ 360 protected JPanel buildMemberEditorPanel() { 361 final JPanel pnl = new JPanel(new GridBagLayout()); 362 // setting up the member table 363 memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel); 364 memberTable.addMouseListener(new MemberTableDblClickAdapter()); 365 memberTableModel.addMemberModelListener(memberTable); 366 367 final JScrollPane scrollPane = new JScrollPane(memberTable); 368 369 GridBagConstraints gc = new GridBagConstraints(); 370 gc.gridx = 0; 371 gc.gridy = 0; 372 gc.gridwidth = 2; 373 gc.fill = GridBagConstraints.HORIZONTAL; 374 gc.anchor = GridBagConstraints.FIRST_LINE_START; 375 gc.weightx = 1.0; 376 gc.weighty = 0.0; 377 pnl.add(new JLabel(tr("Members")), gc); 378 379 gc.gridx = 0; 380 gc.gridy = 1; 381 gc.gridheight = 2; 382 gc.gridwidth = 1; 383 gc.fill = GridBagConstraints.VERTICAL; 384 gc.anchor = GridBagConstraints.NORTHWEST; 385 gc.weightx = 0.0; 386 gc.weighty = 1.0; 387 pnl.add(buildLeftButtonPanel(), gc); 388 389 gc.gridx = 1; 390 gc.gridy = 1; 391 gc.gridheight = 1; 392 gc.fill = GridBagConstraints.BOTH; 393 gc.anchor = GridBagConstraints.CENTER; 394 gc.weightx = 0.6; 395 gc.weighty = 1.0; 396 pnl.add(scrollPane, gc); 397 398 // --- role editing 399 JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT)); 400 p3.add(new JLabel(tr("Apply Role:"))); 401 tfRole = new AutoCompletingTextField(10); 402 tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members")); 403 tfRole.addFocusListener(new FocusAdapter() { 404 @Override 405 public void focusGained(FocusEvent e) { 406 tfRole.selectAll(); 407 } 408 }); 409 tfRole.setAutoCompletionList(new AutoCompletionList()); 410 tfRole.addFocusListener( 411 new FocusAdapter() { 412 @Override 413 public void focusGained(FocusEvent e) { 414 AutoCompletionList list = tfRole.getAutoCompletionList(); 415 if (list != null) { 416 list.clear(); 417 getLayer().data.getAutoCompletionManager().populateWithMemberRoles(list, getRelation()); 418 } 419 } 420 } 421 ); 422 tfRole.setText(Main.pref.get("relation.editor.generic.lastrole", "")); 423 p3.add(tfRole); 424 SetRoleAction setRoleAction = new SetRoleAction(memberTable, memberTableModel, tfRole); 425 memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction); 426 tfRole.getDocument().addDocumentListener(setRoleAction); 427 tfRole.addActionListener(setRoleAction); 428 memberTableModel.getSelectionModel().addListSelectionListener( 429 new ListSelectionListener() { 430 @Override 431 public void valueChanged(ListSelectionEvent e) { 432 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0); 433 } 434 } 435 ); 436 tfRole.setEnabled(memberTable.getSelectedRowCount() > 0); 437 SideButton btnApply = new SideButton(setRoleAction); 438 btnApply.setPreferredSize(new Dimension(20, 20)); 439 btnApply.setText(""); 440 p3.add(btnApply); 441 442 gc.gridx = 1; 443 gc.gridy = 2; 444 gc.fill = GridBagConstraints.HORIZONTAL; 445 gc.anchor = GridBagConstraints.LAST_LINE_START; 446 gc.weightx = 1.0; 447 gc.weighty = 0.0; 448 pnl.add(p3, gc); 449 450 JPanel pnl2 = new JPanel(new GridBagLayout()); 451 452 gc.gridx = 0; 453 gc.gridy = 0; 454 gc.gridheight = 1; 455 gc.gridwidth = 3; 456 gc.fill = GridBagConstraints.HORIZONTAL; 457 gc.anchor = GridBagConstraints.FIRST_LINE_START; 458 gc.weightx = 1.0; 459 gc.weighty = 0.0; 460 pnl2.add(new JLabel(tr("Selection")), gc); 461 462 gc.gridx = 0; 463 gc.gridy = 1; 464 gc.gridheight = 1; 465 gc.gridwidth = 1; 466 gc.fill = GridBagConstraints.VERTICAL; 467 gc.anchor = GridBagConstraints.NORTHWEST; 468 gc.weightx = 0.0; 469 gc.weighty = 1.0; 470 pnl2.add(buildSelectionControlButtonPanel(), gc); 471 472 gc.gridx = 1; 473 gc.gridy = 1; 474 gc.weightx = 1.0; 475 gc.weighty = 1.0; 476 gc.fill = GridBagConstraints.BOTH; 477 pnl2.add(buildSelectionTablePanel(), gc); 478 479 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 480 splitPane.setLeftComponent(pnl); 481 splitPane.setRightComponent(pnl2); 482 splitPane.setOneTouchExpandable(false); 483 addWindowListener(new WindowAdapter() { 484 @Override 485 public void windowOpened(WindowEvent e) { 486 // has to be called when the window is visible, otherwise 487 // no effect 488 splitPane.setDividerLocation(0.6); 489 } 490 }); 491 492 JPanel pnl3 = new JPanel(new BorderLayout()); 493 pnl3.add(splitPane, BorderLayout.CENTER); 494 495 return pnl3; 496 } 497 498 /** 499 * builds the panel with the table displaying the currently selected primitives 500 * 501 * @return panel with current selection 502 */ 503 protected JPanel buildSelectionTablePanel() { 504 JPanel pnl = new JPanel(new BorderLayout()); 505 MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor(); 506 selectionTable = new SelectionTable(selectionTableModel, new SelectionTableColumnModel(memberTableModel)); 507 selectionTable.setMemberTableModel(memberTableModel); 508 selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height); 509 pnl.add(new JScrollPane(selectionTable), BorderLayout.CENTER); 510 return pnl; 511 } 512 513 /** 514 * builds the {@link JSplitPane} which divides the editor in an upper and a lower half 515 * 516 * @return the split panel 517 */ 518 protected JSplitPane buildSplitPane() { 519 final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 520 pane.setTopComponent(buildTagEditorPanel()); 521 pane.setBottomComponent(buildMemberEditorPanel()); 522 pane.setOneTouchExpandable(true); 523 addWindowListener(new WindowAdapter() { 524 @Override 525 public void windowOpened(WindowEvent e) { 526 // has to be called when the window is visible, otherwise no effect 527 pane.setDividerLocation(0.3); 528 } 529 }); 530 return pane; 531 } 532 533 /** 534 * build the panel with the buttons on the left 535 * 536 * @return left button panel 537 */ 538 protected JToolBar buildLeftButtonPanel() { 539 JToolBar tb = new JToolBar(); 540 tb.setOrientation(JToolBar.VERTICAL); 541 tb.setFloatable(false); 542 543 // -- move up action 544 MoveUpAction moveUpAction = new MoveUpAction(memberTable, memberTableModel, "moveUp"); 545 memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction); 546 tb.add(moveUpAction); 547 548 // -- move down action 549 MoveDownAction moveDownAction = new MoveDownAction(memberTable, memberTableModel, "moveDown"); 550 memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction); 551 tb.add(moveDownAction); 552 553 tb.addSeparator(); 554 555 // -- edit action 556 EditAction editAction = new EditAction(memberTable, memberTableModel, getLayer()); 557 memberTableModel.getSelectionModel().addListSelectionListener(editAction); 558 tb.add(editAction); 559 560 // -- delete action 561 RemoveAction removeSelectedAction = new RemoveAction(memberTable, memberTableModel, "removeSelected"); 562 memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction); 563 tb.add(removeSelectedAction); 564 565 tb.addSeparator(); 566 // -- sort action 567 SortAction sortAction = new SortAction(memberTable, memberTableModel); 568 memberTableModel.addTableModelListener(sortAction); 569 tb.add(sortAction); 570 final SortBelowAction sortBelowAction = new SortBelowAction(memberTable, memberTableModel); 571 memberTableModel.addTableModelListener(sortBelowAction); 572 memberTableModel.getSelectionModel().addListSelectionListener(sortBelowAction); 573 sortBelowButton = tb.add(sortBelowAction); 574 575 // -- reverse action 576 ReverseAction reverseAction = new ReverseAction(memberTable, memberTableModel); 577 memberTableModel.addTableModelListener(reverseAction); 578 tb.add(reverseAction); 579 580 tb.addSeparator(); 581 582 // -- download action 583 DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction( 584 memberTable, memberTableModel, "downloadIncomplete", getLayer(), this); 585 memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction); 586 tb.add(downloadIncompleteMembersAction); 587 588 // -- download selected action 589 DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction( 590 memberTable, memberTableModel, null, getLayer(), this); 591 memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction); 592 memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction); 593 tb.add(downloadSelectedIncompleteMembersAction); 594 595 InputMap inputMap = memberTable.getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 596 inputMap.put((KeyStroke) removeSelectedAction.getValue(AbstractAction.ACCELERATOR_KEY), "removeSelected"); 597 inputMap.put((KeyStroke) moveUpAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveUp"); 598 inputMap.put((KeyStroke) moveDownAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveDown"); 599 inputMap.put((KeyStroke) downloadIncompleteMembersAction.getValue(AbstractAction.ACCELERATOR_KEY), "downloadIncomplete"); 600 601 return tb; 602 } 603 604 /** 605 * build the panel with the buttons for adding or removing the current selection 606 * 607 * @return control buttons panel for selection/members 608 */ 609 protected JToolBar buildSelectionControlButtonPanel() { 610 JToolBar tb = new JToolBar(JToolBar.VERTICAL); 611 tb.setFloatable(false); 612 613 // -- add at start action 614 AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction( 615 memberTableModel, selectionTableModel, this); 616 selectionTableModel.addTableModelListener(addSelectionAction); 617 tb.add(addSelectionAction); 618 619 // -- add before selected action 620 AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection( 621 memberTableModel, selectionTableModel, this); 622 selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction); 623 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction); 624 tb.add(addSelectedBeforeSelectionAction); 625 626 // -- add after selected action 627 AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection( 628 memberTableModel, selectionTableModel, this); 629 selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction); 630 memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction); 631 tb.add(addSelectedAfterSelectionAction); 632 633 // -- add at end action 634 AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction( 635 memberTableModel, selectionTableModel, this); 636 selectionTableModel.addTableModelListener(addSelectedAtEndAction); 637 tb.add(addSelectedAtEndAction); 638 639 tb.addSeparator(); 640 641 // -- select members action 642 SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction( 643 memberTableModel, selectionTableModel, getLayer()); 644 selectionTableModel.addTableModelListener(selectMembersForSelectionAction); 645 memberTableModel.addTableModelListener(selectMembersForSelectionAction); 646 tb.add(selectMembersForSelectionAction); 647 648 // -- select action 649 SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction( 650 memberTable, memberTableModel, getLayer()); 651 memberTable.getSelectionModel().addListSelectionListener(selectAction); 652 tb.add(selectAction); 653 654 tb.addSeparator(); 655 656 // -- remove selected action 657 RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction(memberTableModel, selectionTableModel, getLayer()); 658 selectionTableModel.addTableModelListener(removeSelectedAction); 659 tb.add(removeSelectedAction); 660 661 return tb; 662 } 663 664 @Override 665 protected Dimension findMaxDialogSize() { 666 return new Dimension(700, 650); 667 } 668 669 @Override 670 public void setVisible(boolean visible) { 671 if (visible) { 672 tagEditorPanel.initAutoCompletion(getLayer()); 673 } 674 super.setVisible(visible); 675 if (visible) { 676 sortBelowButton.setVisible(ExpertToggleAction.isExpert()); 677 RelationDialogManager.getRelationDialogManager().positionOnScreen(this); 678 if (windowMenuItem == null) { 679 addToWindowMenu(); 680 } 681 tagEditorPanel.requestFocusInWindow(); 682 } else { 683 // make sure all registered listeners are unregistered 684 // 685 memberTable.stopHighlighting(); 686 selectionTableModel.unregister(); 687 memberTableModel.unregister(); 688 memberTable.unlinkAsListener(); 689 if (windowMenuItem != null) { 690 Main.main.menu.windowMenu.remove(windowMenuItem); 691 windowMenuItem = null; 692 } 693 dispose(); 694 } 695 } 696 697 /** adds current relation editor to the windows menu (in the "volatile" group) o*/ 698 protected void addToWindowMenu() { 699 String name = getRelation() == null ? tr("New Relation") : getRelation().getLocalName(); 700 final String tt = tr("Focus Relation Editor with relation ''{0}'' in layer ''{1}''", 701 name, getLayer().getName()); 702 name = tr("Relation Editor: {0}", name == null ? getRelation().getId() : name); 703 final JMenu wm = Main.main.menu.windowMenu; 704 final JosmAction focusAction = new JosmAction(name, "dialogs/relationlist", tt, null, false, false) { 705 @Override 706 public void actionPerformed(ActionEvent e) { 707 final RelationEditor r = (RelationEditor) getValue("relationEditor"); 708 r.setVisible(true); 709 } 710 }; 711 focusAction.putValue("relationEditor", this); 712 windowMenuItem = MainMenu.add(wm, focusAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE); 713 } 714 715 /** 716 * checks whether the current relation has members referring to itself. If so, 717 * warns the users and provides an option for removing these members. 718 * 719 */ 720 protected void cleanSelfReferences() { 721 List<OsmPrimitive> toCheck = new ArrayList<>(); 722 toCheck.add(getRelation()); 723 if (memberTableModel.hasMembersReferringTo(toCheck)) { 724 int ret = ConditionalOptionPaneUtil.showOptionDialog( 725 "clean_relation_self_references", 726 Main.parent, 727 tr("<html>There is at least one member in this relation referring<br>" 728 + "to the relation itself.<br>" 729 + "This creates circular dependencies and is discouraged.<br>" 730 + "How do you want to proceed with circular dependencies?</html>"), 731 tr("Warning"), 732 JOptionPane.YES_NO_OPTION, 733 JOptionPane.WARNING_MESSAGE, 734 new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")}, 735 tr("Remove them, clean up relation") 736 ); 737 switch(ret) { 738 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: 739 case JOptionPane.CLOSED_OPTION: 740 case JOptionPane.NO_OPTION: 741 return; 742 case JOptionPane.YES_OPTION: 743 memberTableModel.removeMembersReferringTo(toCheck); 744 break; 745 } 746 } 747 } 748 749 private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut) { 750 int mods = shortcut.getModifiers(); 751 int code = shortcut.getKeyCode(); 752 if (code != KeyEvent.VK_INSERT && (mods == 0 || mods == InputEvent.SHIFT_DOWN_MASK)) { 753 Main.info(tr("Sorry, shortcut \"{0}\" can not be enabled in Relation editor dialog"), shortcut); 754 return; 755 } 756 getRootPane().getActionMap().put(actionName, action); 757 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 758 // Assign also to JTables because they have their own Copy&Paste implementation 759 // (which is disabled in this case but eats key shortcuts anyway) 760 memberTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName); 761 memberTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName); 762 memberTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 763 selectionTable.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName); 764 selectionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName); 765 selectionTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName); 766 } 767 768 /** 769 * Exception thrown when user aborts add operation. 770 */ 771 public static class AddAbortException extends Exception { 772 } 773 774 /** 775 * Asks confirmationbefore adding a primitive. 776 * @param primitive primitive to add 777 * @return {@code true} is user confirms the operation, {@code false} otherwise 778 * @throws AddAbortException if user aborts operation 779 */ 780 public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException { 781 String msg = tr("<html>This relation already has one or more members referring to<br>" 782 + "the object ''{0}''<br>" 783 + "<br>" 784 + "Do you really want to add another relation member?</html>", 785 primitive.getDisplayName(DefaultNameFormatter.getInstance()) 786 ); 787 int ret = ConditionalOptionPaneUtil.showOptionDialog( 788 "add_primitive_to_relation", 789 Main.parent, 790 msg, 791 tr("Multiple members referring to same object."), 792 JOptionPane.YES_NO_CANCEL_OPTION, 793 JOptionPane.WARNING_MESSAGE, 794 null, 795 null 796 ); 797 switch(ret) { 798 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: 799 case JOptionPane.YES_OPTION: 800 return true; 801 case JOptionPane.NO_OPTION: 802 case JOptionPane.CLOSED_OPTION: 803 return false; 804 case JOptionPane.CANCEL_OPTION: 805 default: 806 throw new AddAbortException(); 807 } 808 } 809 810 /** 811 * Warn about circular references. 812 * @param primitive the concerned primitive 813 */ 814 public static void warnOfCircularReferences(OsmPrimitive primitive) { 815 String msg = tr("<html>You are trying to add a relation to itself.<br>" 816 + "<br>" 817 + "This creates circular references and is therefore discouraged.<br>" 818 + "Skipping relation ''{0}''.</html>", 819 primitive.getDisplayName(DefaultNameFormatter.getInstance())); 820 JOptionPane.showMessageDialog( 821 Main.parent, 822 msg, 823 tr("Warning"), 824 JOptionPane.WARNING_MESSAGE); 825 } 826 827 /** 828 * Adds primitives to a given relation. 829 * @param orig The relation to modify 830 * @param primitivesToAdd The primitives to add as relation members 831 * @return The resulting command 832 * @throws IllegalArgumentException if orig is null 833 */ 834 public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) { 835 CheckParameterUtil.ensureParameterNotNull(orig, "orig"); 836 try { 837 final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets( 838 EnumSet.of(TaggingPresetType.forPrimitive(orig)), orig.getKeys(), false); 839 Relation relation = new Relation(orig); 840 boolean modified = false; 841 for (OsmPrimitive p : primitivesToAdd) { 842 if (p instanceof Relation && orig.equals(p)) { 843 if (!GraphicsEnvironment.isHeadless()) { 844 warnOfCircularReferences(p); 845 } 846 continue; 847 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p)) 848 && !confirmAddingPrimitive(p)) { 849 continue; 850 } 851 final Set<String> roles = findSuggestedRoles(presets, p); 852 relation.addMember(new RelationMember(roles.size() == 1 ? roles.iterator().next() : "", p)); 853 modified = true; 854 } 855 return modified ? new ChangeCommand(orig, relation) : null; 856 } catch (AddAbortException ign) { 857 return null; 858 } 859 } 860 861 protected static Set<String> findSuggestedRoles(final Collection<TaggingPreset> presets, OsmPrimitive p) { 862 final Set<String> roles = new HashSet<>(); 863 for (TaggingPreset preset : presets) { 864 String role = preset.suggestRoleForOsmPrimitive(p); 865 if (role != null && !role.isEmpty()) { 866 roles.add(role); 867 } 868 } 869 return roles; 870 } 871 872 class MemberTableDblClickAdapter extends MouseAdapter { 873 @Override 874 public void mouseClicked(MouseEvent e) { 875 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { 876 new EditAction(memberTable, memberTableModel, getLayer()).actionPerformed(null); 877 } 878 } 879 } 880}