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.List; 018import java.util.Observable; 019import java.util.Observer; 020 021import javax.swing.AbstractAction; 022import javax.swing.Action; 023import javax.swing.ImageIcon; 024import javax.swing.JButton; 025import javax.swing.JCheckBox; 026import javax.swing.JLabel; 027import javax.swing.JPanel; 028import javax.swing.JScrollPane; 029import javax.swing.JTable; 030import javax.swing.JToggleButton; 031import javax.swing.event.ListSelectionEvent; 032import javax.swing.event.ListSelectionListener; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.data.osm.OsmPrimitive; 036import org.openstreetmap.josm.data.osm.PrimitiveId; 037import org.openstreetmap.josm.data.osm.Relation; 038import org.openstreetmap.josm.data.osm.Way; 039import org.openstreetmap.josm.gui.layer.OsmDataLayer; 040import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer; 041import org.openstreetmap.josm.gui.widgets.JosmComboBox; 042import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 043import org.openstreetmap.josm.tools.ImageProvider; 044 045/** 046 * A UI component for resolving conflicts in two lists of entries of type T. 047 * 048 * @param <T> the type of the entries 049 * @see ListMergeModel 050 */ 051public abstract class ListMerger<T extends PrimitiveId> extends JPanel implements PropertyChangeListener, Observer { 052 protected OsmPrimitivesTable myEntriesTable; 053 protected OsmPrimitivesTable mergedEntriesTable; 054 protected OsmPrimitivesTable theirEntriesTable; 055 056 protected 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 AdjustmentSynchronizer adjustmentSynchronizer; 076 077 private JLabel lblMyVersion; 078 private JLabel lblMergedVersion; 079 private JLabel lblTheirVersion; 080 081 private JLabel lblFrozenState; 082 083 abstract protected JScrollPane buildMyElementsTable(); 084 abstract protected JScrollPane buildMergedElementsTable(); 085 abstract protected JScrollPane buildTheirElementsTable(); 086 087 protected JScrollPane embeddInScrollPane(JTable table) { 088 JScrollPane pane = new JScrollPane(table); 089 pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 090 pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 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.addObserver(copyAllLeft); 123 model.addObserver(copyAllRight); 124 model.addPropertyChangeListener(copyAllLeft); 125 model.addPropertyChangeListener(copyAllRight); 126 } 127 128 protected JPanel buildLeftButtonPanel() { 129 JPanel pnl = new JPanel(); 130 pnl.setLayout(new GridBagLayout()); 131 GridBagConstraints gc = new GridBagConstraints(); 132 133 gc.gridx = 0; 134 gc.gridy = 0; 135 copyStartLeftAction = new CopyStartLeftAction(); 136 JButton btn = new JButton(copyStartLeftAction); 137 btn.setName("button.copystartleft"); 138 pnl.add(btn, gc); 139 140 gc.gridx = 0; 141 gc.gridy = 1; 142 copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction(); 143 btn = new JButton(copyBeforeCurrentLeftAction); 144 btn.setName("button.copybeforecurrentleft"); 145 pnl.add(btn, gc); 146 147 gc.gridx = 0; 148 gc.gridy = 2; 149 copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction(); 150 btn = new JButton(copyAfterCurrentLeftAction); 151 btn.setName("button.copyaftercurrentleft"); 152 pnl.add(btn, gc); 153 154 gc.gridx = 0; 155 gc.gridy = 3; 156 copyEndLeftAction = new CopyEndLeftAction(); 157 btn = new JButton(copyEndLeftAction); 158 btn.setName("button.copyendleft"); 159 pnl.add(btn, gc); 160 161 gc.gridx = 0; 162 gc.gridy = 4; 163 copyAllLeft = new CopyAllLeft(); 164 btn = new JButton(copyAllLeft); 165 btn.setName("button.copyallleft"); 166 pnl.add(btn, gc); 167 168 return pnl; 169 } 170 171 protected JPanel buildRightButtonPanel() { 172 JPanel pnl = new JPanel(); 173 pnl.setLayout(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(); 206 pnl.setLayout(new GridBagLayout()); 207 GridBagConstraints gc = new GridBagConstraints(); 208 209 gc.gridx = 0; 210 gc.gridy = 0; 211 gc.gridwidth = 1; 212 gc.gridheight = 1; 213 gc.fill = GridBagConstraints.HORIZONTAL; 214 gc.anchor = GridBagConstraints.CENTER; 215 gc.weightx = 0.3; 216 gc.weighty = 0.0; 217 moveUpMergedAction = new MoveUpMergedAction(); 218 pnl.add(new JButton(moveUpMergedAction), gc); 219 220 gc.gridx = 1; 221 gc.gridy = 0; 222 moveDownMergedAction = new MoveDownMergedAction(); 223 pnl.add(new JButton(moveDownMergedAction), gc); 224 225 gc.gridx = 2; 226 gc.gridy = 0; 227 removeMergedAction = new RemoveMergedAction(); 228 pnl.add(new JButton(removeMergedAction), gc); 229 230 return pnl; 231 } 232 233 protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) { 234 JPanel panel = new JPanel(); 235 panel.setLayout(new FlowLayout(FlowLayout.RIGHT)); 236 panel.add(new JLabel(tr("lock scrolling"))); 237 panel.add(cb); 238 return panel; 239 } 240 241 protected JPanel buildComparePairSelectionPanel() { 242 JPanel p = new JPanel(); 243 p.setLayout(new FlowLayout(FlowLayout.LEFT)); 244 p.add(new JLabel(tr("Compare "))); 245 JosmComboBox cbComparePair = new JosmComboBox(model.getComparePairListModel()); 246 cbComparePair.setRenderer(new ComparePairListCellRenderer()); 247 p.add(cbComparePair); 248 return p; 249 } 250 251 protected JPanel buildFrozeStateControlPanel() { 252 JPanel p = new JPanel(); 253 p.setLayout(new FlowLayout(FlowLayout.LEFT)); 254 lblFrozenState = new JLabel(); 255 p.add(lblFrozenState); 256 freezeAction = new FreezeAction(); 257 JToggleButton btn = new JToggleButton(freezeAction); 258 freezeAction.adapt(btn); 259 btn.setName("button.freeze"); 260 p.add(btn); 261 262 return p; 263 } 264 265 protected void build() { 266 setLayout(new GridBagLayout()); 267 GridBagConstraints gc = new GridBagConstraints(); 268 269 // ------------------ 270 gc.gridx = 0; 271 gc.gridy = 0; 272 gc.gridwidth = 1; 273 gc.gridheight = 1; 274 gc.fill = GridBagConstraints.NONE; 275 gc.anchor = GridBagConstraints.CENTER; 276 gc.weightx = 0.0; 277 gc.weighty = 0.0; 278 gc.insets = new Insets(10,0,0,0); 279 lblMyVersion = new JLabel(tr("My version")); 280 lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset")); 281 add(lblMyVersion, gc); 282 283 gc.gridx = 2; 284 gc.gridy = 0; 285 lblMergedVersion = new JLabel(tr("Merged version")); 286 lblMergedVersion.setToolTipText(tr("List of merged elements. They will replace the list of my elements when the merge decisions are applied.")); 287 add(lblMergedVersion, gc); 288 289 gc.gridx = 4; 290 gc.gridy = 0; 291 lblTheirVersion = new JLabel(tr("Their version")); 292 lblTheirVersion.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset")); 293 add(lblTheirVersion, gc); 294 295 // ------------------------------ 296 gc.gridx = 0; 297 gc.gridy = 1; 298 gc.gridwidth = 1; 299 gc.gridheight = 1; 300 gc.fill = GridBagConstraints.HORIZONTAL; 301 gc.anchor = GridBagConstraints.FIRST_LINE_START; 302 gc.weightx = 0.33; 303 gc.weighty = 0.0; 304 gc.insets = new Insets(0,0,0,0); 305 JCheckBox cbLockMyScrolling = new JCheckBox(); 306 cbLockMyScrolling.setName("checkbox.lockmyscrolling"); 307 add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc); 308 309 gc.gridx = 2; 310 gc.gridy = 1; 311 JCheckBox cbLockMergedScrolling = new JCheckBox(); 312 cbLockMergedScrolling.setName("checkbox.lockmergedscrolling"); 313 add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc); 314 315 gc.gridx = 4; 316 gc.gridy = 1; 317 JCheckBox cbLockTheirScrolling = new JCheckBox(); 318 cbLockTheirScrolling.setName("checkbox.locktheirscrolling"); 319 add(buildAdjustmentLockControlPanel(cbLockTheirScrolling), gc); 320 321 // -------------------------------- 322 gc.gridx = 0; 323 gc.gridy = 2; 324 gc.gridwidth = 1; 325 gc.gridheight = 1; 326 gc.fill = GridBagConstraints.BOTH; 327 gc.anchor = GridBagConstraints.FIRST_LINE_START; 328 gc.weightx = 0.33; 329 gc.weighty = 1.0; 330 gc.insets = new Insets(0,0,0,0); 331 JScrollPane pane = buildMyElementsTable(); 332 adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar()); 333 add(pane, gc); 334 335 gc.gridx = 1; 336 gc.gridy = 2; 337 gc.fill = GridBagConstraints.NONE; 338 gc.anchor = GridBagConstraints.CENTER; 339 gc.weightx = 0.0; 340 gc.weighty = 0.0; 341 add(buildLeftButtonPanel(), gc); 342 343 gc.gridx = 2; 344 gc.gridy = 2; 345 gc.fill = GridBagConstraints.BOTH; 346 gc.anchor = GridBagConstraints.FIRST_LINE_START; 347 gc.weightx = 0.33; 348 gc.weighty = 0.0; 349 pane = buildMergedElementsTable(); 350 adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar()); 351 add(pane, gc); 352 353 gc.gridx = 3; 354 gc.gridy = 2; 355 gc.fill = GridBagConstraints.NONE; 356 gc.anchor = GridBagConstraints.CENTER; 357 gc.weightx = 0.0; 358 gc.weighty = 0.0; 359 add(buildRightButtonPanel(), gc); 360 361 gc.gridx = 4; 362 gc.gridy = 2; 363 gc.fill = GridBagConstraints.BOTH; 364 gc.anchor = GridBagConstraints.FIRST_LINE_START; 365 gc.weightx = 0.33; 366 gc.weighty = 0.0; 367 pane = buildTheirElementsTable(); 368 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 409 */ 410 public ListMerger(ListMergeModel<T> model) { 411 this.model = model; 412 model.addObserver(this); 413 build(); 414 model.addPropertyChangeListener(this); 415 } 416 417 /** 418 * Base class of all other Copy* inner classes. 419 */ 420 abstract class CopyAction extends AbstractAction implements ListSelectionListener { 421 422 protected CopyAction(String icon_name, String action_name, String short_description) { 423 ImageIcon icon = ImageProvider.get("dialogs/conflict", icon_name+".png"); 424 putValue(Action.SMALL_ICON, icon); 425 if (icon == null) { 426 putValue(Action.NAME, action_name); 427 } 428 putValue(Action.SHORT_DESCRIPTION, short_description); 429 setEnabled(false); 430 } 431 } 432 433 /** 434 * Action for copying selected nodes in the list of my nodes to the list of merged 435 * nodes. Inserts the nodes at the beginning of the list of merged nodes. 436 */ 437 class CopyStartLeftAction extends CopyAction { 438 439 public CopyStartLeftAction() { 440 super("copystartleft", tr("> top"), tr("Copy my selected nodes to the start of the merged node list")); 441 } 442 443 @Override 444 public void actionPerformed(ActionEvent e) { 445 model.copyMyToTop(myEntriesTable.getSelectedRows()); 446 } 447 448 @Override 449 public void valueChanged(ListSelectionEvent e) { 450 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 451 } 452 } 453 454 /** 455 * Action for copying selected nodes in the list of my nodes to the list of merged 456 * nodes. Inserts the nodes at the end of the list of merged nodes. 457 */ 458 class CopyEndLeftAction extends CopyAction { 459 460 public CopyEndLeftAction() { 461 super("copyendleft", tr("> bottom"), 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 public CopyBeforeCurrentLeftAction() { 482 super("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 public CopyAfterCurrentLeftAction() { 512 super("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 public CopyStartRightAction() { 538 super("copystartright", tr("< top"), 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("copyendright", tr("< bottom"), 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 public CopyBeforeCurrentRightAction() { 572 super("copybeforecurrentright", tr("< before"), 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 == null || 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 public CopyAfterCurrentRightAction() { 598 super("copyaftercurrentright", tr("< after"), 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 == null || 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 Observer, PropertyChangeListener { 622 623 public CopyAllLeft() { 624 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft.png"); 625 putValue(Action.SMALL_ICON, icon); 626 putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); 627 } 628 629 @Override 630 public void actionPerformed(ActionEvent arg0) { 631 model.copyAll(ListRole.MY_ENTRIES); 632 model.setFrozen(true); 633 } 634 635 private void updateEnabledState() { 636 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 637 } 638 639 @Override 640 public void update(Observable o, Object arg) { 641 updateEnabledState(); 642 } 643 644 @Override 645 public void propertyChange(PropertyChangeEvent evt) { 646 updateEnabledState(); 647 } 648 } 649 650 class CopyAllRight extends AbstractAction implements Observer, PropertyChangeListener { 651 652 public CopyAllRight() { 653 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright.png"); 654 putValue(Action.SMALL_ICON, icon); 655 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 656 } 657 658 @Override 659 public void actionPerformed(ActionEvent arg0) { 660 model.copyAll(ListRole.THEIR_ENTRIES); 661 model.setFrozen(true); 662 } 663 664 private void updateEnabledState() { 665 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 666 } 667 668 @Override 669 public void update(Observable o, Object arg) { 670 updateEnabledState(); 671 } 672 673 @Override 674 public void propertyChange(PropertyChangeEvent evt) { 675 updateEnabledState(); 676 } 677 } 678 679 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 680 681 public MoveUpMergedAction() { 682 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png"); 683 putValue(Action.SMALL_ICON, icon); 684 if (icon == null) { 685 putValue(Action.NAME, tr("Up")); 686 } 687 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position.")); 688 setEnabled(false); 689 } 690 691 @Override 692 public void actionPerformed(ActionEvent arg0) { 693 int [] rows = mergedEntriesTable.getSelectedRows(); 694 model.moveUpMerged(rows); 695 } 696 697 @Override 698 public void valueChanged(ListSelectionEvent e) { 699 int [] rows = mergedEntriesTable.getSelectedRows(); 700 setEnabled( 701 rows != null 702 && rows.length > 0 703 && rows[0] != 0 704 ); 705 } 706 } 707 708 /** 709 * Action for moving the currently selected entries in the list of merged entries 710 * one position down 711 * 712 */ 713 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { 714 715 public MoveDownMergedAction() { 716 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png"); 717 putValue(Action.SMALL_ICON, icon); 718 if (icon == null) { 719 putValue(Action.NAME, tr("Down")); 720 } 721 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 722 setEnabled(false); 723 } 724 725 @Override 726 public void actionPerformed(ActionEvent arg0) { 727 int [] rows = mergedEntriesTable.getSelectedRows(); 728 model.moveDownMerged(rows); 729 } 730 731 @Override 732 public void valueChanged(ListSelectionEvent e) { 733 int [] rows = mergedEntriesTable.getSelectedRows(); 734 setEnabled( 735 rows != null 736 && rows.length > 0 737 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 738 ); 739 } 740 } 741 742 /** 743 * Action for removing the selected entries in the list of merged entries 744 * from the list of merged entries. 745 * 746 */ 747 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 748 749 public RemoveMergedAction() { 750 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png"); 751 putValue(Action.SMALL_ICON, icon); 752 if (icon == null) { 753 putValue(Action.NAME, tr("Remove")); 754 } 755 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 756 setEnabled(false); 757 } 758 759 @Override 760 public void actionPerformed(ActionEvent arg0) { 761 int [] rows = mergedEntriesTable.getSelectedRows(); 762 model.removeMerged(rows); 763 } 764 765 @Override 766 public void valueChanged(ListSelectionEvent e) { 767 int [] rows = mergedEntriesTable.getSelectedRows(); 768 setEnabled( 769 rows != null 770 && rows.length > 0 771 ); 772 } 773 } 774 775 static public interface FreezeActionProperties { 776 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 777 } 778 779 /** 780 * Action for freezing the current state of the list merger 781 * 782 */ 783 class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 784 785 public FreezeAction() { 786 putValue(Action.NAME, tr("Freeze")); 787 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 788 putValue(PROP_SELECTED, false); 789 setEnabled(true); 790 } 791 792 @Override 793 public void actionPerformed(ActionEvent arg0) { 794 // do nothing 795 } 796 797 /** 798 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 799 * such that the action gets notified about item state changes and the button gets 800 * notified about selection state changes of the action. 801 * 802 * @param btn a toggle button 803 */ 804 public void adapt(final JToggleButton btn) { 805 btn.addItemListener(this); 806 addPropertyChangeListener( 807 new PropertyChangeListener() { 808 @Override 809 public void propertyChange(PropertyChangeEvent evt) { 810 if (evt.getPropertyName().equals(PROP_SELECTED)) { 811 btn.setSelected((Boolean)evt.getNewValue()); 812 } 813 } 814 } 815 ); 816 } 817 818 @Override 819 public void itemStateChanged(ItemEvent e) { 820 int state = e.getStateChange(); 821 if (state == ItemEvent.SELECTED) { 822 putValue(Action.NAME, tr("Unfreeze")); 823 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 824 model.setFrozen(true); 825 } else if (state == ItemEvent.DESELECTED) { 826 putValue(Action.NAME, tr("Freeze")); 827 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 828 model.setFrozen(false); 829 } 830 boolean isSelected = (Boolean)getValue(PROP_SELECTED); 831 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 832 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 833 } 834 835 } 836 } 837 838 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) { 839 myEntriesTable.getSelectionModel().clearSelection(); 840 myEntriesTable.setEnabled(!newValue); 841 theirEntriesTable.getSelectionModel().clearSelection(); 842 theirEntriesTable.setEnabled(!newValue); 843 mergedEntriesTable.getSelectionModel().clearSelection(); 844 mergedEntriesTable.setEnabled(!newValue); 845 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 846 if (newValue) { 847 lblFrozenState.setText( 848 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 849 freezeAction.getValue(Action.NAME)) 850 ); 851 } else { 852 lblFrozenState.setText( 853 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 854 freezeAction.getValue(Action.NAME)) 855 ); 856 } 857 } 858 859 @Override 860 public void propertyChange(PropertyChangeEvent evt) { 861 if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) { 862 handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue()); 863 } 864 } 865 866 public ListMergeModel<T> getModel() { 867 return model; 868 } 869 870 @Override 871 public void update(Observable o, Object arg) { 872 lblMyVersion.setText( 873 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) 874 ); 875 lblMergedVersion.setText( 876 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) 877 ); 878 lblTheirVersion.setText( 879 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) 880 ); 881 } 882 883 public void unlinkAsListener() { 884 myEntriesTable.unlinkAsListener(); 885 mergedEntriesTable.unlinkAsListener(); 886 theirEntriesTable.unlinkAsListener(); 887 } 888 889 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 890 if (primitive != null) { 891 List<OsmDataLayer> layers = Main.map.mapView.getLayersOfType(OsmDataLayer.class); 892 // Find layer with same dataset 893 for (OsmDataLayer layer : layers) { 894 if (layer.data == primitive.getDataSet()) { 895 return layer; 896 } 897 } 898 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 899 for (OsmDataLayer layer : layers) { 900 final Collection<? extends OsmPrimitive> collection; 901 if (primitive instanceof Way) { 902 collection = layer.data.getWays(); 903 } else if (primitive instanceof Relation) { 904 collection = layer.data.getRelations(); 905 } else { 906 collection = layer.data.allPrimitives(); 907 } 908 for (OsmPrimitive p : collection) { 909 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 910 return layer; 911 } 912 } 913 } 914 } 915 return null; 916 } 917}