source: josm/trunk/src/org/openstreetmap/josm/data/ImageData.java@ 17335

Last change on this file since 17335 was 17081, checked in by Don-vip, 4 years ago

fix #19865 - IOOBE selecting several pictures and attempting to delete them (patch by francois2)

  • Property svn:eol-style set to native
File size: 9.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.List;
10import java.util.stream.Collectors;
11
12import org.openstreetmap.josm.data.coor.LatLon;
13import org.openstreetmap.josm.data.gpx.GpxImageEntry;
14import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
15import org.openstreetmap.josm.tools.ListenerList;
16
17/**
18 * Class to hold {@link ImageEntry} and the current selection
19 * @since 14590
20 */
21public class ImageData implements Data {
22 /**
23 * A listener that is informed when the current selection change
24 */
25 public interface ImageDataUpdateListener {
26 /**
27 * Called when the data change
28 * @param data the image data
29 */
30 void imageDataUpdated(ImageData data);
31
32 /**
33 * Called when the selection change
34 * @param data the image data
35 */
36 void selectedImageChanged(ImageData data);
37 }
38
39 private final List<ImageEntry> data;
40
41 private final List<Integer> selectedImagesIndex = new ArrayList<>();
42
43 private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
44
45 /**
46 * Construct a new image container without images
47 */
48 public ImageData() {
49 this(null);
50 }
51
52 /**
53 * Construct a new image container with a list of images
54 * @param data the list of {@link ImageEntry}
55 */
56 public ImageData(List<ImageEntry> data) {
57 if (data != null) {
58 Collections.sort(data);
59 this.data = data;
60 } else {
61 this.data = new ArrayList<>();
62 }
63 selectedImagesIndex.add(-1);
64 }
65
66 /**
67 * Returns the images
68 * @return the images
69 */
70 public List<ImageEntry> getImages() {
71 return data;
72 }
73
74 /**
75 * Determines if one image has modified GPS data.
76 * @return {@code true} if data has been modified; {@code false}, otherwise
77 */
78 public boolean isModified() {
79 return data.stream().anyMatch(GpxImageEntry::hasNewGpsData);
80 }
81
82 /**
83 * Merge 2 ImageData
84 * @param otherData {@link ImageData} to merge
85 */
86 public void mergeFrom(ImageData otherData) {
87 data.addAll(otherData.getImages());
88 Collections.sort(data);
89
90 final ImageEntry selected = otherData.getSelectedImage();
91
92 // Suppress the double photos.
93 if (data.size() > 1) {
94 ImageEntry prev = data.get(data.size() - 1);
95 for (int i = data.size() - 2; i >= 0; i--) {
96 ImageEntry cur = data.get(i);
97 if (cur.getFile().equals(prev.getFile())) {
98 data.remove(i);
99 } else {
100 prev = cur;
101 }
102 }
103 }
104 if (selected != null) {
105 setSelectedImageIndex(data.indexOf(selected));
106 }
107 }
108
109 /**
110 * Return the first currently selected image
111 * @return the first selected image as {@link ImageEntry} or null
112 * @see #getSelectedImages
113 */
114 public ImageEntry getSelectedImage() {
115 int selectedImageIndex = selectedImagesIndex.isEmpty() ? -1 : selectedImagesIndex.get(0);
116 if (selectedImageIndex > -1) {
117 return data.get(selectedImageIndex);
118 }
119 return null;
120 }
121
122 /**
123 * Return the current selected images
124 * @return the selected images as list {@link ImageEntry}
125 * @since 15333
126 */
127 public List<ImageEntry> getSelectedImages() {
128 return selectedImagesIndex.stream().filter(i -> i > -1).map(data::get).collect(Collectors.toList());
129 }
130
131 /**
132 * Select the first image of the sequence
133 */
134 public void selectFirstImage() {
135 if (!data.isEmpty()) {
136 setSelectedImageIndex(0);
137 }
138 }
139
140 /**
141 * Select the last image of the sequence
142 */
143 public void selectLastImage() {
144 setSelectedImageIndex(data.size() - 1);
145 }
146
147 /**
148 * Check if there is a next image in the sequence
149 * @return {@code true} is there is a next image, {@code false} otherwise
150 */
151 public boolean hasNextImage() {
152 return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) != data.size() - 1);
153 }
154
155 /**
156 * Select the next image of the sequence
157 */
158 public void selectNextImage() {
159 if (hasNextImage()) {
160 setSelectedImageIndex(selectedImagesIndex.get(0) + 1);
161 }
162 }
163
164 /**
165 * Check if there is a previous image in the sequence
166 * @return {@code true} is there is a previous image, {@code false} otherwise
167 */
168 public boolean hasPreviousImage() {
169 return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) - 1 > -1);
170 }
171
172 /**
173 * Select the previous image of the sequence
174 */
175 public void selectPreviousImage() {
176 if (data.isEmpty()) {
177 return;
178 }
179 setSelectedImageIndex(Integer.max(0, selectedImagesIndex.get(0) - 1));
180 }
181
182 /**
183 * Select as the selected the given image
184 * @param image the selected image
185 */
186 public void setSelectedImage(ImageEntry image) {
187 setSelectedImageIndex(data.indexOf(image));
188 }
189
190 /**
191 * Add image to the list of selected images
192 * @param image {@link ImageEntry} the image to add
193 * @since 15333
194 */
195 public void addImageToSelection(ImageEntry image) {
196 int index = data.indexOf(image);
197 if (selectedImagesIndex.get(0) == -1) {
198 setSelectedImage(image);
199 } else if (!selectedImagesIndex.contains(index)) {
200 selectedImagesIndex.add(index);
201 listeners.fireEvent(l -> l.selectedImageChanged(this));
202 }
203 }
204
205 /**
206 * Remove the image from the list of selected images
207 * @param image {@link ImageEntry} the image to remove
208 * @since 15333
209 */
210 public void removeImageToSelection(ImageEntry image) {
211 int index = data.indexOf(image);
212 selectedImagesIndex.remove(selectedImagesIndex.indexOf(index));
213 if (selectedImagesIndex.isEmpty()) {
214 selectedImagesIndex.add(-1);
215 }
216 listeners.fireEvent(l -> l.selectedImageChanged(this));
217 }
218
219 /**
220 * Clear the selected image(s)
221 */
222 public void clearSelectedImage() {
223 setSelectedImageIndex(-1);
224 }
225
226 private void setSelectedImageIndex(int index) {
227 setSelectedImageIndex(index, false);
228 }
229
230 private void setSelectedImageIndex(int index, boolean forceTrigger) {
231 if (selectedImagesIndex.size() > 1) {
232 selectedImagesIndex.clear();
233 selectedImagesIndex.add(-1);
234 }
235 if (index == selectedImagesIndex.get(0) && !forceTrigger) {
236 return;
237 }
238 selectedImagesIndex.set(0, index);
239 listeners.fireEvent(l -> l.selectedImageChanged(this));
240 }
241
242 /**
243 * Remove the current selected image from the list
244 */
245 public void removeSelectedImage() {
246 List<ImageEntry> selectedImages = getSelectedImages();
247 if (selectedImages.size() > 1) {
248 throw new IllegalStateException(tr("Multiple images have been selected"));
249 }
250 removeImages(selectedImages);
251 }
252
253 /**
254 * Remove the current selected image from the list
255 * @since 15348
256 */
257 public void removeSelectedImages() {
258 List<ImageEntry> selectedImages = getSelectedImages();
259 removeImages(selectedImages);
260 }
261
262 private void removeImages(List<ImageEntry> selectedImages) {
263 if (selectedImages.isEmpty()) {
264 return;
265 }
266 for (ImageEntry img: getSelectedImages()) {
267 data.remove(img);
268 }
269 if (selectedImagesIndex.get(0) >= data.size()) {
270 setSelectedImageIndex(data.size() - 1);
271 } else {
272 setSelectedImageIndex(selectedImagesIndex.get(0), true);
273 }
274 }
275
276 /**
277 * Determines if the image is selected
278 * @param image the {@link ImageEntry} image
279 * @return {@code true} is the image is selected, {@code false} otherwise
280 * @since 15333
281 */
282 public boolean isImageSelected(ImageEntry image) {
283 int index = data.indexOf(image);
284 return selectedImagesIndex.contains(index);
285 }
286
287 /**
288 * Remove the image from the list and trigger update listener
289 * @param img the {@link ImageEntry} to remove
290 */
291 public void removeImage(ImageEntry img) {
292 data.remove(img);
293 notifyImageUpdate();
294 }
295
296 /**
297 * Update the position of the image and trigger update
298 * @param img the image to update
299 * @param newPos the new position
300 */
301 public void updateImagePosition(ImageEntry img, LatLon newPos) {
302 img.setPos(newPos);
303 afterImageUpdated(img);
304 }
305
306 /**
307 * Update the image direction of the image and trigger update
308 * @param img the image to update
309 * @param direction the new direction
310 */
311 public void updateImageDirection(ImageEntry img, double direction) {
312 img.setExifImgDir(direction);
313 afterImageUpdated(img);
314 }
315
316 /**
317 * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)}
318 */
319 public void notifyImageUpdate() {
320 listeners.fireEvent(l -> l.imageDataUpdated(this));
321 }
322
323 private void afterImageUpdated(ImageEntry img) {
324 img.flagNewGpsData();
325 notifyImageUpdate();
326 }
327
328 /**
329 * Add a listener that listens to image data changes
330 * @param listener the {@link ImageDataUpdateListener}
331 */
332 public void addImageDataUpdateListener(ImageDataUpdateListener listener) {
333 listeners.addListener(listener);
334 }
335
336 /**
337 * Removes a listener that listens to image data changes
338 * @param listener The listener
339 */
340 public void removeImageDataUpdateListener(ImageDataUpdateListener listener) {
341 listeners.removeListener(listener);
342 }
343
344 @Override
345 public Collection<DataSource> getDataSources() {
346 return Collections.emptyList();
347 }
348}
Note: See TracBrowser for help on using the repository browser.