001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import java.util.HashSet;
005import java.util.Objects;
006import java.util.Set;
007import java.util.function.BiFunction;
008import java.util.stream.IntStream;
009
010import javax.swing.ListSelectionModel;
011import javax.swing.event.ListSelectionEvent;
012import javax.swing.event.ListSelectionListener;
013
014import org.openstreetmap.josm.gui.util.TableHelper;
015
016/**
017 * Helper class to ensure that two (or more) {@link javax.swing.JTable}s always
018 * have the same entries selected.
019 * 
020 * The tables are usually displayed side-by-side.
021 */
022public class SelectionSynchronizer implements ListSelectionListener {
023
024    private final Set<ListSelectionModel> participants;
025    private boolean preventRecursion;
026    private BiFunction<Integer, ListSelectionModel, IntStream> selectionIndexMapper = (i, model) -> IntStream.of(i);
027
028    /**
029     * Constructs a new {@code SelectionSynchronizer}.
030     */
031    public SelectionSynchronizer() {
032        participants = new HashSet<>();
033    }
034
035    /**
036     * Add {@link ListSelectionModel} of the table to participate in selection
037     * synchronization.
038     * 
039     * Call this method for all tables that should have their selection synchronized.
040     * @param model the selection model of the table
041     */
042    public void participateInSynchronizedSelection(ListSelectionModel model) {
043        if (model == null)
044            return;
045        if (participants.contains(model))
046            return;
047        participants.add(model);
048        model.addListSelectionListener(this);
049    }
050
051    void setSelectionIndexMapper(BiFunction<Integer, ListSelectionModel, IntStream> selectionIndexMapper) {
052        this.selectionIndexMapper = Objects.requireNonNull(selectionIndexMapper);
053    }
054
055    @Override
056    public void valueChanged(ListSelectionEvent e) {
057        if (preventRecursion) {
058            return;
059        }
060        preventRecursion = true;
061        ListSelectionModel referenceModel = (ListSelectionModel) e.getSource();
062        for (ListSelectionModel model : participants) {
063            if (model == referenceModel) {
064                continue;
065            }
066            TableHelper.setSelectedIndices(model,
067                    TableHelper.selectedIndices(referenceModel).flatMap(i -> selectionIndexMapper.apply(i, referenceModel)));
068        }
069        preventRecursion = false;
070    }
071}