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