001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.conflict.pair; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 import static org.openstreetmap.josm.tools.I18n.trn; 006 007 import java.awt.Adjustable; 008 import java.awt.FlowLayout; 009 import java.awt.GridBagConstraints; 010 import java.awt.GridBagLayout; 011 import java.awt.Insets; 012 import java.awt.event.ActionEvent; 013 import java.awt.event.AdjustmentEvent; 014 import java.awt.event.AdjustmentListener; 015 import java.awt.event.ItemEvent; 016 import java.awt.event.ItemListener; 017 import java.beans.PropertyChangeEvent; 018 import java.beans.PropertyChangeListener; 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Observable; 024 import java.util.Observer; 025 026 import javax.swing.AbstractAction; 027 import javax.swing.Action; 028 import javax.swing.ImageIcon; 029 import javax.swing.JButton; 030 import javax.swing.JCheckBox; 031 import javax.swing.JLabel; 032 import javax.swing.JPanel; 033 import javax.swing.JScrollPane; 034 import javax.swing.JTable; 035 import javax.swing.JToggleButton; 036 import javax.swing.event.ListSelectionEvent; 037 import javax.swing.event.ListSelectionListener; 038 039 import org.openstreetmap.josm.Main; 040 import org.openstreetmap.josm.data.osm.OsmPrimitive; 041 import org.openstreetmap.josm.data.osm.PrimitiveId; 042 import org.openstreetmap.josm.data.osm.Relation; 043 import org.openstreetmap.josm.data.osm.Way; 044 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 045 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 046 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 047 import org.openstreetmap.josm.tools.CheckParameterUtil; 048 import org.openstreetmap.josm.tools.ImageProvider; 049 050 /** 051 * A UI component for resolving conflicts in two lists of entries of type T. 052 * 053 * @param T the type of the entries 054 * @see ListMergeModel 055 */ 056 public abstract class ListMerger<T extends PrimitiveId> extends JPanel implements PropertyChangeListener, Observer { 057 protected OsmPrimitivesTable myEntriesTable; 058 protected OsmPrimitivesTable mergedEntriesTable; 059 protected OsmPrimitivesTable theirEntriesTable; 060 061 protected ListMergeModel<T> model; 062 063 private CopyStartLeftAction copyStartLeftAction; 064 private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction; 065 private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction; 066 private CopyEndLeftAction copyEndLeftAction; 067 private CopyAllLeft copyAllLeft; 068 069 private CopyStartRightAction copyStartRightAction; 070 private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction; 071 private CopyAfterCurrentRightAction copyAfterCurrentRightAction; 072 private CopyEndRightAction copyEndRightAction; 073 private CopyAllRight copyAllRight; 074 075 private MoveUpMergedAction moveUpMergedAction; 076 private MoveDownMergedAction moveDownMergedAction; 077 private RemoveMergedAction removeMergedAction; 078 private FreezeAction freezeAction; 079 080 private AdjustmentSynchronizer adjustmentSynchronizer; 081 082 private JCheckBox cbLockMyScrolling; 083 private JCheckBox cbLockMergedScrolling; 084 private JCheckBox cbLockTheirScrolling; 085 086 private JLabel lblMyVersion; 087 private JLabel lblMergedVersion; 088 private JLabel lblTheirVersion; 089 090 private JLabel lblFrozenState; 091 092 abstract protected JScrollPane buildMyElementsTable(); 093 abstract protected JScrollPane buildMergedElementsTable(); 094 abstract protected JScrollPane buildTheirElementsTable(); 095 096 protected JScrollPane embeddInScrollPane(JTable table) { 097 JScrollPane pane = new JScrollPane(table); 098 pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 099 pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 100 if (adjustmentSynchronizer == null) { 101 adjustmentSynchronizer = new AdjustmentSynchronizer(); 102 } 103 return pane; 104 } 105 106 protected void wireActionsToSelectionModels() { 107 myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction); 108 109 myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 110 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); 111 112 myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 113 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); 114 115 myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction); 116 117 theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction); 118 119 theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 120 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); 121 122 theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 123 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); 124 125 theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction); 126 127 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction); 128 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction); 129 mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction); 130 131 model.addObserver(copyAllLeft); 132 model.addObserver(copyAllRight); 133 model.addPropertyChangeListener(copyAllLeft); 134 model.addPropertyChangeListener(copyAllRight); 135 } 136 137 protected JPanel buildLeftButtonPanel() { 138 JPanel pnl = new JPanel(); 139 pnl.setLayout(new GridBagLayout()); 140 GridBagConstraints gc = new GridBagConstraints(); 141 142 gc.gridx = 0; 143 gc.gridy = 0; 144 copyStartLeftAction = new CopyStartLeftAction(); 145 JButton btn = new JButton(copyStartLeftAction); 146 btn.setName("button.copystartleft"); 147 pnl.add(btn, gc); 148 149 gc.gridx = 0; 150 gc.gridy = 1; 151 copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction(); 152 btn = new JButton(copyBeforeCurrentLeftAction); 153 btn.setName("button.copybeforecurrentleft"); 154 pnl.add(btn, gc); 155 156 gc.gridx = 0; 157 gc.gridy = 2; 158 copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction(); 159 btn = new JButton(copyAfterCurrentLeftAction); 160 btn.setName("button.copyaftercurrentleft"); 161 pnl.add(btn, gc); 162 163 gc.gridx = 0; 164 gc.gridy = 3; 165 copyEndLeftAction = new CopyEndLeftAction(); 166 btn = new JButton(copyEndLeftAction); 167 btn.setName("button.copyendleft"); 168 pnl.add(btn, gc); 169 170 gc.gridx = 0; 171 gc.gridy = 4; 172 copyAllLeft = new CopyAllLeft(); 173 btn = new JButton(copyAllLeft); 174 btn.setName("button.copyallleft"); 175 pnl.add(btn, gc); 176 177 return pnl; 178 } 179 180 protected JPanel buildRightButtonPanel() { 181 JPanel pnl = new JPanel(); 182 pnl.setLayout(new GridBagLayout()); 183 GridBagConstraints gc = new GridBagConstraints(); 184 185 gc.gridx = 0; 186 gc.gridy = 0; 187 copyStartRightAction = new CopyStartRightAction(); 188 pnl.add(new JButton(copyStartRightAction), gc); 189 190 gc.gridx = 0; 191 gc.gridy = 1; 192 copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction(); 193 pnl.add(new JButton(copyBeforeCurrentRightAction), gc); 194 195 gc.gridx = 0; 196 gc.gridy = 2; 197 copyAfterCurrentRightAction = new CopyAfterCurrentRightAction(); 198 pnl.add(new JButton(copyAfterCurrentRightAction), gc); 199 200 gc.gridx = 0; 201 gc.gridy = 3; 202 copyEndRightAction = new CopyEndRightAction(); 203 pnl.add(new JButton(copyEndRightAction), gc); 204 205 gc.gridx = 0; 206 gc.gridy = 4; 207 copyAllRight = new CopyAllRight(); 208 pnl.add(new JButton(copyAllRight), gc); 209 210 return pnl; 211 } 212 213 protected JPanel buildMergedListControlButtons() { 214 JPanel pnl = new JPanel(); 215 pnl.setLayout(new GridBagLayout()); 216 GridBagConstraints gc = new GridBagConstraints(); 217 218 gc.gridx = 0; 219 gc.gridy = 0; 220 gc.gridwidth = 1; 221 gc.gridheight = 1; 222 gc.fill = GridBagConstraints.HORIZONTAL; 223 gc.anchor = GridBagConstraints.CENTER; 224 gc.weightx = 0.3; 225 gc.weighty = 0.0; 226 moveUpMergedAction = new MoveUpMergedAction(); 227 pnl.add(new JButton(moveUpMergedAction), gc); 228 229 gc.gridx = 1; 230 gc.gridy = 0; 231 moveDownMergedAction = new MoveDownMergedAction(); 232 pnl.add(new JButton(moveDownMergedAction), gc); 233 234 gc.gridx = 2; 235 gc.gridy = 0; 236 removeMergedAction = new RemoveMergedAction(); 237 pnl.add(new JButton(removeMergedAction), gc); 238 239 return pnl; 240 } 241 242 protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) { 243 JPanel panel = new JPanel(); 244 panel.setLayout(new FlowLayout(FlowLayout.RIGHT)); 245 panel.add(new JLabel(tr("lock scrolling"))); 246 panel.add(cb); 247 return panel; 248 } 249 250 protected JPanel buildComparePairSelectionPanel() { 251 JPanel p = new JPanel(); 252 p.setLayout(new FlowLayout(FlowLayout.LEFT)); 253 p.add(new JLabel(tr("Compare "))); 254 JosmComboBox cbComparePair = new JosmComboBox(model.getComparePairListModel()); 255 cbComparePair.setRenderer(new ComparePairListCellRenderer()); 256 p.add(cbComparePair); 257 return p; 258 } 259 260 protected JPanel buildFrozeStateControlPanel() { 261 JPanel p = new JPanel(); 262 p.setLayout(new FlowLayout(FlowLayout.LEFT)); 263 lblFrozenState = new JLabel(); 264 p.add(lblFrozenState); 265 freezeAction = new FreezeAction(); 266 JToggleButton btn = new JToggleButton(freezeAction); 267 freezeAction.adapt(btn); 268 btn.setName("button.freeze"); 269 p.add(btn); 270 271 return p; 272 } 273 274 protected void build() { 275 setLayout(new GridBagLayout()); 276 GridBagConstraints gc = new GridBagConstraints(); 277 278 // ------------------ 279 gc.gridx = 0; 280 gc.gridy = 0; 281 gc.gridwidth = 1; 282 gc.gridheight = 1; 283 gc.fill = GridBagConstraints.NONE; 284 gc.anchor = GridBagConstraints.CENTER; 285 gc.weightx = 0.0; 286 gc.weighty = 0.0; 287 gc.insets = new Insets(10,0,0,0); 288 lblMyVersion = new JLabel(tr("My version")); 289 lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset")); 290 add(lblMyVersion, gc); 291 292 gc.gridx = 2; 293 gc.gridy = 0; 294 lblMergedVersion = new JLabel(tr("Merged version")); 295 lblMergedVersion.setToolTipText(tr("List of merged elements. They will replace the list of my elements when the merge decisions are applied.")); 296 add(lblMergedVersion, gc); 297 298 gc.gridx = 4; 299 gc.gridy = 0; 300 lblTheirVersion = new JLabel(tr("Their version")); 301 lblTheirVersion.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset")); 302 add(lblTheirVersion, gc); 303 304 // ------------------------------ 305 gc.gridx = 0; 306 gc.gridy = 1; 307 gc.gridwidth = 1; 308 gc.gridheight = 1; 309 gc.fill = GridBagConstraints.HORIZONTAL; 310 gc.anchor = GridBagConstraints.FIRST_LINE_START; 311 gc.weightx = 0.33; 312 gc.weighty = 0.0; 313 gc.insets = new Insets(0,0,0,0); 314 cbLockMyScrolling = new JCheckBox(); 315 cbLockMyScrolling.setName("checkbox.lockmyscrolling"); 316 add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc); 317 318 gc.gridx = 2; 319 gc.gridy = 1; 320 cbLockMergedScrolling = new JCheckBox(); 321 cbLockMergedScrolling.setName("checkbox.lockmergedscrolling"); 322 add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc); 323 324 gc.gridx = 4; 325 gc.gridy = 1; 326 cbLockTheirScrolling = new JCheckBox(); 327 cbLockTheirScrolling.setName("checkbox.locktheirscrolling"); 328 add(buildAdjustmentLockControlPanel(cbLockTheirScrolling), gc); 329 330 // -------------------------------- 331 gc.gridx = 0; 332 gc.gridy = 2; 333 gc.gridwidth = 1; 334 gc.gridheight = 1; 335 gc.fill = GridBagConstraints.BOTH; 336 gc.anchor = GridBagConstraints.FIRST_LINE_START; 337 gc.weightx = 0.33; 338 gc.weighty = 1.0; 339 gc.insets = new Insets(0,0,0,0); 340 JScrollPane pane = buildMyElementsTable(); 341 adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar()); 342 add(pane, gc); 343 344 gc.gridx = 1; 345 gc.gridy = 2; 346 gc.fill = GridBagConstraints.NONE; 347 gc.anchor = GridBagConstraints.CENTER; 348 gc.weightx = 0.0; 349 gc.weighty = 0.0; 350 add(buildLeftButtonPanel(), gc); 351 352 gc.gridx = 2; 353 gc.gridy = 2; 354 gc.fill = GridBagConstraints.BOTH; 355 gc.anchor = GridBagConstraints.FIRST_LINE_START; 356 gc.weightx = 0.33; 357 gc.weighty = 0.0; 358 pane = buildMergedElementsTable(); 359 adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar()); 360 add(pane, gc); 361 362 gc.gridx = 3; 363 gc.gridy = 2; 364 gc.fill = GridBagConstraints.NONE; 365 gc.anchor = GridBagConstraints.CENTER; 366 gc.weightx = 0.0; 367 gc.weighty = 0.0; 368 add(buildRightButtonPanel(), gc); 369 370 gc.gridx = 4; 371 gc.gridy = 2; 372 gc.fill = GridBagConstraints.BOTH; 373 gc.anchor = GridBagConstraints.FIRST_LINE_START; 374 gc.weightx = 0.33; 375 gc.weighty = 0.0; 376 pane = buildTheirElementsTable(); 377 adjustmentSynchronizer.adapt(cbLockTheirScrolling, pane.getVerticalScrollBar()); 378 add(pane, gc); 379 380 // ---------------------------------- 381 gc.gridx = 2; 382 gc.gridy = 3; 383 gc.gridwidth = 1; 384 gc.gridheight = 1; 385 gc.fill = GridBagConstraints.BOTH; 386 gc.anchor = GridBagConstraints.CENTER; 387 gc.weightx = 0.0; 388 gc.weighty = 0.0; 389 add(buildMergedListControlButtons(), gc); 390 391 // ----------------------------------- 392 gc.gridx = 0; 393 gc.gridy = 4; 394 gc.gridwidth = 2; 395 gc.gridheight = 1; 396 gc.fill = GridBagConstraints.HORIZONTAL; 397 gc.anchor = GridBagConstraints.LINE_START; 398 gc.weightx = 0.0; 399 gc.weighty = 0.0; 400 add(buildComparePairSelectionPanel(), gc); 401 402 gc.gridx = 2; 403 gc.gridy = 4; 404 gc.gridwidth = 3; 405 gc.gridheight = 1; 406 gc.fill = GridBagConstraints.HORIZONTAL; 407 gc.anchor = GridBagConstraints.LINE_START; 408 gc.weightx = 0.0; 409 gc.weighty = 0.0; 410 add(buildFrozeStateControlPanel(), gc); 411 412 wireActionsToSelectionModels(); 413 } 414 415 public ListMerger(ListMergeModel<T> model) { 416 this.model = model; 417 model.addObserver(this); 418 build(); 419 model.addPropertyChangeListener(this); 420 } 421 422 /** 423 * Action for copying selected nodes in the list of my nodes to the list of merged 424 * nodes. Inserts the nodes at the beginning of the list of merged nodes. 425 * 426 */ 427 class CopyStartLeftAction extends AbstractAction implements ListSelectionListener { 428 429 public CopyStartLeftAction() { 430 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartleft.png"); 431 putValue(Action.SMALL_ICON, icon); 432 if (icon == null) { 433 putValue(Action.NAME, tr("> top")); 434 } 435 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the start of the merged node list")); 436 setEnabled(false); 437 } 438 439 public void actionPerformed(ActionEvent arg0) { 440 int [] rows = myEntriesTable.getSelectedRows(); 441 model.copyMyToTop(rows); 442 } 443 444 public void valueChanged(ListSelectionEvent e) { 445 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 446 } 447 } 448 449 /** 450 * Action for copying selected nodes in the list of my nodes to the list of merged 451 * nodes. Inserts the nodes at the end of the list of merged nodes. 452 * 453 */ 454 class CopyEndLeftAction extends AbstractAction implements ListSelectionListener { 455 456 public CopyEndLeftAction() { 457 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendleft.png"); 458 putValue(Action.SMALL_ICON, icon); 459 if (icon == null) { 460 putValue(Action.NAME, tr("> bottom")); 461 } 462 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements to the end of the list of merged elements.")); 463 setEnabled(false); 464 } 465 466 public void actionPerformed(ActionEvent arg0) { 467 int [] rows = myEntriesTable.getSelectedRows(); 468 model.copyMyToEnd(rows); 469 } 470 471 public void valueChanged(ListSelectionEvent e) { 472 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); 473 } 474 } 475 476 /** 477 * Action for copying selected nodes in the list of my nodes to the list of merged 478 * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 479 * 480 */ 481 class CopyBeforeCurrentLeftAction extends AbstractAction implements ListSelectionListener { 482 483 public CopyBeforeCurrentLeftAction() { 484 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentleft.png"); 485 putValue(Action.SMALL_ICON, icon); 486 if (icon == null) { 487 putValue(Action.NAME, "> before"); 488 } 489 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements before the first selected element in the list of merged elements.")); 490 setEnabled(false); 491 } 492 493 public void actionPerformed(ActionEvent arg0) { 494 int [] myRows = myEntriesTable.getSelectedRows(); 495 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 496 if (mergedRows == null || mergedRows.length == 0) 497 return; 498 int current = mergedRows[0]; 499 model.copyMyBeforeCurrent(myRows, current); 500 } 501 502 public void valueChanged(ListSelectionEvent e) { 503 setEnabled( 504 !myEntriesTable.getSelectionModel().isSelectionEmpty() 505 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() 506 ); 507 } 508 } 509 510 /** 511 * Action for copying selected nodes in the list of my nodes to the list of merged 512 * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 513 * 514 */ 515 class CopyAfterCurrentLeftAction extends AbstractAction implements ListSelectionListener { 516 517 public CopyAfterCurrentLeftAction() { 518 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentleft.png"); 519 putValue(Action.SMALL_ICON, icon); 520 if (icon == null) { 521 putValue(Action.NAME, "> after"); 522 } 523 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements after the first selected element in the list of merged elements.")); 524 setEnabled(false); 525 } 526 527 public void actionPerformed(ActionEvent arg0) { 528 int [] myRows = myEntriesTable.getSelectedRows(); 529 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 530 if (mergedRows == null || mergedRows.length == 0) 531 return; 532 int current = mergedRows[0]; 533 model.copyMyAfterCurrent(myRows, current); 534 } 535 536 public void valueChanged(ListSelectionEvent e) { 537 setEnabled( 538 !myEntriesTable.getSelectionModel().isSelectionEmpty() 539 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() 540 ); 541 } 542 } 543 544 class CopyStartRightAction extends AbstractAction implements ListSelectionListener { 545 546 public CopyStartRightAction() { 547 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartright.png"); 548 putValue(Action.SMALL_ICON, icon); 549 if (icon == null) { 550 putValue(Action.NAME, "< top"); 551 } 552 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element to the start of the list of merged elements.")); 553 setEnabled(false); 554 } 555 556 public void actionPerformed(ActionEvent arg0) { 557 int [] rows = theirEntriesTable.getSelectedRows(); 558 model.copyTheirToTop(rows); 559 } 560 561 public void valueChanged(ListSelectionEvent e) { 562 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 563 } 564 } 565 566 class CopyEndRightAction extends AbstractAction implements ListSelectionListener { 567 568 public CopyEndRightAction() { 569 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendright.png"); 570 putValue(Action.SMALL_ICON, icon); 571 if (icon == null) { 572 putValue(Action.NAME, "< bottom"); 573 } 574 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements to the end of the list of merged elements.")); 575 setEnabled(false); 576 } 577 578 public void actionPerformed(ActionEvent arg0) { 579 int [] rows = theirEntriesTable.getSelectedRows(); 580 model.copyTheirToEnd(rows); 581 } 582 583 public void valueChanged(ListSelectionEvent e) { 584 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); 585 } 586 } 587 588 class CopyBeforeCurrentRightAction extends AbstractAction implements ListSelectionListener { 589 590 public CopyBeforeCurrentRightAction() { 591 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentright.png"); 592 putValue(Action.SMALL_ICON, icon); 593 if (icon == null) { 594 putValue(Action.NAME, "< before"); 595 } 596 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements before the first selected element in the list of merged elements.")); 597 setEnabled(false); 598 } 599 600 public void actionPerformed(ActionEvent arg0) { 601 int [] myRows = theirEntriesTable.getSelectedRows(); 602 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 603 if (mergedRows == null || mergedRows.length == 0) 604 return; 605 int current = mergedRows[0]; 606 model.copyTheirBeforeCurrent(myRows, current); 607 } 608 609 public void valueChanged(ListSelectionEvent e) { 610 setEnabled( 611 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 612 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() 613 ); 614 } 615 } 616 617 class CopyAfterCurrentRightAction extends AbstractAction implements ListSelectionListener { 618 619 public CopyAfterCurrentRightAction() { 620 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentright.png"); 621 putValue(Action.SMALL_ICON, icon); 622 if (icon == null) { 623 putValue(Action.NAME, "< after"); 624 } 625 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element after the first selected element in the list of merged elements")); 626 setEnabled(false); 627 } 628 629 public void actionPerformed(ActionEvent arg0) { 630 int [] myRows = theirEntriesTable.getSelectedRows(); 631 int [] mergedRows = mergedEntriesTable.getSelectedRows(); 632 if (mergedRows == null || mergedRows.length == 0) 633 return; 634 int current = mergedRows[0]; 635 model.copyTheirAfterCurrent(myRows, current); 636 } 637 638 public void valueChanged(ListSelectionEvent e) { 639 setEnabled( 640 !theirEntriesTable.getSelectionModel().isSelectionEmpty() 641 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() 642 ); 643 } 644 } 645 646 class CopyAllLeft extends AbstractAction implements Observer, PropertyChangeListener { 647 648 public CopyAllLeft() { 649 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft.png"); 650 putValue(Action.SMALL_ICON, icon); 651 putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); 652 } 653 654 public void actionPerformed(ActionEvent arg0) { 655 model.copyAll(ListRole.MY_ENTRIES); 656 model.setFrozen(true); 657 } 658 659 private void updateEnabledState() { 660 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 661 } 662 663 public void update(Observable o, Object arg) { 664 updateEnabledState(); 665 } 666 667 public void propertyChange(PropertyChangeEvent evt) { 668 updateEnabledState(); 669 } 670 } 671 672 class CopyAllRight extends AbstractAction implements Observer, PropertyChangeListener { 673 674 public CopyAllRight() { 675 ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright.png"); 676 putValue(Action.SMALL_ICON, icon); 677 putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); 678 } 679 680 public void actionPerformed(ActionEvent arg0) { 681 model.copyAll(ListRole.THEIR_ENTRIES); 682 model.setFrozen(true); 683 } 684 685 private void updateEnabledState() { 686 setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); 687 } 688 689 public void update(Observable o, Object arg) { 690 updateEnabledState(); 691 } 692 693 public void propertyChange(PropertyChangeEvent evt) { 694 updateEnabledState(); 695 } 696 } 697 698 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { 699 700 public MoveUpMergedAction() { 701 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png"); 702 putValue(Action.SMALL_ICON, icon); 703 if (icon == null) { 704 putValue(Action.NAME, tr("Up")); 705 } 706 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected elements by one position.")); 707 setEnabled(false); 708 } 709 710 public void actionPerformed(ActionEvent arg0) { 711 int [] rows = mergedEntriesTable.getSelectedRows(); 712 model.moveUpMerged(rows); 713 } 714 715 public void valueChanged(ListSelectionEvent e) { 716 int [] rows = mergedEntriesTable.getSelectedRows(); 717 setEnabled( 718 rows != null 719 && rows.length > 0 720 && rows[0] != 0 721 ); 722 } 723 } 724 725 /** 726 * Action for moving the currently selected entries in the list of merged entries 727 * one position down 728 * 729 */ 730 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { 731 732 public MoveDownMergedAction() { 733 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png"); 734 putValue(Action.SMALL_ICON, icon); 735 if (icon == null) { 736 putValue(Action.NAME, tr("Down")); 737 } 738 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); 739 setEnabled(false); 740 } 741 742 public void actionPerformed(ActionEvent arg0) { 743 int [] rows = mergedEntriesTable.getSelectedRows(); 744 model.moveDownMerged(rows); 745 } 746 747 public void valueChanged(ListSelectionEvent e) { 748 int [] rows = mergedEntriesTable.getSelectedRows(); 749 setEnabled( 750 rows != null 751 && rows.length > 0 752 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 753 ); 754 } 755 } 756 757 /** 758 * Action for removing the selected entries in the list of merged entries 759 * from the list of merged entries. 760 * 761 */ 762 class RemoveMergedAction extends AbstractAction implements ListSelectionListener { 763 764 public RemoveMergedAction() { 765 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png"); 766 putValue(Action.SMALL_ICON, icon); 767 if (icon == null) { 768 putValue(Action.NAME, tr("Remove")); 769 } 770 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); 771 setEnabled(false); 772 } 773 774 public void actionPerformed(ActionEvent arg0) { 775 int [] rows = mergedEntriesTable.getSelectedRows(); 776 model.removeMerged(rows); 777 } 778 779 public void valueChanged(ListSelectionEvent e) { 780 int [] rows = mergedEntriesTable.getSelectedRows(); 781 setEnabled( 782 rows != null 783 && rows.length > 0 784 ); 785 } 786 } 787 788 static public interface FreezeActionProperties { 789 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; 790 } 791 792 /** 793 * Action for freezing the current state of the list merger 794 * 795 */ 796 class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { 797 798 public FreezeAction() { 799 putValue(Action.NAME, tr("Freeze")); 800 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 801 putValue(PROP_SELECTED, false); 802 setEnabled(true); 803 } 804 805 public void actionPerformed(ActionEvent arg0) { 806 // do nothing 807 } 808 809 /** 810 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action 811 * such that the action gets notified about item state changes and the button gets 812 * notified about selection state changes of the action. 813 * 814 * @param btn a toggle button 815 */ 816 public void adapt(final JToggleButton btn) { 817 btn.addItemListener(this); 818 addPropertyChangeListener( 819 new PropertyChangeListener() { 820 public void propertyChange(PropertyChangeEvent evt) { 821 if (evt.getPropertyName().equals(PROP_SELECTED)) { 822 btn.setSelected((Boolean)evt.getNewValue()); 823 } 824 } 825 } 826 ); 827 } 828 829 public void itemStateChanged(ItemEvent e) { 830 int state = e.getStateChange(); 831 if (state == ItemEvent.SELECTED) { 832 putValue(Action.NAME, tr("Unfreeze")); 833 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); 834 model.setFrozen(true); 835 } else if (state == ItemEvent.DESELECTED) { 836 putValue(Action.NAME, tr("Freeze")); 837 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); 838 model.setFrozen(false); 839 } 840 boolean isSelected = (Boolean)getValue(PROP_SELECTED); 841 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { 842 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); 843 } 844 845 } 846 } 847 848 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) { 849 myEntriesTable.getSelectionModel().clearSelection(); 850 myEntriesTable.setEnabled(!newValue); 851 theirEntriesTable.getSelectionModel().clearSelection(); 852 theirEntriesTable.setEnabled(!newValue); 853 mergedEntriesTable.getSelectionModel().clearSelection(); 854 mergedEntriesTable.setEnabled(!newValue); 855 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); 856 if (newValue) { 857 lblFrozenState.setText( 858 tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", 859 freezeAction.getValue(Action.NAME)) 860 ); 861 } else { 862 lblFrozenState.setText( 863 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", 864 freezeAction.getValue(Action.NAME)) 865 ); 866 } 867 } 868 869 public void propertyChange(PropertyChangeEvent evt) { 870 if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) { 871 handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue()); 872 } 873 } 874 875 public ListMergeModel<T> getModel() { 876 return model; 877 } 878 879 public void update(Observable o, Object arg) { 880 lblMyVersion.setText( 881 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) 882 ); 883 lblMergedVersion.setText( 884 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) 885 ); 886 lblTheirVersion.setText( 887 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) 888 ); 889 } 890 891 public void unlinkAsListener() { 892 myEntriesTable.unlinkAsListener(); 893 mergedEntriesTable.unlinkAsListener(); 894 theirEntriesTable.unlinkAsListener(); 895 } 896 897 /** 898 * Synchronizes scrollbar adjustments between a set of 899 * {@link Adjustable}s. Whenever the adjustment of one of 900 * the registerd Adjustables is updated the adjustment of 901 * the other registered Adjustables is adjusted too. 902 * 903 */ 904 class AdjustmentSynchronizer implements AdjustmentListener { 905 906 private final ArrayList<Adjustable> synchronizedAdjustables; 907 private final HashMap<Adjustable, Boolean> enabledMap; 908 909 private final Observable observable; 910 911 public AdjustmentSynchronizer() { 912 synchronizedAdjustables = new ArrayList<Adjustable>(); 913 enabledMap = new HashMap<Adjustable, Boolean>(); 914 observable = new Observable(); 915 } 916 917 /** 918 * registers an {@link Adjustable} for participation in synchronized 919 * scrolling. 920 * 921 * @param adjustable the adjustable 922 */ 923 public void participateInSynchronizedScrolling(Adjustable adjustable) { 924 if (adjustable == null) 925 return; 926 if (synchronizedAdjustables.contains(adjustable)) 927 return; 928 synchronizedAdjustables.add(adjustable); 929 setParticipatingInSynchronizedScrolling(adjustable, true); 930 adjustable.addAdjustmentListener(this); 931 } 932 933 /** 934 * event handler for {@link AdjustmentEvent}s 935 * 936 */ 937 public void adjustmentValueChanged(AdjustmentEvent e) { 938 if (! enabledMap.get(e.getAdjustable())) 939 return; 940 for (Adjustable a : synchronizedAdjustables) { 941 if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) { 942 a.setValue(e.getValue()); 943 } 944 } 945 } 946 947 /** 948 * sets whether adjustable participates in adjustment synchronization 949 * or not 950 * 951 * @param adjustable the adjustable 952 */ 953 protected void setParticipatingInSynchronizedScrolling(Adjustable adjustable, boolean isParticipating) { 954 CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable"); 955 if (! synchronizedAdjustables.contains(adjustable)) 956 throw new IllegalStateException(tr("Adjustable {0} not registered yet. Cannot set participation in synchronized adjustment.", adjustable)); 957 958 enabledMap.put(adjustable, isParticipating); 959 observable.notifyObservers(); 960 } 961 962 /** 963 * returns true if an adjustable is participating in synchronized scrolling 964 * 965 * @param adjustable the adjustable 966 * @return true, if the adjustable is participating in synchronized scrolling, false otherwise 967 * @throws IllegalStateException thrown, if adjustable is not registered for synchronized scrolling 968 */ 969 protected boolean isParticipatingInSynchronizedScrolling(Adjustable adjustable) throws IllegalStateException { 970 if (! synchronizedAdjustables.contains(adjustable)) 971 throw new IllegalStateException(tr("Adjustable {0} not registered yet.", adjustable)); 972 973 return enabledMap.get(adjustable); 974 } 975 976 /** 977 * wires a {@link JCheckBox} to the adjustment synchronizer, in such a way that: 978 * <li> 979 * <ol>state changes in the checkbox control whether the adjustable participates 980 * in synchronized adjustment</ol> 981 * <ol>state changes in this {@link AdjustmentSynchronizer} are reflected in the 982 * {@link JCheckBox}</ol> 983 * </li> 984 * 985 * 986 * @param view the checkbox to control whether an adjustable participates in synchronized 987 * adjustment 988 * @param adjustable the adjustable 989 * @exception IllegalArgumentException thrown, if view is null 990 * @exception IllegalArgumentException thrown, if adjustable is null 991 */ 992 protected void adapt(final JCheckBox view, final Adjustable adjustable) throws IllegalStateException { 993 CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable"); 994 CheckParameterUtil.ensureParameterNotNull(view, "view"); 995 996 if (! synchronizedAdjustables.contains(adjustable)) { 997 participateInSynchronizedScrolling(adjustable); 998 } 999 1000 // register an item lister with the check box 1001 // 1002 view.addItemListener(new ItemListener() { 1003 public void itemStateChanged(ItemEvent e) { 1004 switch(e.getStateChange()) { 1005 case ItemEvent.SELECTED: 1006 if (!isParticipatingInSynchronizedScrolling(adjustable)) { 1007 setParticipatingInSynchronizedScrolling(adjustable, true); 1008 } 1009 break; 1010 case ItemEvent.DESELECTED: 1011 if (isParticipatingInSynchronizedScrolling(adjustable)) { 1012 setParticipatingInSynchronizedScrolling(adjustable, false); 1013 } 1014 break; 1015 } 1016 } 1017 }); 1018 1019 observable.addObserver( 1020 new Observer() { 1021 public void update(Observable o, Object arg) { 1022 boolean sync = isParticipatingInSynchronizedScrolling(adjustable); 1023 if (view.isSelected() != sync) { 1024 view.setSelected(sync); 1025 } 1026 } 1027 } 1028 ); 1029 setParticipatingInSynchronizedScrolling(adjustable, true); 1030 view.setSelected(true); 1031 } 1032 } 1033 1034 protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { 1035 if (primitive != null) { 1036 List<OsmDataLayer> layers = Main.map.mapView.getLayersOfType(OsmDataLayer.class); 1037 // Find layer with same dataset 1038 for (OsmDataLayer layer : layers) { 1039 if (layer.data == primitive.getDataSet()) { 1040 return layer; 1041 } 1042 } 1043 // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive 1044 for (OsmDataLayer layer : layers) { 1045 final Collection<? extends OsmPrimitive> collection; 1046 if (primitive instanceof Way) { 1047 collection = layer.data.getWays(); 1048 } else if (primitive instanceof Relation) { 1049 collection = layer.data.getRelations(); 1050 } else { 1051 collection = layer.data.allPrimitives(); 1052 } 1053 for (OsmPrimitive p : collection) { 1054 if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { 1055 return layer; 1056 } 1057 } 1058 } 1059 } 1060 return null; 1061 } 1062 }