001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.conflict.pair; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.FlowLayout; 008import java.awt.GridBagConstraints; 009import java.awt.GridBagLayout; 010import java.awt.Insets; 011import java.awt.event.ActionEvent; 012import java.awt.event.ItemEvent; 013import java.awt.event.ItemListener; 014import java.beans.PropertyChangeEvent; 015import java.beans.PropertyChangeListener; 016import java.util.Collection; 017 018import javax.swing.AbstractAction; 019import javax.swing.Action; 020import javax.swing.ImageIcon; 021import javax.swing.JButton; 022import javax.swing.JCheckBox; 023import javax.swing.JLabel; 024import javax.swing.JPanel; 025import javax.swing.JScrollPane; 026import javax.swing.JTable; 027import javax.swing.JToggleButton; 028import javax.swing.event.ChangeEvent; 029import javax.swing.event.ChangeListener; 030import javax.swing.event.ListSelectionEvent; 031import javax.swing.event.ListSelectionListener; 032 033import org.openstreetmap.josm.Main; 034import org.openstreetmap.josm.command.conflict.ConflictResolveCommand; 035import org.openstreetmap.josm.data.osm.OsmPrimitive; 036import org.openstreetmap.josm.data.osm.PrimitiveId; 037import org.openstreetmap.josm.data.osm.Relation; 038import org.openstreetmap.josm.data.osm.Way; 039import org.openstreetmap.josm.gui.layer.OsmDataLayer; 040import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer; 041import org.openstreetmap.josm.gui.widgets.JosmComboBox; 042import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 043import org.openstreetmap.josm.tools.ImageProvider; 044 045/** 046 * A UI component for resolving conflicts in two lists of entries of type T. 047 * 048 * @param <T> the type of the entries 049 * @param <C> the type of conflict resolution command 050 * @see AbstractListMergeModel 051 * @since 1631 052 */ 053public abstract class AbstractListMerger<T extends PrimitiveId, C extends ConflictResolveCommand> extends JPanel 054implements PropertyChangeListener, ChangeListener, IConflictResolver { 055 protected OsmPrimitivesTable myEntriesTable; 056 protected OsmPrimitivesTable mergedEntriesTable; 057 protected OsmPrimitivesTable theirEntriesTable; 058 059 protected transient AbstractListMergeModel<T, C> model; 060 061 private CopyStartLeftAction copyStartLeftAction; 062 private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction; 063 private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction; 064 private CopyEndLeftAction copyEndLeftAction; 065 private CopyAllLeft copyAllLeft; 066 067 private CopyStartRightAction copyStartRightAction; 068 private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction; 069 private CopyAfterCurrentRightAction copyAfterCurrentRightAction; 070 private CopyEndRightAction copyEndRightAction; 071 private CopyAllRight copyAllRight; 072 073 private MoveUpMergedAction moveUpMergedAction; 074 private MoveDownMergedAction moveDownMergedAction; 075 private RemoveMergedAction removeMergedAction; 076 private FreezeAction freezeAction; 077 078 private transient AdjustmentSynchronizer adjustmentSynchronizer; 079 080 private JLabel lblMyVersion; 081 private JLabel lblMergedVersion; 082 private JLabel lblTheirVersion; 083 084 private JLabel lblFrozenState; 085 086 protected abstract JScrollPane buildMyElementsTable(); 087 088 protected abstract JScrollPane buildMergedElementsTable(); 089 090 protected abstract JScrollPane buildTheirElementsTable(); 091 092 protected JScrollPane embeddInScrollPane(JTable table) { 093 JScrollPane pane = new JScrollPane(table); 094 if (adjustmentSynchronizer == null) { 095 adjustmentSynchronizer = new AdjustmentSynchronizer(); 096 } 097 return pane; 098 } 099 100 protected void wireActionsToSelectionModels() { 101 myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction); 102 103 myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 104 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 105 106 myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 107 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 108 109 myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction); 110 111 theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction); 112 113 theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 114 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 115 116 theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 117 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 118 119 theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction); 120 121 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction); 122 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction); 123 mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction); 124 125 model.addChangeListener(copyAllLeft); 126 model.addChangeListener(copyAllRight); 127 model.addPropertyChangeListener(copyAllLeft); 128 model.addPropertyChangeListener(copyAllRight); 129 } 130 131 protected JPanel buildLeftButtonPanel() { 132 JPanel pnl = new JPanel(new GridBagLayout()); 133 GridBagConstraints gc = new GridBagConstraints(); 134 135 gc.gridx = 0; 136 gc.gridy = 0; 137 copyStartLeftAction = new CopyStartLeftAction(); 138 JButton btn = new JButton(copyStartLeftAction); 139 btn.setName("button.copystartleft"); 140 pnl.add(btn, gc); 141 142 gc.gridx = 0; 143 gc.gridy = 1; 144 copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction(); 145 btn = new JButton(copyBeforeCurrentLeftAction); 146 btn.setName("button.copybeforecurrentleft"); 147 pnl.add(btn, gc); 148 149 gc.gridx = 0; 150 gc.gridy = 2; 151 copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction(); 152 btn = new JButton(copyAfterCurrentLeftAction); 153 btn.setName("button.copyaftercurrentleft"); 154 pnl.add(btn, gc); 155 156 gc.gridx = 0; 157 gc.gridy = 3; 158 copyEndLeftAction = new CopyEndLeftAction(); 159 btn = new JButton(copyEndLeftAction); 160 btn.setName("button.copyendleft"); 161 pnl.add(btn, gc); 162 163 gc.gridx = 0; 164 gc.gridy = 4; 165 copyAllLeft = new CopyAllLeft(); 166 btn = new JButton(copyAllLeft); 167 btn.setName("button.copyallleft"); 168 pnl.add(btn, gc); 169 170 return pnl; 171 } 172 173 protected JPanel buildRightButtonPanel() { 174 JPanel pnl = new JPanel(new GridBagLayout()); 175 GridBagConstraints gc = new GridBagConstraints(); 176 177 gc.gridx = 0; 178 gc.gridy = 0; 179 copyStartRightAction = new CopyStartRightAction(); 180 pnl.add(new JButton(copyStartRightAction), gc); 181 182 gc.gridx = 0; 183 gc.gridy = 1; 184 copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction(); 185 pnl.add(new JButton(copyBeforeCurrentRightAction), gc); 186 187 gc.gridx = 0; 188 gc.gridy = 2; 189 copyAfterCurrentRightAction = new CopyAfterCurrentRightAction(); 190 pnl.add(new JButton(copyAfterCurrentRightAction), gc); 191 192 gc.gridx = 0; 193 gc.gridy = 3; 194 copyEndRightAction = new CopyEndRightAction(); 195 pnl.add(new JButton(copyEndRightAction), gc); 196 197 gc.gridx = 0; 198 gc.gridy = 4; 199 copyAllRight = new CopyAllRight(); 200 pnl.add(new JButton(copyAllRight), gc); 201 202 return pnl; 203 } 204 205 protected JPanel buildMergedListControlButtons() { 206 JPanel pnl = new JPanel(new GridBagLayout()); 207 GridBagConstraints gc = new GridBagConstraints(); 208 209 gc.gridx = 0; 210 gc.gridy = 0; 211 gc.gridwidth = 1; 212 gc.gridheight = 1; 213 gc.fill = GridBagConstraints.HORIZONTAL; 214 gc.anchor = GridBagConstraints.CENTER; 215 gc.weightx = 0.3; 216 gc.weighty = 0.0; 217 moveUpMergedAction = new MoveUpMergedAction(); 218 pnl.add(new JButton(moveUpMergedAction), gc); 219 220 gc.gridx = 1; 221 gc.gridy = 0; 222 moveDownMergedAction = new MoveDownMergedAction(); 223 pnl.add(new JButton(moveDownMergedAction), gc); 224 225 gc.gridx = 2; 226 gc.gridy = 0; 227 removeMergedAction = new RemoveMergedAction(); 228 pnl.add(new JButton(removeMergedAction), gc); 229 230 return pnl; 231 } 232 233 protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) { 234 JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); 235 panel.add(new JLabel(tr("lock scrolling"))); 236 panel.add(cb); 237 return panel; 238 } 239 240 protected JPanel buildComparePairSelectionPanel() { 241 JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); 242 p.add(new JLabel(tr("Compare "))); 243 JosmComboBox<ComparePairType> cbComparePair = new JosmComboBox<>(model.getComparePairListModel()); 244 cbComparePair.setRenderer(new ComparePairListCellRenderer()); 245 p.add(cbComparePair); 246 return p; 247 } 248 249 protected JPanel buildFrozeStateControlPanel() { 250 JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); 251 lblFrozenState = new JLabel(); 252 p.add(lblFrozenState); 253 freezeAction = new FreezeAction(); 254 JToggleButton btn = new JToggleButton(freezeAction); 255 freezeAction.adapt(btn); 256 btn.setName("button.freeze"); 257 p.add(btn); 258 259 return p; 260 } 261 262 protected final void build() { 263 setLayout(new GridBagLayout()); 264 GridBagConstraints gc = new GridBagConstraints(); 265 266 // ------------------ 267 gc.gridx = 0; 268 gc.gridy = 0; 269 gc.gridwidth = 1; 270 gc.gridheight = 1; 271 gc.fill = GridBagConstraints.NONE; 272 gc.anchor = GridBagConstraints.CENTER; 273 gc.weightx = 0.0; 274 gc.weighty = 0.0; 275 gc.insets = new Insets(10, 0, 0, 0); 276 lblMyVersion = new JLabel(tr("My version")); 277 lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset")); 278 add(lblMyVersion, gc); 279 280 gc.gridx = 2; 281 gc.gridy = 0; 282 lblMergedVersion = new JLabel(tr("Merged version")); 283 lblMergedVersion.setToolTipText( 284 tr("List of merged elements. They will replace the list of my elements when the merge decisions are applied.")); 285 add(lblMergedVersion, gc); 286 287 gc.gridx = 4; 288 gc.gridy = 0; 289 lblTheirVersion = new JLabel(tr("Their version")); 290 lblTheirVersion.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset")); 291 add(lblTheirVersion, gc); 292 293 // ------------------------------ 294 gc.gridx = 0; 295 gc.gridy = 1; 296 gc.gridwidth = 1; 297 gc.gridheight = 1; 298 gc.fill = GridBagConstraints.HORIZONTAL; 299 gc.anchor = GridBagConstraints.FIRST_LINE_START; 300 gc.weightx = 0.33; 301 gc.weighty = 0.0; 302 gc.insets = new Insets(0, 0, 0, 0); 303 JCheckBox cbLockMyScrolling = new JCheckBox(); 304 cbLockMyScrolling.setName("checkbox.lockmyscrolling"); 305 add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc); 306 307 gc.gridx = 2; 308 gc.gridy = 1; 309 JCheckBox cbLockMergedScrolling = new JCheckBox(); 310 cbLockMergedScrolling.setName("checkbox.lockmergedscrolling"); 311 add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc); 312 313 gc.gridx = 4; 314 gc.gridy = 1; 315 JCheckBox cbLockTheirScrolling = new JCheckBox(); 316 cbLockTheirScrolling.setName("checkbox.locktheirscrolling"); 317 add(buildAdjustmentLockControlPanel(cbLockTheirScrolling), gc); 318 319 // -------------------------------- 320 gc.gridx = 0; 321 gc.gridy = 2; 322 gc.gridwidth = 1; 323 gc.gridheight = 1; 324 gc.fill = GridBagConstraints.BOTH; 325 gc.anchor = GridBagConstraints.FIRST_LINE_START; 326 gc.weightx = 0.33; 327 gc.weighty = 1.0; 328 gc.insets = new Insets(0, 0, 0, 0); 329 JScrollPane pane = buildMyElementsTable(); 330 lblMyVersion.setLabelFor(pane); 331 adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar()); 332 add(pane, gc); 333 334 gc.gridx = 1; 335 gc.gridy = 2; 336 gc.fill = GridBagConstraints.NONE; 337 gc.anchor = GridBagConstraints.CENTER; 338 gc.weightx = 0.0; 339 gc.weighty = 0.0; 340 add(buildLeftButtonPanel(), gc); 341 342 gc.gridx = 2; 343 gc.gridy = 2; 344 gc.fill = GridBagConstraints.BOTH; 345 gc.anchor = GridBagConstraints.FIRST_LINE_START; 346 gc.weightx = 0.33; 347 gc.weighty = 0.0; 348 pane = buildMergedElementsTable(); 349 lblMergedVersion.setLabelFor(pane); 350 adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar()); 351 add(pane, gc); 352 353 gc.gridx = 3; 354 gc.gridy = 2; 355 gc.fill = GridBagConstraints.NONE; 356 gc.anchor = GridBagConstraints.CENTER; 357 gc.weightx = 0.0; 358 gc.weighty = 0.0; 359 add(buildRightButtonPanel(), gc); 360 361 gc.gridx = 4; 362 gc.gridy = 2; 363 gc.fill = GridBagConstraints.BOTH; 364 gc.anchor = GridBagConstraints.FIRST_LINE_START; 365 gc.weightx = 0.33; 366 gc.weighty = 0.0; 367 pane = buildTheirElementsTable(); 368 lblTheirVersion.setLabelFor(pane); 369 adjustmentSynchronizer.adapt(cbLockTheirScrolling, pane.getVerticalScrollBar()); 370 add(pane, gc); 371 372 // ---------------------------------- 373 gc.gridx = 2; 374 gc.gridy = 3; 375 gc.gridwidth = 1; 376 gc.gridheight = 1; 377 gc.fill = GridBagConstraints.BOTH; 378 gc.anchor = GridBagConstraints.CENTER; 379 gc.weightx = 0.0; 380 gc.weighty = 0.0; 381 add(buildMergedListControlButtons(), gc); 382 383 // ----------------------------------- 384 gc.gridx = 0; 385 gc.gridy = 4; 386 gc.gridwidth = 2; 387 gc.gridheight = 1; 388 gc.fill = GridBagConstraints.HORIZONTAL; 389 gc.anchor = GridBagConstraints.LINE_START; 390 gc.weightx = 0.0; 391 gc.weighty = 0.0; 392 add(buildComparePairSelectionPanel(), gc); 393 394 gc.gridx = 2; 395 gc.gridy = 4; 396 gc.gridwidth = 3; 397 gc.gridheight = 1; 398 gc.fill = GridBagConstraints.HORIZONTAL; 399 gc.anchor = GridBagConstraints.LINE_START; 400 gc.weightx = 0.0; 401 gc.weighty = 0.0; 402 add(buildFrozeStateControlPanel(), gc); 403 404 wireActionsToSelectionModels(); 405 } 406 407 /** 408 * Constructs a new {@code ListMerger}. 409 * @param model list merger model 410 */ 411 public AbstractListMerger(AbstractListMergeModel<T, C> model) { 412 this.model = model; 413 model.addChangeListener(this); 414 build(); 415 model.addPropertyChangeListener(this); 416 } 417 418 /** 419 * Base class of all other Copy* inner classes. 420 */ 421 abstract static class CopyAction extends AbstractAction implements ListSelectionListener { 422 423 protected CopyAction(String iconName, String actionName, String shortDescription) { 424 ImageIcon icon = ImageProvider.get("dialogs/conflict", iconName); 425 putValue(Action.SMALL_ICON, icon); 426 if (icon == null) { 427 putValue(Action.NAME, actionName); 428 } 429 putValue(Action.SHORT_DESCRIPTION, shortDescription); 430 setEnabled(false); 431 } 432 } 433 434 /** 435 * Action for copying selected nodes in the list of my nodes to the list of merged 436 * nodes. Inserts the nodes at the beginning of the list of merged nodes. 437 */ 438 class CopyStartLeftAction extends CopyAction { 439 440 CopyStartLeftAction() { 441 super(/* ICON(dialogs/conflict/)*/ "copystartleft", tr("> top"), 442 tr("Copy my selected nodes to the start of the merged node list")); 443 } 444 445 @Override 446 public void actionPerformed(ActionEvent e) { 447 model.copyMyToTop(myEntriesTable.getSelectedRows()); 448 } 449 450 @Override 451 public void valueChanged(ListSelectionEvent e) { 452 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 453 } 454 } 455 456 /** 457 * Action for copying selected nodes in the list of my nodes to the list of merged 458 * nodes. Inserts the nodes at the end of the list of merged nodes. 459 */ 460 class CopyEndLeftAction extends CopyAction { 461 462 CopyEndLeftAction() { 463 super(/* ICON(dialogs/conflict/)*/ "copyendleft", tr("> bottom"), 464 tr("Copy my selected elements to the end of the list of merged elements.")); 465 } 466 467 @Override 468 public void actionPerformed(ActionEvent e) { 469 model.copyMyToEnd(myEntriesTable.getSelectedRows()); 470 } 471 472 @Override 473 public void valueChanged(ListSelectionEvent e) { 474 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 475 } 476 } 477 478 /** 479 * Action for copying selected nodes in the list of my nodes to the list of merged 480 * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 481 */ 482 class CopyBeforeCurrentLeftAction extends CopyAction { 483 484 CopyBeforeCurrentLeftAction() { 485 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentleft", tr("> before"), 486 tr("Copy my selected elements before the first selected element in the list of merged elements.")); 487 } 488 489 @Override 490 public void actionPerformed(ActionEvent e) { 491 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 492 if (mergedRows.length == 0) 493 return; 494 int[] myRows = myEntriesTable.getSelectedRows(); 495 int current = mergedRows[0]; 496 model.copyMyBeforeCurrent(myRows, current); 497 } 498 499 @Override 500 public void valueChanged(ListSelectionEvent e) { 501 setEnabled( 502 !myEntriesTable.getSelectionModel().isSelectionEmpty() 503 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 504 ); 505 } 506 } 507 508 /** 509 * Action for copying selected nodes in the list of my nodes to the list of merged 510 * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 511 */ 512 class CopyAfterCurrentLeftAction extends CopyAction { 513 514 CopyAfterCurrentLeftAction() { 515 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentleft", tr("> after"), 516 tr("Copy my selected elements after the first selected element in the list of merged elements.")); 517 } 518 519 @Override 520 public void actionPerformed(ActionEvent e) { 521 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 522 if (mergedRows.length == 0) 523 return; 524 int[] myRows = myEntriesTable.getSelectedRows(); 525 int current = mergedRows[0]; 526 model.copyMyAfterCurrent(myRows, current); 527 } 528 529 @Override 530 public void valueChanged(ListSelectionEvent e) { 531 setEnabled( 532 !myEntriesTable.getSelectionModel().isSelectionEmpty() 533 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 534 ); 535 } 536 } 537 538 class CopyStartRightAction extends CopyAction { 539 540 CopyStartRightAction() { 541 super(/* ICON(dialogs/conflict/)*/ "copystartright", tr("< top"), 542 tr("Copy their selected element to the start of the list of merged elements.")); 543 } 544 545 @Override 546 public void actionPerformed(ActionEvent e) { 547 model.copyTheirToTop(theirEntriesTable.getSelectedRows()); 548 } 549 550 @Override 551 public void valueChanged(ListSelectionEvent e) { 552 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 553 } 554 } 555 556 class CopyEndRightAction extends CopyAction { 557 558 CopyEndRightAction() { 559 super(/* ICON(dialogs/conflict/)*/ "copyendright", tr("< bottom"), 560 tr("Copy their selected elements to the end of the list of merged elements.")); 561 } 562 563 @Override 564 public void actionPerformed(ActionEvent arg0) { 565 model.copyTheirToEnd(theirEntriesTable.getSelectedRows()); 566 } 567 568 @Override 569 public void valueChanged(ListSelectionEvent e) { 570 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 571 } 572 } 573 574 class CopyBeforeCurrentRightAction extends CopyAction { 575 576 CopyBeforeCurrentRightAction() { 577 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentright", tr("< before"), 578 tr("Copy their selected elements before the first selected element in the list of merged elements.")); 579 } 580 581 @Override 582 public void actionPerformed(ActionEvent e) { 583 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 584 if (mergedRows.length == 0) 585 return; 586 int[] myRows = theirEntriesTable.getSelectedRows(); 587 int current = mergedRows[0]; 588 model.copyTheirBeforeCurrent(myRows, current); 589 } 590 591 @Override 592 public void valueChanged(ListSelectionEvent e) { 593 setEnabled( 594 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 595 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 596 ); 597 } 598 } 599 600 class CopyAfterCurrentRightAction extends CopyAction { 601 602 CopyAfterCurrentRightAction() { 603 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentright", tr("< after"), 604 tr("Copy their selected element after the first selected element in the list of merged elements")); 605 } 606 607 @Override 608 public void actionPerformed(ActionEvent e) { 609 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 610 if (mergedRows.length == 0) 611 return; 612 int[] myRows = theirEntriesTable.getSelectedRows(); 613 int current = mergedRows[0]; 614 model.copyTheirAfterCurrent(myRows, current); 615 } 616 617 @Override 618 public void valueChanged(ListSelectionEvent e) { 619 setEnabled( 620 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 621 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 622 ); 623 } 624 } 625 626 class CopyAllLeft extends AbstractAction implements ChangeListener, PropertyChangeListener { 627 628 CopyAllLeft() { 629 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft"); 630 putValue(Action.SMALL_ICON, icon); 631 putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); 632 } 633 634 @Override 635 public void actionPerformed(ActionEvent arg0) { 636 model.copyAll(ListRole.MY_ENTRIES); 637 model.setFrozen(true); 638 } 639 640 private void updateEnabledState() { 641 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 642 } 643 644 @Override 645 public void stateChanged(ChangeEvent e) { 646 updateEnabledState(); 647 } 648 649 @Override 650 public void propertyChange(PropertyChangeEvent evt) { 651 updateEnabledState(); 652 } 653 } 654 655 class CopyAllRight extends AbstractAction implements ChangeListener, PropertyChangeListener { 656 657 CopyAllRight() { 658 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright"); 659 putValue(Action.SMALL_ICON, icon); 660 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 661 } 662 663 @Override 664 public void actionPerformed(ActionEvent arg0) { 665 model.copyAll(ListRole.THEIR_ENTRIES); 666 model.setFrozen(true); 667 } 668 669 private void updateEnabledState() { 670 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 671 } 672 673 @Override 674 public void stateChanged(ChangeEvent e) { 675 updateEnabledState(); 676 } 677 678 @Override 679 public void propertyChange(PropertyChangeEvent evt) { 680 updateEnabledState(); 681 } 682 } 683 684 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 685 686 MoveUpMergedAction() { 687 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup"); 688 putValue(Action.SMALL_ICON, icon); 689 if (icon == null) { 690 putValue(Action.NAME, tr("Up")); 691 } 692 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position.")); 693 setEnabled(false); 694 } 695 696 @Override 697 public void actionPerformed(ActionEvent arg0) { 698 int[] rows = mergedEntriesTable.getSelectedRows(); 699 model.moveUpMerged(rows); 700 } 701 702 @Override 703 public void valueChanged(ListSelectionEvent e) { 704 int[] rows = mergedEntriesTable.getSelectedRows(); 705 setEnabled(rows.length > 0 706 && rows[0] != 0 707 ); 708 } 709 } 710 711 /** 712 * Action for moving the currently selected entries in the list of merged entries 713 * one position down 714 * 715 */ 716 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { 717 718 MoveDownMergedAction() { 719 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown"); 720 putValue(Action.SMALL_ICON, icon); 721 if (icon == null) { 722 putValue(Action.NAME, tr("Down")); 723 } 724 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 725 setEnabled(false); 726 } 727 728 @Override 729 public void actionPerformed(ActionEvent arg0) { 730 int[] rows = mergedEntriesTable.getSelectedRows(); 731 model.moveDownMerged(rows); 732 } 733 734 @Override 735 public void valueChanged(ListSelectionEvent e) { 736 int[] rows = mergedEntriesTable.getSelectedRows(); 737 setEnabled(rows.length > 0 738 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 739 ); 740 } 741 } 742 743 /** 744 * Action for removing the selected entries in the list of merged entries 745 * from the list of merged entries. 746 * 747 */ 748 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 749 750 RemoveMergedAction() { 751 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove"); 752 putValue(Action.SMALL_ICON, icon); 753 if (icon == null) { 754 putValue(Action.NAME, tr("Remove")); 755 } 756 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 757 setEnabled(false); 758 } 759 760 @Override 761 public void actionPerformed(ActionEvent arg0) { 762 int[] rows = mergedEntriesTable.getSelectedRows(); 763 model.removeMerged(rows); 764 } 765 766 @Override 767 public void valueChanged(ListSelectionEvent e) { 768 int[] rows = mergedEntriesTable.getSelectedRows(); 769 setEnabled(rows.length > 0); 770 } 771 } 772 773 private interface FreezeActionProperties { 774 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 775 } 776 777 /** 778 * Action for freezing the current state of the list merger 779 * 780 */ 781 private final class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 782 783 private FreezeAction() { 784 putValue(Action.NAME, tr("Freeze")); 785 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 786 putValue(PROP_SELECTED, Boolean.FALSE); 787 setEnabled(true); 788 } 789 790 @Override 791 public void actionPerformed(ActionEvent arg0) { 792 // do nothing 793 } 794 795 /** 796 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 797 * such that the action gets notified about item state changes and the button gets 798 * notified about selection state changes of the action. 799 * 800 * @param btn a toggle button 801 */ 802 public void adapt(final JToggleButton btn) { 803 btn.addItemListener(this); 804 addPropertyChangeListener(evt -> { 805 if (evt.getPropertyName().equals(PROP_SELECTED)) { 806 btn.setSelected((Boolean) evt.getNewValue()); 807 } 808 }); 809 } 810 811 @Override 812 public void itemStateChanged(ItemEvent e) { 813 int state = e.getStateChange(); 814 if (state == ItemEvent.SELECTED) { 815 putValue(Action.NAME, tr("Unfreeze")); 816 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 817 model.setFrozen(true); 818 } else if (state == ItemEvent.DESELECTED) { 819 putValue(Action.NAME, tr("Freeze")); 820 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 821 model.setFrozen(false); 822 } 823 boolean isSelected = (Boolean) getValue(PROP_SELECTED); 824 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 825 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 826 } 827 828 } 829 } 830 831 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) { 832 myEntriesTable.getSelectionModel().clearSelection(); 833 myEntriesTable.setEnabled(!newValue); 834 theirEntriesTable.getSelectionModel().clearSelection(); 835 theirEntriesTable.setEnabled(!newValue); 836 mergedEntriesTable.getSelectionModel().clearSelection(); 837 mergedEntriesTable.setEnabled(!newValue); 838 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 839 if (newValue) { 840 lblFrozenState.setText( 841 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 842 freezeAction.getValue(Action.NAME)) 843 ); 844 } else { 845 lblFrozenState.setText( 846 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 847 freezeAction.getValue(Action.NAME)) 848 ); 849 } 850 } 851 852 @Override 853 public void propertyChange(PropertyChangeEvent evt) { 854 if (evt.getPropertyName().equals(AbstractListMergeModel.FROZEN_PROP)) { 855 handlePropertyChangeFrozen((Boolean) evt.getOldValue(), (Boolean) evt.getNewValue()); 856 } 857 } 858 859 /** 860 * Returns the model. 861 * @return the model 862 */ 863 public AbstractListMergeModel<T, C> getModel() { 864 return model; 865 } 866 867 @Override 868 public void stateChanged(ChangeEvent e) { 869 lblMyVersion.setText( 870 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) 871 ); 872 lblMergedVersion.setText( 873 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) 874 ); 875 lblTheirVersion.setText( 876 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) 877 ); 878 } 879 880 /** 881 * Adds all registered listeners by this merger 882 * @see #unregisterListeners() 883 * @since 10454 884 */ 885 public void registerListeners() { 886 myEntriesTable.registerListeners(); 887 mergedEntriesTable.registerListeners(); 888 theirEntriesTable.registerListeners(); 889 } 890 891 /** 892 * Removes all registered listeners by this merger 893 * @since 10454 894 */ 895 public void unregisterListeners() { 896 myEntriesTable.unregisterListeners(); 897 mergedEntriesTable.unregisterListeners(); 898 theirEntriesTable.unregisterListeners(); 899 } 900 901 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 902 if (primitive != null) { 903 Iterable<OsmDataLayer> layers = Main.getLayerManager().getLayersOfType(OsmDataLayer.class); 904 // Find layer with same dataset 905 for (OsmDataLayer layer : layers) { 906 if (layer.data == primitive.getDataSet()) { 907 return layer; 908 } 909 } 910 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 911 for (OsmDataLayer layer : layers) { 912 final Collection<? extends OsmPrimitive> collection; 913 if (primitive instanceof Way) { 914 collection = layer.data.getWays(); 915 } else if (primitive instanceof Relation) { 916 collection = layer.data.getRelations(); 917 } else { 918 collection = layer.data.allPrimitives(); 919 } 920 for (OsmPrimitive p : collection) { 921 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 922 return layer; 923 } 924 } 925 } 926 } 927 return null; 928 } 929 930 @Override 931 public void decideRemaining(MergeDecisionType decision) { 932 if (!model.isFrozen()) { 933 model.copyAll(MergeDecisionType.KEEP_MINE.equals(decision) ? ListRole.MY_ENTRIES : ListRole.THEIR_ENTRIES); 934 model.setFrozen(true); 935 } 936 } 937}