001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.changeset; 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Comparator; 007import java.util.List; 008import java.util.Set; 009import java.util.stream.Collectors; 010import java.util.stream.IntStream; 011 012import javax.swing.DefaultListModel; 013import javax.swing.DefaultListSelectionModel; 014 015import org.openstreetmap.josm.data.osm.AbstractPrimitive; 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.data.osm.DataSet; 021import org.openstreetmap.josm.data.osm.OsmPrimitive; 022import org.openstreetmap.josm.data.osm.Storage; 023import org.openstreetmap.josm.gui.util.GuiHelper; 024import org.openstreetmap.josm.gui.util.TableHelper; 025import org.openstreetmap.josm.tools.Utils; 026 027/** 028 * This is the model that backs a list of changesets 029 */ 030public class ChangesetListModel extends DefaultListModel<Changeset> implements ChangesetCacheListener { 031 private final transient List<Changeset> data = new ArrayList<>(); 032 private final transient Storage<Changeset> shownChangesets = new Storage<>(true); 033 private final DefaultListSelectionModel selectionModel; 034 035 /** 036 * Creates a new {@link ChangesetListModel} 037 * @param selectionModel The selection model to use for this list 038 */ 039 public ChangesetListModel(DefaultListSelectionModel selectionModel) { 040 this.selectionModel = selectionModel; 041 } 042 043 /** 044 * Gets the list of changesets that are currently selected 045 * @return The selected changesets 046 */ 047 public synchronized Set<Changeset> getSelectedChangesets() { 048 return IntStream.range(0, getSize()).filter(selectionModel::isSelectedIndex) 049 .mapToObj(data::get).collect(Collectors.toSet()); 050 } 051 052 /** 053 * Gets the IDs of the changesets that are selected 054 * @return The selected ids 055 */ 056 public synchronized Set<Integer> getSelectedChangesetIds() { 057 return IntStream.range(0, getSize()).filter(selectionModel::isSelectedIndex) 058 .mapToObj(data::get).map(Changeset::getId).collect(Collectors.toSet()); 059 } 060 061 /** 062 * Sets the changesets to select 063 * @param changesets The changesets 064 */ 065 public synchronized void setSelectedChangesets(Collection<Changeset> changesets) { 066 TableHelper.setSelectedIndices(selectionModel, 067 changesets != null ? changesets.stream().mapToInt(data::indexOf) : IntStream.empty()); 068 } 069 070 protected void setChangesets(Collection<Changeset> changesets) { 071 shownChangesets.clear(); 072 if (changesets != null) { 073 shownChangesets.addAll(changesets); 074 } 075 updateModel(); 076 } 077 078 private synchronized void updateModel() { 079 Set<Changeset> sel = getSelectedChangesets(); 080 data.clear(); 081 data.addAll(shownChangesets); 082 ChangesetCache cache = ChangesetCache.getInstance(); 083 for (Changeset cs: data) { 084 if (cache.contains(cs) && cache.get(cs.getId()) != cs) { 085 cs.mergeFrom(cache.get(cs.getId())); 086 } 087 } 088 sort(); 089 fireIntervalAdded(this, 0, getSize()); 090 setSelectedChangesets(sel); 091 } 092 093 /** 094 * Loads this list with the given changesets 095 * @param ids The ids of the changesets to display 096 */ 097 public void initFromChangesetIds(Collection<Integer> ids) { 098 if (Utils.isEmpty(ids)) { 099 setChangesets(null); 100 return; 101 } 102 Set<Changeset> changesets = ids.stream().mapToInt(id -> id) 103 .filter(id -> id > 0).mapToObj(Changeset::new).collect(Collectors.toSet()); 104 setChangesets(changesets); 105 } 106 107 /** 108 * Loads this list with the given changesets 109 * @param primitives The primitives of which the changesets should be displayed 110 */ 111 public void initFromPrimitives(Collection<? extends OsmPrimitive> primitives) { 112 if (primitives == null) { 113 setChangesets(null); 114 return; 115 } 116 initFromChangesetIds(primitives.stream().map(AbstractPrimitive::getChangesetId).collect(Collectors.toList())); 117 } 118 119 /** 120 * Loads this list with the given changesets 121 * @param ds The data set to get all changesets from 122 */ 123 public void initFromDataSet(DataSet ds) { 124 if (ds == null) { 125 setChangesets(null); 126 return; 127 } 128 initFromChangesetIds(ds.allPrimitives().stream().map(AbstractPrimitive::getChangesetId).collect(Collectors.toList())); 129 } 130 131 @Override 132 public synchronized Changeset getElementAt(int idx) { 133 return data.get(idx); 134 } 135 136 @Override 137 public synchronized int getSize() { 138 return data.size(); 139 } 140 141 protected synchronized void sort() { 142 data.sort(Comparator.comparingInt(Changeset::getId).reversed()); 143 } 144 145 /** 146 * Replies true if there is at least one selected open changeset 147 * 148 * @return true if there is at least one selected open changeset 149 */ 150 public boolean hasSelectedOpenChangesets() { 151 return !getSelectedOpenChangesets().isEmpty(); 152 } 153 154 /** 155 * Replies the selected open changesets 156 * 157 * @return the selected open changesets 158 */ 159 public synchronized List<Changeset> getSelectedOpenChangesets() { 160 return IntStream.range(0, getSize()) 161 .filter(selectionModel::isSelectedIndex) 162 .mapToObj(data::get) 163 .filter(Changeset::isOpen) 164 .collect(Collectors.toList()); 165 } 166 167 /* ---------------------------------------------------------------------------- */ 168 /* Interface ChangesetCacheListener */ 169 /* ---------------------------------------------------------------------------- */ 170 @Override 171 public synchronized void changesetCacheUpdated(ChangesetCacheEvent event) { 172 Set<Changeset> sel = getSelectedChangesets(); 173 for (Changeset cs: event.getAddedChangesets()) { 174 int idx = data.indexOf(cs); 175 if (idx >= 0 && data.get(idx) != cs) { 176 data.get(idx).mergeFrom(cs); 177 } 178 } 179 for (Changeset cs: event.getUpdatedChangesets()) { 180 int idx = data.indexOf(cs); 181 if (idx >= 0 && data.get(idx) != cs) { 182 data.get(idx).mergeFrom(cs); 183 } 184 } 185 for (Changeset cs: event.getRemovedChangesets()) { 186 int idx = data.indexOf(cs); 187 if (idx >= 0) { 188 // replace with an incomplete changeset 189 data.set(idx, new Changeset(cs.getId())); 190 } 191 } 192 GuiHelper.runInEDT(() -> { 193 fireContentsChanged(this, 0, getSize()); 194 setSelectedChangesets(sel); 195 }); 196 } 197}