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