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.JButton; 021import javax.swing.JCheckBox; 022import javax.swing.JLabel; 023import javax.swing.JPanel; 024import javax.swing.JScrollPane; 025import javax.swing.JTable; 026import javax.swing.JToggleButton; 027import javax.swing.event.ChangeEvent; 028import javax.swing.event.ChangeListener; 029import javax.swing.event.ListSelectionEvent; 030import javax.swing.event.ListSelectionListener; 031 032import org.openstreetmap.josm.command.conflict.ConflictResolveCommand; 033import org.openstreetmap.josm.data.osm.OsmPrimitive; 034import org.openstreetmap.josm.data.osm.PrimitiveId; 035import org.openstreetmap.josm.data.osm.Relation; 036import org.openstreetmap.josm.data.osm.Way; 037import org.openstreetmap.josm.gui.MainApplication; 038import org.openstreetmap.josm.gui.layer.OsmDataLayer; 039import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer; 040import org.openstreetmap.josm.gui.widgets.JosmComboBox; 041import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 042import org.openstreetmap.josm.tools.ImageProvider; 043import org.openstreetmap.josm.tools.ImageResource; 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 ImageResource icon = new ImageProvider("dialogs/conflict", iconName).getResource(); 425 if (icon == null) { 426 putValue(Action.NAME, actionName); 427 } else { 428 icon.attachImageIcon(this, true); 429 } 430 putValue(Action.SHORT_DESCRIPTION, shortDescription); 431 setEnabled(false); 432 } 433 } 434 435 /** 436 * Action for copying selected nodes in the list of my nodes to the list of merged 437 * nodes. Inserts the nodes at the beginning of the list of merged nodes. 438 */ 439 class CopyStartLeftAction extends CopyAction { 440 441 CopyStartLeftAction() { 442 super(/* ICON(dialogs/conflict/)*/ "copystartleft", tr("> top"), 443 tr("Copy my selected nodes to the start of the merged node list")); 444 } 445 446 @Override 447 public void actionPerformed(ActionEvent e) { 448 model.copyMyToTop(myEntriesTable.getSelectedRows()); 449 } 450 451 @Override 452 public void valueChanged(ListSelectionEvent e) { 453 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 454 } 455 } 456 457 /** 458 * Action for copying selected nodes in the list of my nodes to the list of merged 459 * nodes. Inserts the nodes at the end of the list of merged nodes. 460 */ 461 class CopyEndLeftAction extends CopyAction { 462 463 CopyEndLeftAction() { 464 super(/* ICON(dialogs/conflict/)*/ "copyendleft", tr("> bottom"), 465 tr("Copy my selected elements to the end of the list of merged elements.")); 466 } 467 468 @Override 469 public void actionPerformed(ActionEvent e) { 470 model.copyMyToEnd(myEntriesTable.getSelectedRows()); 471 } 472 473 @Override 474 public void valueChanged(ListSelectionEvent e) { 475 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 476 } 477 } 478 479 /** 480 * Action for copying selected nodes in the list of my nodes to the list of merged 481 * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 482 */ 483 class CopyBeforeCurrentLeftAction extends CopyAction { 484 485 CopyBeforeCurrentLeftAction() { 486 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentleft", tr("> before"), 487 tr("Copy my selected elements before the first selected element in the list of merged elements.")); 488 } 489 490 @Override 491 public void actionPerformed(ActionEvent e) { 492 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 493 if (mergedRows.length == 0) 494 return; 495 int[] myRows = myEntriesTable.getSelectedRows(); 496 int current = mergedRows[0]; 497 model.copyMyBeforeCurrent(myRows, current); 498 } 499 500 @Override 501 public void valueChanged(ListSelectionEvent e) { 502 setEnabled( 503 !myEntriesTable.getSelectionModel().isSelectionEmpty() 504 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 505 ); 506 } 507 } 508 509 /** 510 * Action for copying selected nodes in the list of my nodes to the list of merged 511 * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 512 */ 513 class CopyAfterCurrentLeftAction extends CopyAction { 514 515 CopyAfterCurrentLeftAction() { 516 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentleft", tr("> after"), 517 tr("Copy my selected elements after the first selected element in the list of merged elements.")); 518 } 519 520 @Override 521 public void actionPerformed(ActionEvent e) { 522 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 523 if (mergedRows.length == 0) 524 return; 525 int[] myRows = myEntriesTable.getSelectedRows(); 526 int current = mergedRows[0]; 527 model.copyMyAfterCurrent(myRows, current); 528 } 529 530 @Override 531 public void valueChanged(ListSelectionEvent e) { 532 setEnabled( 533 !myEntriesTable.getSelectionModel().isSelectionEmpty() 534 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 535 ); 536 } 537 } 538 539 class CopyStartRightAction extends CopyAction { 540 541 CopyStartRightAction() { 542 super(/* ICON(dialogs/conflict/)*/ "copystartright", tr("< top"), 543 tr("Copy their selected element to the start of the list of merged elements.")); 544 } 545 546 @Override 547 public void actionPerformed(ActionEvent e) { 548 model.copyTheirToTop(theirEntriesTable.getSelectedRows()); 549 } 550 551 @Override 552 public void valueChanged(ListSelectionEvent e) { 553 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 554 } 555 } 556 557 class CopyEndRightAction extends CopyAction { 558 559 CopyEndRightAction() { 560 super(/* ICON(dialogs/conflict/)*/ "copyendright", tr("< bottom"), 561 tr("Copy their selected elements to the end of the list of merged elements.")); 562 } 563 564 @Override 565 public void actionPerformed(ActionEvent arg0) { 566 model.copyTheirToEnd(theirEntriesTable.getSelectedRows()); 567 } 568 569 @Override 570 public void valueChanged(ListSelectionEvent e) { 571 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 572 } 573 } 574 575 class CopyBeforeCurrentRightAction extends CopyAction { 576 577 CopyBeforeCurrentRightAction() { 578 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentright", tr("< before"), 579 tr("Copy their selected elements before the first selected element in the list of merged elements.")); 580 } 581 582 @Override 583 public void actionPerformed(ActionEvent e) { 584 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 585 if (mergedRows.length == 0) 586 return; 587 int[] myRows = theirEntriesTable.getSelectedRows(); 588 int current = mergedRows[0]; 589 model.copyTheirBeforeCurrent(myRows, current); 590 } 591 592 @Override 593 public void valueChanged(ListSelectionEvent e) { 594 setEnabled( 595 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 596 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 597 ); 598 } 599 } 600 601 class CopyAfterCurrentRightAction extends CopyAction { 602 603 CopyAfterCurrentRightAction() { 604 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentright", tr("< after"), 605 tr("Copy their selected element after the first selected element in the list of merged elements")); 606 } 607 608 @Override 609 public void actionPerformed(ActionEvent e) { 610 int[] mergedRows = mergedEntriesTable.getSelectedRows(); 611 if (mergedRows.length == 0) 612 return; 613 int[] myRows = theirEntriesTable.getSelectedRows(); 614 int current = mergedRows[0]; 615 model.copyTheirAfterCurrent(myRows, current); 616 } 617 618 @Override 619 public void valueChanged(ListSelectionEvent e) { 620 setEnabled( 621 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 622 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 623 ); 624 } 625 } 626 627 class CopyAllLeft extends AbstractAction implements ChangeListener, PropertyChangeListener { 628 629 CopyAllLeft() { 630 new ImageProvider("dialogs/conflict", "useallleft").getResource().attachImageIcon(this, true); 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 new ImageProvider("dialogs/conflict", "useallright").getResource().attachImageIcon(this, true); 659 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 660 } 661 662 @Override 663 public void actionPerformed(ActionEvent arg0) { 664 model.copyAll(ListRole.THEIR_ENTRIES); 665 model.setFrozen(true); 666 } 667 668 private void updateEnabledState() { 669 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 670 } 671 672 @Override 673 public void stateChanged(ChangeEvent e) { 674 updateEnabledState(); 675 } 676 677 @Override 678 public void propertyChange(PropertyChangeEvent evt) { 679 updateEnabledState(); 680 } 681 } 682 683 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 684 685 MoveUpMergedAction() { 686 ImageResource icon = new ImageProvider("dialogs/conflict", "moveup").getResource(); 687 if (icon == null) { 688 putValue(Action.NAME, tr("Up")); 689 } else { 690 icon.attachImageIcon(this, true); 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 ImageResource icon = new ImageProvider("dialogs/conflict", "movedown").getResource(); 720 if (icon == null) { 721 putValue(Action.NAME, tr("Down")); 722 } else { 723 icon.attachImageIcon(this, true); 724 } 725 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 726 setEnabled(false); 727 } 728 729 @Override 730 public void actionPerformed(ActionEvent arg0) { 731 int[] rows = mergedEntriesTable.getSelectedRows(); 732 model.moveDownMerged(rows); 733 } 734 735 @Override 736 public void valueChanged(ListSelectionEvent e) { 737 int[] rows = mergedEntriesTable.getSelectedRows(); 738 setEnabled(rows.length > 0 739 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 740 ); 741 } 742 } 743 744 /** 745 * Action for removing the selected entries in the list of merged entries 746 * from the list of merged entries. 747 * 748 */ 749 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 750 751 RemoveMergedAction() { 752 ImageResource icon = new ImageProvider("dialogs/conflict", "remove").getResource(); 753 if (icon == null) { 754 putValue(Action.NAME, tr("Remove")); 755 } else { 756 icon.attachImageIcon(this, true); 757 } 758 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 759 setEnabled(false); 760 } 761 762 @Override 763 public void actionPerformed(ActionEvent arg0) { 764 int[] rows = mergedEntriesTable.getSelectedRows(); 765 model.removeMerged(rows); 766 } 767 768 @Override 769 public void valueChanged(ListSelectionEvent e) { 770 int[] rows = mergedEntriesTable.getSelectedRows(); 771 setEnabled(rows.length > 0); 772 } 773 } 774 775 private interface FreezeActionProperties { 776 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 777 } 778 779 /** 780 * Action for freezing the current state of the list merger 781 * 782 */ 783 private final class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 784 785 private FreezeAction() { 786 putValue(Action.NAME, tr("Freeze")); 787 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 788 putValue(PROP_SELECTED, Boolean.FALSE); 789 setEnabled(true); 790 } 791 792 @Override 793 public void actionPerformed(ActionEvent arg0) { 794 // do nothing 795 } 796 797 /** 798 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 799 * such that the action gets notified about item state changes and the button gets 800 * notified about selection state changes of the action. 801 * 802 * @param btn a toggle button 803 */ 804 public void adapt(final JToggleButton btn) { 805 btn.addItemListener(this); 806 addPropertyChangeListener(evt -> { 807 if (evt.getPropertyName().equals(PROP_SELECTED)) { 808 btn.setSelected((Boolean) evt.getNewValue()); 809 } 810 }); 811 } 812 813 @Override 814 public void itemStateChanged(ItemEvent e) { 815 int state = e.getStateChange(); 816 if (state == ItemEvent.SELECTED) { 817 putValue(Action.NAME, tr("Unfreeze")); 818 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 819 model.setFrozen(true); 820 } else if (state == ItemEvent.DESELECTED) { 821 putValue(Action.NAME, tr("Freeze")); 822 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 823 model.setFrozen(false); 824 } 825 boolean isSelected = (Boolean) getValue(PROP_SELECTED); 826 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 827 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 828 } 829 830 } 831 } 832 833 protected void handlePropertyChangeFrozen(boolean newValue) { 834 myEntriesTable.getSelectionModel().clearSelection(); 835 myEntriesTable.setEnabled(!newValue); 836 theirEntriesTable.getSelectionModel().clearSelection(); 837 theirEntriesTable.setEnabled(!newValue); 838 mergedEntriesTable.getSelectionModel().clearSelection(); 839 mergedEntriesTable.setEnabled(!newValue); 840 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 841 if (newValue) { 842 lblFrozenState.setText( 843 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 844 freezeAction.getValue(Action.NAME)) 845 ); 846 } else { 847 lblFrozenState.setText( 848 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 849 freezeAction.getValue(Action.NAME)) 850 ); 851 } 852 } 853 854 @Override 855 public void propertyChange(PropertyChangeEvent evt) { 856 if (evt.getPropertyName().equals(AbstractListMergeModel.FROZEN_PROP)) { 857 handlePropertyChangeFrozen((Boolean) evt.getNewValue()); 858 } 859 } 860 861 /** 862 * Returns the model. 863 * @return the model 864 */ 865 public AbstractListMergeModel<T, C> getModel() { 866 return model; 867 } 868 869 @Override 870 public void stateChanged(ChangeEvent e) { 871 lblMyVersion.setText( 872 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) 873 ); 874 lblMergedVersion.setText( 875 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) 876 ); 877 lblTheirVersion.setText( 878 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) 879 ); 880 } 881 882 /** 883 * Adds all registered listeners by this merger 884 * @see #unregisterListeners() 885 * @since 10454 886 */ 887 public void registerListeners() { 888 myEntriesTable.registerListeners(); 889 mergedEntriesTable.registerListeners(); 890 theirEntriesTable.registerListeners(); 891 } 892 893 /** 894 * Removes all registered listeners by this merger 895 * @since 10454 896 */ 897 public void unregisterListeners() { 898 myEntriesTable.unregisterListeners(); 899 mergedEntriesTable.unregisterListeners(); 900 theirEntriesTable.unregisterListeners(); 901 } 902 903 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 904 if (primitive != null) { 905 Iterable<OsmDataLayer> layers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class); 906 // Find layer with same dataset 907 for (OsmDataLayer layer : layers) { 908 if (layer.data == primitive.getDataSet()) { 909 return layer; 910 } 911 } 912 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 913 for (OsmDataLayer layer : layers) { 914 final Collection<? extends OsmPrimitive> collection; 915 if (primitive instanceof Way) { 916 collection = layer.data.getWays(); 917 } else if (primitive instanceof Relation) { 918 collection = layer.data.getRelations(); 919 } else { 920 collection = layer.data.allPrimitives(); 921 } 922 for (OsmPrimitive p : collection) { 923 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 924 return layer; 925 } 926 } 927 } 928 } 929 return null; 930 } 931 932 @Override 933 public void decideRemaining(MergeDecisionType decision) { 934 if (!model.isFrozen()) { 935 model.copyAll(MergeDecisionType.KEEP_MINE.equals(decision) ? ListRole.MY_ENTRIES : ListRole.THEIR_ENTRIES); 936 model.setFrozen(true); 937 } 938 } 939}