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