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