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; 017import java.util.Observable; 018import java.util.Observer; 019 020import javax.swing.AbstractAction; 021import javax.swing.Action; 022import javax.swing.ImageIcon; 023import javax.swing.JButton; 024import javax.swing.JCheckBox; 025import javax.swing.JLabel; 026import javax.swing.JPanel; 027import javax.swing.JScrollPane; 028import javax.swing.JTable; 029import javax.swing.JToggleButton; 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 */ 050public abstract class ListMerger<T extends PrimitiveId> extends JPanel implements PropertyChangeListener, Observer { 051 protected OsmPrimitivesTable myEntriesTable; 052 protected OsmPrimitivesTable mergedEntriesTable; 053 protected OsmPrimitivesTable theirEntriesTable; 054 055 protected ListMergeModel<T> model; 056 057 private CopyStartLeftAction copyStartLeftAction; 058 private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction; 059 private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction; 060 private CopyEndLeftAction copyEndLeftAction; 061 private CopyAllLeft copyAllLeft; 062 063 private CopyStartRightAction copyStartRightAction; 064 private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction; 065 private CopyAfterCurrentRightAction copyAfterCurrentRightAction; 066 private CopyEndRightAction copyEndRightAction; 067 private CopyAllRight copyAllRight; 068 069 private MoveUpMergedAction moveUpMergedAction; 070 private MoveDownMergedAction moveDownMergedAction; 071 private RemoveMergedAction removeMergedAction; 072 private FreezeAction freezeAction; 073 074 private AdjustmentSynchronizer adjustmentSynchronizer; 075 076 private JLabel lblMyVersion; 077 private JLabel lblMergedVersion; 078 private JLabel lblTheirVersion; 079 080 private JLabel lblFrozenState; 081 082 protected abstract JScrollPane buildMyElementsTable(); 083 protected abstract JScrollPane buildMergedElementsTable(); 084 protected abstract JScrollPane buildTheirElementsTable(); 085 086 protected JScrollPane embeddInScrollPane(JTable table) { 087 JScrollPane pane = new JScrollPane(table); 088 if (adjustmentSynchronizer == null) { 089 adjustmentSynchronizer = new AdjustmentSynchronizer(); 090 } 091 return pane; 092 } 093 094 protected void wireActionsToSelectionModels() { 095 myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction); 096 097 myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 098 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 099 100 myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 101 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 102 103 myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction); 104 105 theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction); 106 107 theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 108 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 109 110 theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 111 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 112 113 theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction); 114 115 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction); 116 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction); 117 mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction); 118 119 model.addObserver(copyAllLeft); 120 model.addObserver(copyAllRight); 121 model.addPropertyChangeListener(copyAllLeft); 122 model.addPropertyChangeListener(copyAllRight); 123 } 124 125 protected JPanel buildLeftButtonPanel() { 126 JPanel pnl = new JPanel(); 127 pnl.setLayout(new GridBagLayout()); 128 GridBagConstraints gc = new GridBagConstraints(); 129 130 gc.gridx = 0; 131 gc.gridy = 0; 132 copyStartLeftAction = new CopyStartLeftAction(); 133 JButton btn = new JButton(copyStartLeftAction); 134 btn.setName("button.copystartleft"); 135 pnl.add(btn, gc); 136 137 gc.gridx = 0; 138 gc.gridy = 1; 139 copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction(); 140 btn = new JButton(copyBeforeCurrentLeftAction); 141 btn.setName("button.copybeforecurrentleft"); 142 pnl.add(btn, gc); 143 144 gc.gridx = 0; 145 gc.gridy = 2; 146 copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction(); 147 btn = new JButton(copyAfterCurrentLeftAction); 148 btn.setName("button.copyaftercurrentleft"); 149 pnl.add(btn, gc); 150 151 gc.gridx = 0; 152 gc.gridy = 3; 153 copyEndLeftAction = new CopyEndLeftAction(); 154 btn = new JButton(copyEndLeftAction); 155 btn.setName("button.copyendleft"); 156 pnl.add(btn, gc); 157 158 gc.gridx = 0; 159 gc.gridy = 4; 160 copyAllLeft = new CopyAllLeft(); 161 btn = new JButton(copyAllLeft); 162 btn.setName("button.copyallleft"); 163 pnl.add(btn, gc); 164 165 return pnl; 166 } 167 168 protected JPanel buildRightButtonPanel() { 169 JPanel pnl = new JPanel(); 170 pnl.setLayout(new GridBagLayout()); 171 GridBagConstraints gc = new GridBagConstraints(); 172 173 gc.gridx = 0; 174 gc.gridy = 0; 175 copyStartRightAction = new CopyStartRightAction(); 176 pnl.add(new JButton(copyStartRightAction), gc); 177 178 gc.gridx = 0; 179 gc.gridy = 1; 180 copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction(); 181 pnl.add(new JButton(copyBeforeCurrentRightAction), gc); 182 183 gc.gridx = 0; 184 gc.gridy = 2; 185 copyAfterCurrentRightAction = new CopyAfterCurrentRightAction(); 186 pnl.add(new JButton(copyAfterCurrentRightAction), gc); 187 188 gc.gridx = 0; 189 gc.gridy = 3; 190 copyEndRightAction = new CopyEndRightAction(); 191 pnl.add(new JButton(copyEndRightAction), gc); 192 193 gc.gridx = 0; 194 gc.gridy = 4; 195 copyAllRight = new CopyAllRight(); 196 pnl.add(new JButton(copyAllRight), gc); 197 198 return pnl; 199 } 200 201 protected JPanel buildMergedListControlButtons() { 202 JPanel pnl = new JPanel(); 203 pnl.setLayout(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(); 232 panel.setLayout(new FlowLayout(FlowLayout.RIGHT)); 233 panel.add(new JLabel(tr("lock scrolling"))); 234 panel.add(cb); 235 return panel; 236 } 237 238 protected JPanel buildComparePairSelectionPanel() { 239 JPanel p = new JPanel(); 240 p.setLayout(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(); 250 p.setLayout(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(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 adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar()); 330 add(pane, gc); 331 332 gc.gridx = 1; 333 gc.gridy = 2; 334 gc.fill = GridBagConstraints.NONE; 335 gc.anchor = GridBagConstraints.CENTER; 336 gc.weightx = 0.0; 337 gc.weighty = 0.0; 338 add(buildLeftButtonPanel(), gc); 339 340 gc.gridx = 2; 341 gc.gridy = 2; 342 gc.fill = GridBagConstraints.BOTH; 343 gc.anchor = GridBagConstraints.FIRST_LINE_START; 344 gc.weightx = 0.33; 345 gc.weighty = 0.0; 346 pane = buildMergedElementsTable(); 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 adjustmentSynchronizer.adapt(cbLockTheirScrolling, pane.getVerticalScrollBar()); 366 add(pane, gc); 367 368 // ---------------------------------- 369 gc.gridx = 2; 370 gc.gridy = 3; 371 gc.gridwidth = 1; 372 gc.gridheight = 1; 373 gc.fill = GridBagConstraints.BOTH; 374 gc.anchor = GridBagConstraints.CENTER; 375 gc.weightx = 0.0; 376 gc.weighty = 0.0; 377 add(buildMergedListControlButtons(), gc); 378 379 // ----------------------------------- 380 gc.gridx = 0; 381 gc.gridy = 4; 382 gc.gridwidth = 2; 383 gc.gridheight = 1; 384 gc.fill = GridBagConstraints.HORIZONTAL; 385 gc.anchor = GridBagConstraints.LINE_START; 386 gc.weightx = 0.0; 387 gc.weighty = 0.0; 388 add(buildComparePairSelectionPanel(), gc); 389 390 gc.gridx = 2; 391 gc.gridy = 4; 392 gc.gridwidth = 3; 393 gc.gridheight = 1; 394 gc.fill = GridBagConstraints.HORIZONTAL; 395 gc.anchor = GridBagConstraints.LINE_START; 396 gc.weightx = 0.0; 397 gc.weighty = 0.0; 398 add(buildFrozeStateControlPanel(), gc); 399 400 wireActionsToSelectionModels(); 401 } 402 403 /** 404 * Constructs a new {@code ListMerger}. 405 * @param model 406 */ 407 public ListMerger(ListMergeModel<T> model) { 408 this.model = model; 409 model.addObserver(this); 410 build(); 411 model.addPropertyChangeListener(this); 412 } 413 414 /** 415 * Base class of all other Copy* inner classes. 416 */ 417 abstract class CopyAction extends AbstractAction implements ListSelectionListener { 418 419 protected CopyAction(String icon_name, String action_name, String short_description) { 420 ImageIcon icon = ImageProvider.get("dialogs/conflict", icon_name+".png"); 421 putValue(Action.SMALL_ICON, icon); 422 if (icon == null) { 423 putValue(Action.NAME, action_name); 424 } 425 putValue(Action.SHORT_DESCRIPTION, short_description); 426 setEnabled(false); 427 } 428 } 429 430 /** 431 * Action for copying selected nodes in the list of my nodes to the list of merged 432 * nodes. Inserts the nodes at the beginning of the list of merged nodes. 433 */ 434 class CopyStartLeftAction extends CopyAction { 435 436 public CopyStartLeftAction() { 437 super("copystartleft", tr("> top"), 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 public CopyEndLeftAction() { 458 super("copyendleft", tr("> bottom"), tr("Copy my selected elements to the end of the list of merged elements.")); 459 } 460 461 @Override 462 public void actionPerformed(ActionEvent e) { 463 model.copyMyToEnd(myEntriesTable.getSelectedRows()); 464 } 465 466 @Override 467 public void valueChanged(ListSelectionEvent e) { 468 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 469 } 470 } 471 472 /** 473 * Action for copying selected nodes in the list of my nodes to the list of merged 474 * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 475 */ 476 class CopyBeforeCurrentLeftAction extends CopyAction { 477 478 public CopyBeforeCurrentLeftAction() { 479 super("copybeforecurrentleft", tr("> before"), 480 tr("Copy my selected elements before the first selected element in the list of merged elements.")); 481 } 482 483 @Override 484 public void actionPerformed(ActionEvent e) { 485 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 486 if (mergedRows == null || mergedRows.length == 0) 487 return; 488 int [] myRows = myEntriesTable.getSelectedRows(); 489 int current = mergedRows[0]; 490 model.copyMyBeforeCurrent(myRows, current); 491 } 492 493 @Override 494 public void valueChanged(ListSelectionEvent e) { 495 setEnabled( 496 !myEntriesTable.getSelectionModel().isSelectionEmpty() 497 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 498 ); 499 } 500 } 501 502 /** 503 * Action for copying selected nodes in the list of my nodes to the list of merged 504 * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 505 */ 506 class CopyAfterCurrentLeftAction extends CopyAction { 507 508 public CopyAfterCurrentLeftAction() { 509 super("copyaftercurrentleft", tr("> after"), 510 tr("Copy my selected elements after the first selected element in the list of merged elements.")); 511 } 512 513 @Override 514 public void actionPerformed(ActionEvent e) { 515 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 516 if (mergedRows == null || mergedRows.length == 0) 517 return; 518 int [] myRows = myEntriesTable.getSelectedRows(); 519 int current = mergedRows[0]; 520 model.copyMyAfterCurrent(myRows, current); 521 } 522 523 @Override 524 public void valueChanged(ListSelectionEvent e) { 525 setEnabled( 526 !myEntriesTable.getSelectionModel().isSelectionEmpty() 527 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 528 ); 529 } 530 } 531 532 class CopyStartRightAction extends CopyAction { 533 534 public CopyStartRightAction() { 535 super("copystartright", tr("< top"), tr("Copy their selected element to the start of the list of merged elements.")); 536 } 537 538 @Override 539 public void actionPerformed(ActionEvent e) { 540 model.copyTheirToTop(theirEntriesTable.getSelectedRows()); 541 } 542 543 @Override 544 public void valueChanged(ListSelectionEvent e) { 545 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 546 } 547 } 548 549 class CopyEndRightAction extends CopyAction { 550 551 public CopyEndRightAction() { 552 super("copyendright", tr("< bottom"), tr("Copy their selected elements to the end of the list of merged elements.")); 553 } 554 555 @Override 556 public void actionPerformed(ActionEvent arg0) { 557 model.copyTheirToEnd(theirEntriesTable.getSelectedRows()); 558 } 559 560 @Override 561 public void valueChanged(ListSelectionEvent e) { 562 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 563 } 564 } 565 566 class CopyBeforeCurrentRightAction extends CopyAction { 567 568 public CopyBeforeCurrentRightAction() { 569 super("copybeforecurrentright", tr("< before"), 570 tr("Copy their selected elements before the first selected element in the list of merged elements.")); 571 } 572 573 @Override 574 public void actionPerformed(ActionEvent e) { 575 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 576 if (mergedRows == null || mergedRows.length == 0) 577 return; 578 int [] myRows = theirEntriesTable.getSelectedRows(); 579 int current = mergedRows[0]; 580 model.copyTheirBeforeCurrent(myRows, current); 581 } 582 583 @Override 584 public void valueChanged(ListSelectionEvent e) { 585 setEnabled( 586 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 587 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 588 ); 589 } 590 } 591 592 class CopyAfterCurrentRightAction extends CopyAction { 593 594 public CopyAfterCurrentRightAction() { 595 super("copyaftercurrentright", tr("< after"), 596 tr("Copy their selected element after the first selected element in the list of merged elements")); 597 } 598 599 @Override 600 public void actionPerformed(ActionEvent e) { 601 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 602 if (mergedRows == null || mergedRows.length == 0) 603 return; 604 int [] myRows = theirEntriesTable.getSelectedRows(); 605 int current = mergedRows[0]; 606 model.copyTheirAfterCurrent(myRows, current); 607 } 608 609 @Override 610 public void valueChanged(ListSelectionEvent e) { 611 setEnabled( 612 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 613 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 614 ); 615 } 616 } 617 618 class CopyAllLeft extends AbstractAction implements Observer, PropertyChangeListener { 619 620 public CopyAllLeft() { 621 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft.png"); 622 putValue(Action.SMALL_ICON, icon); 623 putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); 624 } 625 626 @Override 627 public void actionPerformed(ActionEvent arg0) { 628 model.copyAll(ListRole.MY_ENTRIES); 629 model.setFrozen(true); 630 } 631 632 private void updateEnabledState() { 633 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 634 } 635 636 @Override 637 public void update(Observable o, Object arg) { 638 updateEnabledState(); 639 } 640 641 @Override 642 public void propertyChange(PropertyChangeEvent evt) { 643 updateEnabledState(); 644 } 645 } 646 647 class CopyAllRight extends AbstractAction implements Observer, PropertyChangeListener { 648 649 public CopyAllRight() { 650 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright.png"); 651 putValue(Action.SMALL_ICON, icon); 652 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 653 } 654 655 @Override 656 public void actionPerformed(ActionEvent arg0) { 657 model.copyAll(ListRole.THEIR_ENTRIES); 658 model.setFrozen(true); 659 } 660 661 private void updateEnabledState() { 662 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 663 } 664 665 @Override 666 public void update(Observable o, Object arg) { 667 updateEnabledState(); 668 } 669 670 @Override 671 public void propertyChange(PropertyChangeEvent evt) { 672 updateEnabledState(); 673 } 674 } 675 676 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 677 678 public MoveUpMergedAction() { 679 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png"); 680 putValue(Action.SMALL_ICON, icon); 681 if (icon == null) { 682 putValue(Action.NAME, tr("Up")); 683 } 684 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position.")); 685 setEnabled(false); 686 } 687 688 @Override 689 public void actionPerformed(ActionEvent arg0) { 690 int [] rows = mergedEntriesTable.getSelectedRows(); 691 model.moveUpMerged(rows); 692 } 693 694 @Override 695 public void valueChanged(ListSelectionEvent e) { 696 int [] rows = mergedEntriesTable.getSelectedRows(); 697 setEnabled( 698 rows != null 699 && rows.length > 0 700 && rows[0] != 0 701 ); 702 } 703 } 704 705 /** 706 * Action for moving the currently selected entries in the list of merged entries 707 * one position down 708 * 709 */ 710 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { 711 712 public MoveDownMergedAction() { 713 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png"); 714 putValue(Action.SMALL_ICON, icon); 715 if (icon == null) { 716 putValue(Action.NAME, tr("Down")); 717 } 718 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 719 setEnabled(false); 720 } 721 722 @Override 723 public void actionPerformed(ActionEvent arg0) { 724 int [] rows = mergedEntriesTable.getSelectedRows(); 725 model.moveDownMerged(rows); 726 } 727 728 @Override 729 public void valueChanged(ListSelectionEvent e) { 730 int [] rows = mergedEntriesTable.getSelectedRows(); 731 setEnabled( 732 rows != null 733 && rows.length > 0 734 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 735 ); 736 } 737 } 738 739 /** 740 * Action for removing the selected entries in the list of merged entries 741 * from the list of merged entries. 742 * 743 */ 744 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 745 746 public RemoveMergedAction() { 747 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png"); 748 putValue(Action.SMALL_ICON, icon); 749 if (icon == null) { 750 putValue(Action.NAME, tr("Remove")); 751 } 752 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 753 setEnabled(false); 754 } 755 756 @Override 757 public void actionPerformed(ActionEvent arg0) { 758 int [] rows = mergedEntriesTable.getSelectedRows(); 759 model.removeMerged(rows); 760 } 761 762 @Override 763 public void valueChanged(ListSelectionEvent e) { 764 int [] rows = mergedEntriesTable.getSelectedRows(); 765 setEnabled( 766 rows != null 767 && rows.length > 0 768 ); 769 } 770 } 771 772 public static interface FreezeActionProperties { 773 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 774 } 775 776 /** 777 * Action for freezing the current state of the list merger 778 * 779 */ 780 class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 781 782 public FreezeAction() { 783 putValue(Action.NAME, tr("Freeze")); 784 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 785 putValue(PROP_SELECTED, false); 786 setEnabled(true); 787 } 788 789 @Override 790 public void actionPerformed(ActionEvent arg0) { 791 // do nothing 792 } 793 794 /** 795 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 796 * such that the action gets notified about item state changes and the button gets 797 * notified about selection state changes of the action. 798 * 799 * @param btn a toggle button 800 */ 801 public void adapt(final JToggleButton btn) { 802 btn.addItemListener(this); 803 addPropertyChangeListener( 804 new PropertyChangeListener() { 805 @Override 806 public void propertyChange(PropertyChangeEvent evt) { 807 if (evt.getPropertyName().equals(PROP_SELECTED)) { 808 btn.setSelected((Boolean)evt.getNewValue()); 809 } 810 } 811 } 812 ); 813 } 814 815 @Override 816 public void itemStateChanged(ItemEvent e) { 817 int state = e.getStateChange(); 818 if (state == ItemEvent.SELECTED) { 819 putValue(Action.NAME, tr("Unfreeze")); 820 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 821 model.setFrozen(true); 822 } else if (state == ItemEvent.DESELECTED) { 823 putValue(Action.NAME, tr("Freeze")); 824 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 825 model.setFrozen(false); 826 } 827 boolean isSelected = (Boolean)getValue(PROP_SELECTED); 828 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 829 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 830 } 831 832 } 833 } 834 835 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) { 836 myEntriesTable.getSelectionModel().clearSelection(); 837 myEntriesTable.setEnabled(!newValue); 838 theirEntriesTable.getSelectionModel().clearSelection(); 839 theirEntriesTable.setEnabled(!newValue); 840 mergedEntriesTable.getSelectionModel().clearSelection(); 841 mergedEntriesTable.setEnabled(!newValue); 842 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 843 if (newValue) { 844 lblFrozenState.setText( 845 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 846 freezeAction.getValue(Action.NAME)) 847 ); 848 } else { 849 lblFrozenState.setText( 850 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 851 freezeAction.getValue(Action.NAME)) 852 ); 853 } 854 } 855 856 @Override 857 public void propertyChange(PropertyChangeEvent evt) { 858 if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) { 859 handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue()); 860 } 861 } 862 863 public ListMergeModel<T> getModel() { 864 return model; 865 } 866 867 @Override 868 public void update(Observable o, Object arg) { 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 public void unlinkAsListener() { 881 myEntriesTable.unlinkAsListener(); 882 mergedEntriesTable.unlinkAsListener(); 883 theirEntriesTable.unlinkAsListener(); 884 } 885 886 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 887 if (primitive != null) { 888 Iterable<OsmDataLayer> layers = Main.map.mapView.getLayersOfType(OsmDataLayer.class); 889 // Find layer with same dataset 890 for (OsmDataLayer layer : layers) { 891 if (layer.data == primitive.getDataSet()) { 892 return layer; 893 } 894 } 895 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 896 for (OsmDataLayer layer : layers) { 897 final Collection<? extends OsmPrimitive> collection; 898 if (primitive instanceof Way) { 899 collection = layer.data.getWays(); 900 } else if (primitive instanceof Relation) { 901 collection = layer.data.getRelations(); 902 } else { 903 collection = layer.data.allPrimitives(); 904 } 905 for (OsmPrimitive p : collection) { 906 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 907 return layer; 908 } 909 } 910 } 911 } 912 return null; 913 } 914}