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); 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(/* ICON(dialogs/conflict/)*/ "copystartleft", tr("> top"), 438 tr("Copy my selected nodes to the start of the merged node list")); 439 } 440 441 @Override 442 public void actionPerformed(ActionEvent e) { 443 model.copyMyToTop(myEntriesTable.getSelectedRows()); 444 } 445 446 @Override 447 public void valueChanged(ListSelectionEvent e) { 448 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 449 } 450 } 451 452 /** 453 * Action for copying selected nodes in the list of my nodes to the list of merged 454 * nodes. Inserts the nodes at the end of the list of merged nodes. 455 */ 456 class CopyEndLeftAction extends CopyAction { 457 458 public CopyEndLeftAction() { 459 super(/* ICON(dialogs/conflict/)*/ "copyendleft", tr("> bottom"), 460 tr("Copy my selected elements to the end of the list of merged elements.")); 461 } 462 463 @Override 464 public void actionPerformed(ActionEvent e) { 465 model.copyMyToEnd(myEntriesTable.getSelectedRows()); 466 } 467 468 @Override 469 public void valueChanged(ListSelectionEvent e) { 470 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 471 } 472 } 473 474 /** 475 * Action for copying selected nodes in the list of my nodes to the list of merged 476 * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 477 */ 478 class CopyBeforeCurrentLeftAction extends CopyAction { 479 480 public CopyBeforeCurrentLeftAction() { 481 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentleft", tr("> before"), 482 tr("Copy my selected elements before the first selected element in the list of merged elements.")); 483 } 484 485 @Override 486 public void actionPerformed(ActionEvent e) { 487 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 488 if (mergedRows == null || mergedRows.length == 0) 489 return; 490 int [] myRows = myEntriesTable.getSelectedRows(); 491 int current = mergedRows[0]; 492 model.copyMyBeforeCurrent(myRows, current); 493 } 494 495 @Override 496 public void valueChanged(ListSelectionEvent e) { 497 setEnabled( 498 !myEntriesTable.getSelectionModel().isSelectionEmpty() 499 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 500 ); 501 } 502 } 503 504 /** 505 * Action for copying selected nodes in the list of my nodes to the list of merged 506 * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 507 */ 508 class CopyAfterCurrentLeftAction extends CopyAction { 509 510 public CopyAfterCurrentLeftAction() { 511 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentleft", tr("> after"), 512 tr("Copy my selected elements after the first selected element in the list of merged elements.")); 513 } 514 515 @Override 516 public void actionPerformed(ActionEvent e) { 517 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 518 if (mergedRows == null || mergedRows.length == 0) 519 return; 520 int [] myRows = myEntriesTable.getSelectedRows(); 521 int current = mergedRows[0]; 522 model.copyMyAfterCurrent(myRows, current); 523 } 524 525 @Override 526 public void valueChanged(ListSelectionEvent e) { 527 setEnabled( 528 !myEntriesTable.getSelectionModel().isSelectionEmpty() 529 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 530 ); 531 } 532 } 533 534 class CopyStartRightAction extends CopyAction { 535 536 public CopyStartRightAction() { 537 super(/* ICON(dialogs/conflict/)*/ "copystartright", tr("< top"), 538 tr("Copy their selected element to the start of the list of merged elements.")); 539 } 540 541 @Override 542 public void actionPerformed(ActionEvent e) { 543 model.copyTheirToTop(theirEntriesTable.getSelectedRows()); 544 } 545 546 @Override 547 public void valueChanged(ListSelectionEvent e) { 548 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 549 } 550 } 551 552 class CopyEndRightAction extends CopyAction { 553 554 public CopyEndRightAction() { 555 super(/* ICON(dialogs/conflict/)*/ "copyendright", tr("< bottom"), 556 tr("Copy their selected elements to the end of the list of merged elements.")); 557 } 558 559 @Override 560 public void actionPerformed(ActionEvent arg0) { 561 model.copyTheirToEnd(theirEntriesTable.getSelectedRows()); 562 } 563 564 @Override 565 public void valueChanged(ListSelectionEvent e) { 566 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 567 } 568 } 569 570 class CopyBeforeCurrentRightAction extends CopyAction { 571 572 public CopyBeforeCurrentRightAction() { 573 super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentright", tr("< before"), 574 tr("Copy their selected elements before the first selected element in the list of merged elements.")); 575 } 576 577 @Override 578 public void actionPerformed(ActionEvent e) { 579 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 580 if (mergedRows == null || mergedRows.length == 0) 581 return; 582 int [] myRows = theirEntriesTable.getSelectedRows(); 583 int current = mergedRows[0]; 584 model.copyTheirBeforeCurrent(myRows, current); 585 } 586 587 @Override 588 public void valueChanged(ListSelectionEvent e) { 589 setEnabled( 590 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 591 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 592 ); 593 } 594 } 595 596 class CopyAfterCurrentRightAction extends CopyAction { 597 598 public CopyAfterCurrentRightAction() { 599 super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentright", tr("< after"), 600 tr("Copy their selected element after the first selected element in the list of merged elements")); 601 } 602 603 @Override 604 public void actionPerformed(ActionEvent e) { 605 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 606 if (mergedRows == null || mergedRows.length == 0) 607 return; 608 int [] myRows = theirEntriesTable.getSelectedRows(); 609 int current = mergedRows[0]; 610 model.copyTheirAfterCurrent(myRows, current); 611 } 612 613 @Override 614 public void valueChanged(ListSelectionEvent e) { 615 setEnabled( 616 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 617 && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() 618 ); 619 } 620 } 621 622 class CopyAllLeft extends AbstractAction implements Observer, PropertyChangeListener { 623 624 public CopyAllLeft() { 625 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft.png"); 626 putValue(Action.SMALL_ICON, icon); 627 putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); 628 } 629 630 @Override 631 public void actionPerformed(ActionEvent arg0) { 632 model.copyAll(ListRole.MY_ENTRIES); 633 model.setFrozen(true); 634 } 635 636 private void updateEnabledState() { 637 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 638 } 639 640 @Override 641 public void update(Observable o, Object arg) { 642 updateEnabledState(); 643 } 644 645 @Override 646 public void propertyChange(PropertyChangeEvent evt) { 647 updateEnabledState(); 648 } 649 } 650 651 class CopyAllRight extends AbstractAction implements Observer, PropertyChangeListener { 652 653 public CopyAllRight() { 654 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright.png"); 655 putValue(Action.SMALL_ICON, icon); 656 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 657 } 658 659 @Override 660 public void actionPerformed(ActionEvent arg0) { 661 model.copyAll(ListRole.THEIR_ENTRIES); 662 model.setFrozen(true); 663 } 664 665 private void updateEnabledState() { 666 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 667 } 668 669 @Override 670 public void update(Observable o, Object arg) { 671 updateEnabledState(); 672 } 673 674 @Override 675 public void propertyChange(PropertyChangeEvent evt) { 676 updateEnabledState(); 677 } 678 } 679 680 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 681 682 public MoveUpMergedAction() { 683 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png"); 684 putValue(Action.SMALL_ICON, icon); 685 if (icon == null) { 686 putValue(Action.NAME, tr("Up")); 687 } 688 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position.")); 689 setEnabled(false); 690 } 691 692 @Override 693 public void actionPerformed(ActionEvent arg0) { 694 int [] rows = mergedEntriesTable.getSelectedRows(); 695 model.moveUpMerged(rows); 696 } 697 698 @Override 699 public void valueChanged(ListSelectionEvent e) { 700 int [] rows = mergedEntriesTable.getSelectedRows(); 701 setEnabled( 702 rows != null 703 && rows.length > 0 704 && rows[0] != 0 705 ); 706 } 707 } 708 709 /** 710 * Action for moving the currently selected entries in the list of merged entries 711 * one position down 712 * 713 */ 714 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { 715 716 public MoveDownMergedAction() { 717 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png"); 718 putValue(Action.SMALL_ICON, icon); 719 if (icon == null) { 720 putValue(Action.NAME, tr("Down")); 721 } 722 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 723 setEnabled(false); 724 } 725 726 @Override 727 public void actionPerformed(ActionEvent arg0) { 728 int [] rows = mergedEntriesTable.getSelectedRows(); 729 model.moveDownMerged(rows); 730 } 731 732 @Override 733 public void valueChanged(ListSelectionEvent e) { 734 int [] rows = mergedEntriesTable.getSelectedRows(); 735 setEnabled( 736 rows != null 737 && rows.length > 0 738 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 739 ); 740 } 741 } 742 743 /** 744 * Action for removing the selected entries in the list of merged entries 745 * from the list of merged entries. 746 * 747 */ 748 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 749 750 public RemoveMergedAction() { 751 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png"); 752 putValue(Action.SMALL_ICON, icon); 753 if (icon == null) { 754 putValue(Action.NAME, tr("Remove")); 755 } 756 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 757 setEnabled(false); 758 } 759 760 @Override 761 public void actionPerformed(ActionEvent arg0) { 762 int [] rows = mergedEntriesTable.getSelectedRows(); 763 model.removeMerged(rows); 764 } 765 766 @Override 767 public void valueChanged(ListSelectionEvent e) { 768 int [] rows = mergedEntriesTable.getSelectedRows(); 769 setEnabled( 770 rows != null 771 && rows.length > 0 772 ); 773 } 774 } 775 776 public static interface FreezeActionProperties { 777 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 778 } 779 780 /** 781 * Action for freezing the current state of the list merger 782 * 783 */ 784 class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 785 786 public FreezeAction() { 787 putValue(Action.NAME, tr("Freeze")); 788 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 789 putValue(PROP_SELECTED, false); 790 setEnabled(true); 791 } 792 793 @Override 794 public void actionPerformed(ActionEvent arg0) { 795 // do nothing 796 } 797 798 /** 799 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 800 * such that the action gets notified about item state changes and the button gets 801 * notified about selection state changes of the action. 802 * 803 * @param btn a toggle button 804 */ 805 public void adapt(final JToggleButton btn) { 806 btn.addItemListener(this); 807 addPropertyChangeListener( 808 new PropertyChangeListener() { 809 @Override 810 public void propertyChange(PropertyChangeEvent evt) { 811 if (evt.getPropertyName().equals(PROP_SELECTED)) { 812 btn.setSelected((Boolean)evt.getNewValue()); 813 } 814 } 815 } 816 ); 817 } 818 819 @Override 820 public void itemStateChanged(ItemEvent e) { 821 int state = e.getStateChange(); 822 if (state == ItemEvent.SELECTED) { 823 putValue(Action.NAME, tr("Unfreeze")); 824 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 825 model.setFrozen(true); 826 } else if (state == ItemEvent.DESELECTED) { 827 putValue(Action.NAME, tr("Freeze")); 828 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 829 model.setFrozen(false); 830 } 831 boolean isSelected = (Boolean)getValue(PROP_SELECTED); 832 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 833 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 834 } 835 836 } 837 } 838 839 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) { 840 myEntriesTable.getSelectionModel().clearSelection(); 841 myEntriesTable.setEnabled(!newValue); 842 theirEntriesTable.getSelectionModel().clearSelection(); 843 theirEntriesTable.setEnabled(!newValue); 844 mergedEntriesTable.getSelectionModel().clearSelection(); 845 mergedEntriesTable.setEnabled(!newValue); 846 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 847 if (newValue) { 848 lblFrozenState.setText( 849 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 850 freezeAction.getValue(Action.NAME)) 851 ); 852 } else { 853 lblFrozenState.setText( 854 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 855 freezeAction.getValue(Action.NAME)) 856 ); 857 } 858 } 859 860 @Override 861 public void propertyChange(PropertyChangeEvent evt) { 862 if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) { 863 handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue()); 864 } 865 } 866 867 public ListMergeModel<T> getModel() { 868 return model; 869 } 870 871 @Override 872 public void update(Observable o, Object arg) { 873 lblMyVersion.setText( 874 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) 875 ); 876 lblMergedVersion.setText( 877 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) 878 ); 879 lblTheirVersion.setText( 880 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) 881 ); 882 } 883 884 public void unlinkAsListener() { 885 myEntriesTable.unlinkAsListener(); 886 mergedEntriesTable.unlinkAsListener(); 887 theirEntriesTable.unlinkAsListener(); 888 } 889 890 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 891 if (primitive != null) { 892 Iterable<OsmDataLayer> layers = Main.map.mapView.getLayersOfType(OsmDataLayer.class); 893 // Find layer with same dataset 894 for (OsmDataLayer layer : layers) { 895 if (layer.data == primitive.getDataSet()) { 896 return layer; 897 } 898 } 899 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 900 for (OsmDataLayer layer : layers) { 901 final Collection<? extends OsmPrimitive> collection; 902 if (primitive instanceof Way) { 903 collection = layer.data.getWays(); 904 } else if (primitive instanceof Relation) { 905 collection = layer.data.getRelations(); 906 } else { 907 collection = layer.data.allPrimitives(); 908 } 909 for (OsmPrimitive p : collection) { 910 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 911 return layer; 912 } 913 } 914 } 915 } 916 return null; 917 } 918}