001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset;
003
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyChangeSupport;
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.Comparator;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Set;
012
013import javax.swing.DefaultListSelectionModel;
014import javax.swing.table.AbstractTableModel;
015
016import org.openstreetmap.josm.data.osm.Changeset;
017import org.openstreetmap.josm.data.osm.ChangesetCache;
018import org.openstreetmap.josm.data.osm.ChangesetCacheEvent;
019import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
020import org.openstreetmap.josm.gui.util.GuiHelper;
021
022/**
023 * This is the model for the changeset cache manager dialog.
024 */
025public class ChangesetCacheManagerModel extends AbstractTableModel implements ChangesetCacheListener {
026
027    /** the name of the property for the currently selected changeset in the detail view */
028    public static final String CHANGESET_IN_DETAIL_VIEW_PROP = ChangesetCacheManagerModel.class.getName() + ".changesetInDetailView";
029
030    private final transient List<Changeset> data = new ArrayList<>();
031    private final DefaultListSelectionModel selectionModel;
032    private transient Changeset changesetInDetailView;
033    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
034
035    /**
036     * Creates a new ChangesetCacheManagerModel that is based on the selectionModel
037     * @param selectionModel A new selection model that should be used.
038     */
039    public ChangesetCacheManagerModel(DefaultListSelectionModel selectionModel) {
040        this.selectionModel = selectionModel;
041    }
042
043    /**
044     * Adds a property change listener to this model.
045     * @param listener The listener
046     */
047    public void addPropertyChangeListener(PropertyChangeListener listener) {
048        support.addPropertyChangeListener(listener);
049    }
050
051    /**
052     * Removes a property change listener from this model.
053     * @param listener The listener
054     */
055    public void removePropertyChangeListener(PropertyChangeListener listener) {
056        support.removePropertyChangeListener(listener);
057    }
058
059    /**
060     * Sets the changeset currently displayed in the detail view. Fires a property change event
061     * for the property {@link #CHANGESET_IN_DETAIL_VIEW_PROP} if necessary.
062     *
063     * @param cs the changeset currently displayed in the detail view.
064     */
065    public void setChangesetInDetailView(Changeset cs) {
066        Changeset oldValue = changesetInDetailView;
067        changesetInDetailView = cs;
068        if (oldValue != cs) {
069            support.firePropertyChange(CHANGESET_IN_DETAIL_VIEW_PROP, oldValue, changesetInDetailView);
070        }
071    }
072
073    /**
074     * Replies true if there is at least one selected changeset
075     *
076     * @return true if there is at least one selected changeset
077     */
078    public boolean hasSelectedChangesets() {
079        return selectionModel.getMinSelectionIndex() >= 0;
080    }
081
082    /**
083     * Replies the list of selected changesets
084     *
085     * @return the list of selected changesets
086     */
087    public List<Changeset> getSelectedChangesets() {
088        List<Changeset> ret = new ArrayList<>();
089        for (int i = 0; i < data.size(); i++) {
090            Changeset cs = data.get(i);
091            if (selectionModel.isSelectedIndex(i)) {
092                ret.add(cs);
093            }
094        }
095        return ret;
096    }
097
098    /**
099     * Replies a set of ids of the selected changesets
100     *
101     * @return a set of ids of the selected changesets
102     */
103    public Set<Integer> getSelectedChangesetIds() {
104        Set<Integer> ret = new HashSet<>();
105        for (Changeset cs: getSelectedChangesets()) {
106            ret.add(cs.getId());
107        }
108        return ret;
109    }
110
111    /**
112     * Selects the changesets in <code>selected</code>.
113     *
114     * @param selected the collection of changesets to select. Ignored if empty.
115     */
116    public void setSelectedChangesets(Collection<Changeset> selected) {
117        selectionModel.setValueIsAdjusting(true);
118        selectionModel.clearSelection();
119        if (selected != null) {
120            for (Changeset cs: selected) {
121                final int idx = data.indexOf(cs);
122                if (idx >= 0) {
123                    selectionModel.addSelectionInterval(idx, idx);
124                }
125            }
126        }
127        GuiHelper.runInEDTAndWait(() -> selectionModel.setValueIsAdjusting(false));
128    }
129
130    @Override
131    public int getColumnCount() {
132        return 5;
133    }
134
135    @Override
136    public int getRowCount() {
137        return data.size();
138    }
139
140    @Override
141    public Changeset getValueAt(int row, int column) {
142        return data.get(row);
143    }
144
145    /**
146     * Initializes the data that is displayed using the changeset cache.
147     */
148    public void init() {
149        ChangesetCache cc = ChangesetCache.getInstance();
150        List<Changeset> selected = getSelectedChangesets();
151        data.clear();
152        data.addAll(cc.getChangesets());
153        sort();
154        fireTableDataChanged();
155        setSelectedChangesets(selected);
156
157        cc.addChangesetCacheListener(this);
158    }
159
160    /**
161     * Destroys and unregisters this model.
162     */
163    public void tearDown() {
164        ChangesetCache.getInstance().removeChangesetCacheListener(this);
165    }
166
167    /**
168     * Gets the selection model this table is based on.
169     * @return The selection model.
170     */
171    public DefaultListSelectionModel getSelectionModel() {
172        return selectionModel;
173    }
174
175    protected void sort() {
176        data.sort(Comparator.comparingInt(Changeset::getId).reversed());
177    }
178
179    /* ------------------------------------------------------------------------------ */
180    /* interface ChangesetCacheListener                                               */
181    /* ------------------------------------------------------------------------------ */
182    @Override
183    public void changesetCacheUpdated(ChangesetCacheEvent event) {
184        List<Changeset> selected = getSelectedChangesets();
185        for (Changeset cs: event.getAddedChangesets()) {
186            data.add(cs);
187        }
188        for (Changeset cs: event.getRemovedChangesets()) {
189            data.remove(cs);
190        }
191        for (Changeset cs: event.getUpdatedChangesets()) {
192            int idx = data.indexOf(cs);
193            if (idx >= 0) {
194                Changeset mine = data.get(idx);
195                if (mine != cs) {
196                    mine.mergeFrom(cs);
197                }
198            }
199        }
200        GuiHelper.runInEDT(() -> {
201            sort();
202            fireTableDataChanged();
203            setSelectedChangesets(selected);
204        });
205    }
206}