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