001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.conflict.pair;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.awt.FlowLayout;
008import java.awt.GridBagConstraints;
009import java.awt.GridBagLayout;
010import java.awt.Insets;
011import java.awt.event.ActionEvent;
012import java.awt.event.ItemEvent;
013import java.awt.event.ItemListener;
014import java.beans.PropertyChangeEvent;
015import java.beans.PropertyChangeListener;
016import java.util.Collection;
017
018import javax.swing.AbstractAction;
019import javax.swing.Action;
020import javax.swing.JButton;
021import javax.swing.JCheckBox;
022import javax.swing.JLabel;
023import javax.swing.JPanel;
024import javax.swing.JScrollPane;
025import javax.swing.JTable;
026import javax.swing.JToggleButton;
027import javax.swing.event.ChangeEvent;
028import javax.swing.event.ChangeListener;
029import javax.swing.event.ListSelectionEvent;
030import javax.swing.event.ListSelectionListener;
031
032import org.openstreetmap.josm.command.conflict.ConflictResolveCommand;
033import org.openstreetmap.josm.data.osm.OsmPrimitive;
034import org.openstreetmap.josm.data.osm.PrimitiveId;
035import org.openstreetmap.josm.data.osm.Relation;
036import org.openstreetmap.josm.data.osm.Way;
037import org.openstreetmap.josm.gui.MainApplication;
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 * @param <C> the type of conflict resolution command
049 * @see AbstractListMergeModel
050 * @since 1631
051 */
052public abstract class AbstractListMerger<T extends PrimitiveId, C extends ConflictResolveCommand> extends JPanel
053implements PropertyChangeListener, ChangeListener, IConflictResolver {
054    protected OsmPrimitivesTable myEntriesTable;
055    protected OsmPrimitivesTable mergedEntriesTable;
056    protected OsmPrimitivesTable theirEntriesTable;
057
058    protected transient AbstractListMergeModel<T, C> model;
059
060    private CopyStartLeftAction copyStartLeftAction;
061    private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction;
062    private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction;
063    private CopyEndLeftAction copyEndLeftAction;
064    private CopyAllLeft copyAllLeft;
065
066    private CopyStartRightAction copyStartRightAction;
067    private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction;
068    private CopyAfterCurrentRightAction copyAfterCurrentRightAction;
069    private CopyEndRightAction copyEndRightAction;
070    private CopyAllRight copyAllRight;
071
072    private MoveUpMergedAction moveUpMergedAction;
073    private MoveDownMergedAction moveDownMergedAction;
074    private RemoveMergedAction removeMergedAction;
075    private FreezeAction freezeAction;
076
077    private transient AdjustmentSynchronizer adjustmentSynchronizer;
078
079    private JLabel lblMyVersion;
080    private JLabel lblMergedVersion;
081    private JLabel lblTheirVersion;
082
083    private JLabel lblFrozenState;
084
085    protected abstract JScrollPane buildMyElementsTable();
086
087    protected abstract JScrollPane buildMergedElementsTable();
088
089    protected abstract JScrollPane buildTheirElementsTable();
090
091    protected JScrollPane embedInScrollPane(JTable table) {
092        JScrollPane pane = new JScrollPane(table);
093        if (adjustmentSynchronizer == null) {
094            adjustmentSynchronizer = new AdjustmentSynchronizer();
095        }
096        return pane;
097    }
098
099    protected void wireActionsToSelectionModels() {
100        myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction);
101
102        myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
103        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
104
105        myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
106        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
107
108        myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction);
109
110        theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction);
111
112        theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
113        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
114
115        theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
116        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
117
118        theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction);
119
120        mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction);
121        mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction);
122        mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction);
123
124        model.addChangeListener(copyAllLeft);
125        model.addChangeListener(copyAllRight);
126        model.addPropertyChangeListener(copyAllLeft);
127        model.addPropertyChangeListener(copyAllRight);
128    }
129
130    protected JPanel buildLeftButtonPanel() {
131        JPanel pnl = new JPanel(new GridBagLayout());
132        GridBagConstraints gc = new GridBagConstraints();
133
134        gc.gridx = 0;
135        gc.gridy = 0;
136        copyStartLeftAction = new CopyStartLeftAction();
137        JButton btn = new JButton(copyStartLeftAction);
138        btn.setName("button.copystartleft");
139        pnl.add(btn, gc);
140
141        gc.gridx = 0;
142        gc.gridy = 1;
143        copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction();
144        btn = new JButton(copyBeforeCurrentLeftAction);
145        btn.setName("button.copybeforecurrentleft");
146        pnl.add(btn, gc);
147
148        gc.gridx = 0;
149        gc.gridy = 2;
150        copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction();
151        btn = new JButton(copyAfterCurrentLeftAction);
152        btn.setName("button.copyaftercurrentleft");
153        pnl.add(btn, gc);
154
155        gc.gridx = 0;
156        gc.gridy = 3;
157        copyEndLeftAction = new CopyEndLeftAction();
158        btn = new JButton(copyEndLeftAction);
159        btn.setName("button.copyendleft");
160        pnl.add(btn, gc);
161
162        gc.gridx = 0;
163        gc.gridy = 4;
164        copyAllLeft = new CopyAllLeft();
165        btn = new JButton(copyAllLeft);
166        btn.setName("button.copyallleft");
167        pnl.add(btn, gc);
168
169        return pnl;
170    }
171
172    protected JPanel buildRightButtonPanel() {
173        JPanel pnl = new JPanel(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(new GridBagLayout());
206        GridBagConstraints gc = new GridBagConstraints();
207
208        gc.gridx = 0;
209        gc.gridy = 0;
210        gc.gridwidth = 1;
211        gc.gridheight = 1;
212        gc.fill = GridBagConstraints.HORIZONTAL;
213        gc.anchor = GridBagConstraints.CENTER;
214        gc.weightx = 0.3;
215        gc.weighty = 0.0;
216        moveUpMergedAction = new MoveUpMergedAction();
217        pnl.add(new JButton(moveUpMergedAction), gc);
218
219        gc.gridx = 1;
220        gc.gridy = 0;
221        moveDownMergedAction = new MoveDownMergedAction();
222        pnl.add(new JButton(moveDownMergedAction), gc);
223
224        gc.gridx = 2;
225        gc.gridy = 0;
226        removeMergedAction = new RemoveMergedAction();
227        pnl.add(new JButton(removeMergedAction), gc);
228
229        return pnl;
230    }
231
232    protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) {
233        JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
234        panel.add(new JLabel(tr("lock scrolling")));
235        panel.add(cb);
236        return panel;
237    }
238
239    protected JPanel buildComparePairSelectionPanel() {
240        JPanel p = new JPanel(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(new FlowLayout(FlowLayout.LEFT));
250        lblFrozenState = new JLabel();
251        p.add(lblFrozenState);
252        freezeAction = new FreezeAction();
253        JToggleButton btn = new JToggleButton(freezeAction);
254        freezeAction.adapt(btn);
255        btn.setName("button.freeze");
256        p.add(btn);
257
258        return p;
259    }
260
261    protected final void build() {
262        setLayout(new GridBagLayout());
263        GridBagConstraints gc = new GridBagConstraints();
264
265        // ------------------
266        gc.gridx = 0;
267        gc.gridy = 0;
268        gc.gridwidth = 1;
269        gc.gridheight = 1;
270        gc.fill = GridBagConstraints.NONE;
271        gc.anchor = GridBagConstraints.CENTER;
272        gc.weightx = 0.0;
273        gc.weighty = 0.0;
274        gc.insets = new Insets(10, 0, 0, 0);
275        lblMyVersion = new JLabel(tr("My version"));
276        lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset"));
277        add(lblMyVersion, gc);
278
279        gc.gridx = 2;
280        gc.gridy = 0;
281        lblMergedVersion = new JLabel(tr("Merged version"));
282        lblMergedVersion.setToolTipText(
283                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        lblMyVersion.setLabelFor(pane);
330        adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar());
331        add(pane, gc);
332
333        gc.gridx = 1;
334        gc.gridy = 2;
335        gc.fill = GridBagConstraints.NONE;
336        gc.anchor = GridBagConstraints.CENTER;
337        gc.weightx = 0.0;
338        gc.weighty = 0.0;
339        add(buildLeftButtonPanel(), gc);
340
341        gc.gridx = 2;
342        gc.gridy = 2;
343        gc.fill = GridBagConstraints.BOTH;
344        gc.anchor = GridBagConstraints.FIRST_LINE_START;
345        gc.weightx = 0.33;
346        gc.weighty = 0.0;
347        pane = buildMergedElementsTable();
348        lblMergedVersion.setLabelFor(pane);
349        adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar());
350        add(pane, gc);
351
352        gc.gridx = 3;
353        gc.gridy = 2;
354        gc.fill = GridBagConstraints.NONE;
355        gc.anchor = GridBagConstraints.CENTER;
356        gc.weightx = 0.0;
357        gc.weighty = 0.0;
358        add(buildRightButtonPanel(), gc);
359
360        gc.gridx = 4;
361        gc.gridy = 2;
362        gc.fill = GridBagConstraints.BOTH;
363        gc.anchor = GridBagConstraints.FIRST_LINE_START;
364        gc.weightx = 0.33;
365        gc.weighty = 0.0;
366        pane = buildTheirElementsTable();
367        lblTheirVersion.setLabelFor(pane);
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 list merger model
409     */
410    protected AbstractListMerger(AbstractListMergeModel<T, C> model) {
411        this.model = model;
412        model.addChangeListener(this);
413        build();
414        model.addPropertyChangeListener(this);
415    }
416
417    /**
418     * Base class of all other Copy* inner classes.
419     */
420    abstract static class CopyAction extends AbstractAction implements ListSelectionListener {
421
422        protected CopyAction(String iconName, String shortDescription) {
423            new ImageProvider("dialogs/conflict", iconName).getResource().attachImageIcon(this, true);
424            putValue(Action.SHORT_DESCRIPTION, shortDescription);
425            setEnabled(false);
426        }
427    }
428
429    /**
430     * Action for copying selected nodes in the list of my nodes to the list of merged
431     * nodes. Inserts the nodes at the beginning of the list of merged nodes.
432     */
433    class CopyStartLeftAction extends CopyAction {
434
435        CopyStartLeftAction() {
436            super(/* ICON(dialogs/conflict/)*/ "copystartleft",
437                tr("Copy my selected nodes to the start of the merged node list"));
438        }
439
440        @Override
441        public void actionPerformed(ActionEvent e) {
442            model.copyMyToTop(myEntriesTable.getSelectedRows());
443        }
444
445        @Override
446        public void valueChanged(ListSelectionEvent e) {
447            setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
448        }
449    }
450
451    /**
452     * Action for copying selected nodes in the list of my nodes to the list of merged
453     * nodes. Inserts the nodes at the end of the list of merged nodes.
454     */
455    class CopyEndLeftAction extends CopyAction {
456
457        CopyEndLeftAction() {
458            super(/* ICON(dialogs/conflict/)*/ "copyendleft",
459                tr("Copy my selected elements to the end of the list of merged elements."));
460        }
461
462        @Override
463        public void actionPerformed(ActionEvent e) {
464            model.copyMyToEnd(myEntriesTable.getSelectedRows());
465        }
466
467        @Override
468        public void valueChanged(ListSelectionEvent e) {
469            setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
470        }
471    }
472
473    /**
474     * Action for copying selected nodes in the list of my nodes to the list of merged
475     * nodes. Inserts the nodes before the first selected row in the list of merged nodes.
476     */
477    class CopyBeforeCurrentLeftAction extends CopyAction {
478
479        CopyBeforeCurrentLeftAction() {
480            super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentleft",
481                    tr("Copy my selected elements before the first selected element in the list of merged elements."));
482        }
483
484        @Override
485        public void actionPerformed(ActionEvent e) {
486            int[] mergedRows = mergedEntriesTable.getSelectedRows();
487            if (mergedRows.length == 0)
488                return;
489            int[] myRows = myEntriesTable.getSelectedRows();
490            int current = mergedRows[0];
491            model.copyMyBeforeCurrent(myRows, current);
492        }
493
494        @Override
495        public void valueChanged(ListSelectionEvent e) {
496            setEnabled(
497                    !myEntriesTable.getSelectionModel().isSelectionEmpty()
498                    && !mergedEntriesTable.getSelectionModel().isSelectionEmpty()
499            );
500        }
501    }
502
503    /**
504     * Action for copying selected nodes in the list of my nodes to the list of merged
505     * nodes. Inserts the nodes after the first selected row in the list of merged nodes.
506     */
507    class CopyAfterCurrentLeftAction extends CopyAction {
508
509        CopyAfterCurrentLeftAction() {
510            super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentleft",
511                    tr("Copy my selected elements after the first selected element in the list of merged elements."));
512        }
513
514        @Override
515        public void actionPerformed(ActionEvent e) {
516            int[] mergedRows = mergedEntriesTable.getSelectedRows();
517            if (mergedRows.length == 0)
518                return;
519            int[] myRows = myEntriesTable.getSelectedRows();
520            int current = mergedRows[0];
521            model.copyMyAfterCurrent(myRows, current);
522        }
523
524        @Override
525        public void valueChanged(ListSelectionEvent e) {
526            setEnabled(
527                    !myEntriesTable.getSelectionModel().isSelectionEmpty()
528                    && !mergedEntriesTable.getSelectionModel().isSelectionEmpty()
529            );
530        }
531    }
532
533    class CopyStartRightAction extends CopyAction {
534
535        CopyStartRightAction() {
536            super(/* ICON(dialogs/conflict/)*/ "copystartright",
537                tr("Copy their selected element to the start of the list of merged elements."));
538        }
539
540        @Override
541        public void actionPerformed(ActionEvent e) {
542            model.copyTheirToTop(theirEntriesTable.getSelectedRows());
543        }
544
545        @Override
546        public void valueChanged(ListSelectionEvent e) {
547            setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty());
548        }
549    }
550
551    class CopyEndRightAction extends CopyAction {
552
553        CopyEndRightAction() {
554            super(/* ICON(dialogs/conflict/)*/ "copyendright",
555                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        CopyBeforeCurrentRightAction() {
572            super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentright",
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.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        CopyAfterCurrentRightAction() {
598            super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentright",
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.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 ChangeListener, PropertyChangeListener {
622
623        CopyAllLeft() {
624            new ImageProvider("dialogs/conflict", "useallleft").getResource().attachImageIcon(this, true);
625            putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target"));
626        }
627
628        @Override
629        public void actionPerformed(ActionEvent arg0) {
630            model.copyAll(ListRole.MY_ENTRIES);
631            model.setFrozen(true);
632        }
633
634        private void updateEnabledState() {
635            setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen());
636        }
637
638        @Override
639        public void stateChanged(ChangeEvent e) {
640            updateEnabledState();
641        }
642
643        @Override
644        public void propertyChange(PropertyChangeEvent evt) {
645            updateEnabledState();
646        }
647    }
648
649    class CopyAllRight extends AbstractAction implements ChangeListener, PropertyChangeListener {
650
651        CopyAllRight() {
652            new ImageProvider("dialogs/conflict", "useallright").getResource().attachImageIcon(this, true);
653            putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target"));
654        }
655
656        @Override
657        public void actionPerformed(ActionEvent arg0) {
658            model.copyAll(ListRole.THEIR_ENTRIES);
659            model.setFrozen(true);
660        }
661
662        private void updateEnabledState() {
663            setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen());
664        }
665
666        @Override
667        public void stateChanged(ChangeEvent e) {
668            updateEnabledState();
669        }
670
671        @Override
672        public void propertyChange(PropertyChangeEvent evt) {
673            updateEnabledState();
674        }
675    }
676
677    class MoveUpMergedAction extends AbstractAction implements ListSelectionListener {
678
679        MoveUpMergedAction() {
680            new ImageProvider("dialogs/conflict", "moveup").getResource().attachImageIcon(this, true);
681            putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position."));
682            setEnabled(false);
683        }
684
685        @Override
686        public void actionPerformed(ActionEvent arg0) {
687            int[] rows = mergedEntriesTable.getSelectedRows();
688            model.moveUpMerged(rows);
689        }
690
691        @Override
692        public void valueChanged(ListSelectionEvent e) {
693            int[] rows = mergedEntriesTable.getSelectedRows();
694            setEnabled(rows.length > 0
695                    && rows[0] != 0
696            );
697        }
698    }
699
700    /**
701     * Action for moving the currently selected entries in the list of merged entries
702     * one position down
703     *
704     */
705    class MoveDownMergedAction extends AbstractAction implements ListSelectionListener {
706
707        MoveDownMergedAction() {
708            new ImageProvider("dialogs/conflict", "movedown").getResource().attachImageIcon(this, true);
709            putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position."));
710            setEnabled(false);
711        }
712
713        @Override
714        public void actionPerformed(ActionEvent arg0) {
715            int[] rows = mergedEntriesTable.getSelectedRows();
716            model.moveDownMerged(rows);
717        }
718
719        @Override
720        public void valueChanged(ListSelectionEvent e) {
721            int[] rows = mergedEntriesTable.getSelectedRows();
722            setEnabled(rows.length > 0
723                    && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1
724            );
725        }
726    }
727
728    /**
729     * Action for removing the selected entries in the list of merged entries
730     * from the list of merged entries.
731     *
732     */
733    class RemoveMergedAction extends AbstractAction implements ListSelectionListener {
734
735        RemoveMergedAction() {
736            new ImageProvider("dialogs/conflict", "remove").getResource().attachImageIcon(this, true);
737            putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements."));
738            setEnabled(false);
739        }
740
741        @Override
742        public void actionPerformed(ActionEvent arg0) {
743            int[] rows = mergedEntriesTable.getSelectedRows();
744            model.removeMerged(rows);
745        }
746
747        @Override
748        public void valueChanged(ListSelectionEvent e) {
749            int[] rows = mergedEntriesTable.getSelectedRows();
750            setEnabled(rows.length > 0);
751        }
752    }
753
754    private interface FreezeActionProperties {
755        String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected";
756    }
757
758    /**
759     * Action for freezing the current state of the list merger
760     *
761     */
762    private final class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties {
763
764        private FreezeAction() {
765            putValue(Action.NAME, tr("Freeze"));
766            putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements."));
767            putValue(PROP_SELECTED, Boolean.FALSE);
768            setEnabled(true);
769        }
770
771        @Override
772        public void actionPerformed(ActionEvent arg0) {
773            // do nothing
774        }
775
776        /**
777         * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action
778         * such that the action gets notified about item state changes and the button gets
779         * notified about selection state changes of the action.
780         *
781         * @param btn a toggle button
782         */
783        public void adapt(final JToggleButton btn) {
784            btn.addItemListener(this);
785            addPropertyChangeListener(evt -> {
786                    if (evt.getPropertyName().equals(PROP_SELECTED)) {
787                        btn.setSelected((Boolean) evt.getNewValue());
788                    }
789                });
790        }
791
792        @Override
793        public void itemStateChanged(ItemEvent e) {
794            int state = e.getStateChange();
795            if (state == ItemEvent.SELECTED) {
796                putValue(Action.NAME, tr("Unfreeze"));
797                putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging."));
798                model.setFrozen(true);
799            } else if (state == ItemEvent.DESELECTED) {
800                putValue(Action.NAME, tr("Freeze"));
801                putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements."));
802                model.setFrozen(false);
803            }
804            boolean isSelected = (Boolean) getValue(PROP_SELECTED);
805            if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) {
806                putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED);
807            }
808
809        }
810    }
811
812    protected void handlePropertyChangeFrozen(boolean newValue) {
813        myEntriesTable.getSelectionModel().clearSelection();
814        myEntriesTable.setEnabled(!newValue);
815        theirEntriesTable.getSelectionModel().clearSelection();
816        theirEntriesTable.setEnabled(!newValue);
817        mergedEntriesTable.getSelectionModel().clearSelection();
818        mergedEntriesTable.setEnabled(!newValue);
819        freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue);
820        if (newValue) {
821            lblFrozenState.setText(
822                    tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>",
823                            freezeAction.getValue(Action.NAME))
824            );
825        } else {
826            lblFrozenState.setText(
827                    tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>",
828                            freezeAction.getValue(Action.NAME))
829            );
830        }
831    }
832
833    @Override
834    public void propertyChange(PropertyChangeEvent evt) {
835        if (evt.getPropertyName().equals(AbstractListMergeModel.FROZEN_PROP)) {
836            handlePropertyChangeFrozen((Boolean) evt.getNewValue());
837        }
838    }
839
840    /**
841     * Returns the model.
842     * @return the model
843     */
844    public AbstractListMergeModel<T, C> getModel() {
845        return model;
846    }
847
848    @Override
849    public void stateChanged(ChangeEvent e) {
850        lblMyVersion.setText(
851                trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize())
852        );
853        lblMergedVersion.setText(
854                trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize())
855        );
856        lblTheirVersion.setText(
857                trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize())
858        );
859    }
860
861    /**
862     * Adds all registered listeners by this merger
863     * @see #unregisterListeners()
864     * @since 10454
865     */
866    public void registerListeners() {
867        myEntriesTable.registerListeners();
868        mergedEntriesTable.registerListeners();
869        theirEntriesTable.registerListeners();
870    }
871
872    /**
873     * Removes all registered listeners by this merger
874     * @since 10454
875     */
876    public void unregisterListeners() {
877        myEntriesTable.unregisterListeners();
878        mergedEntriesTable.unregisterListeners();
879        theirEntriesTable.unregisterListeners();
880    }
881
882    protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) {
883        if (primitive != null) {
884            Iterable<OsmDataLayer> layers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class);
885            // Find layer with same dataset
886            for (OsmDataLayer layer : layers) {
887                if (layer.data == primitive.getDataSet()) {
888                    return layer;
889                }
890            }
891            // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive
892            for (OsmDataLayer layer : layers) {
893                final Collection<? extends OsmPrimitive> collection;
894                if (primitive instanceof Way) {
895                    collection = layer.data.getWays();
896                } else if (primitive instanceof Relation) {
897                    collection = layer.data.getRelations();
898                } else {
899                    collection = layer.data.allPrimitives();
900                }
901                for (OsmPrimitive p : collection) {
902                    if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) {
903                        return layer;
904                    }
905                }
906            }
907        }
908        return null;
909    }
910
911    @Override
912    public void decideRemaining(MergeDecisionType decision) {
913        if (!model.isFrozen()) {
914            model.copyAll(MergeDecisionType.KEEP_MINE == decision ? ListRole.MY_ENTRIES : ListRole.THEIR_ENTRIES);
915            model.setFrozen(true);
916        }
917    }
918}