Ticket #17050: refactor_geoimagelayer.3.patch
| File refactor_geoimagelayer.3.patch, 61.8 KB (added by , 7 years ago) |
|---|
-
new file src/org/openstreetmap/josm/data/ImageData.java
diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java new file mode 100644 index 000000000..16c8db585
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data; 3 4 import java.util.ArrayList; 5 import java.util.Collections; 6 import java.util.List; 7 8 import org.openstreetmap.josm.data.coor.LatLon; 9 import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry; 10 import org.openstreetmap.josm.tools.ListenerList; 11 12 /** 13 * Class to hold {@link ImageEntry} and the current selection 14 * @since xxx 15 */ 16 public class ImageData { 17 /** 18 * A listener that is informed when the current selection change 19 */ 20 public interface ImageDataUpdateListener { 21 /** 22 * Called when the data change 23 * @param data the image data 24 */ 25 void imageDataUpdated(ImageData data); 26 /** 27 * Called when the selection change 28 * @param data the image data 29 */ 30 void selectedImageChanged(ImageData data); 31 } 32 33 private final List<ImageEntry> data; 34 35 private int selectedImageIndex = -1; 36 37 private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create(); 38 39 /** 40 * Construct a new image container without images 41 */ 42 public ImageData() { 43 this(null); 44 } 45 46 /** 47 * Construct a new image container with a list of images 48 * @param data the list of {@link ImageEntry} 49 */ 50 public ImageData(List<ImageEntry> data) { 51 if (data != null) { 52 Collections.sort(data); 53 this.data = data; 54 } else { 55 this.data = new ArrayList<>(); 56 } 57 } 58 59 /** 60 * Returns the images 61 * @return the images 62 */ 63 public List<ImageEntry> getImages() { 64 return this.data; 65 } 66 67 /** 68 * Determines if one image has modified GPS data. 69 * @return {@code true} if data has been modified; {@code false}, otherwise 70 */ 71 public boolean isModified() { 72 for (ImageEntry e : data) { 73 if (e.hasNewGpsData()) { 74 return true; 75 } 76 } 77 return false; 78 } 79 80 /** 81 * Merge 2 ImageData 82 * @param data {@link ImageData} 83 */ 84 public void mergeFrom(ImageData data) { 85 this.data.addAll(data.getImages()); 86 Collections.sort(this.data); 87 88 final ImageEntry selected = data.getSelectedImage(); 89 90 // Suppress the double photos. 91 if (this.data.size() > 1) { 92 ImageEntry cur; 93 ImageEntry prev = this.data.get(this.data.size() - 1); 94 for (int i = this.data.size() - 2; i >= 0; i--) { 95 cur = this.data.get(i); 96 if (cur.getFile().equals(prev.getFile())) { 97 this.data.remove(i); 98 } else { 99 prev = cur; 100 } 101 } 102 } 103 if (selected != null) { 104 this.setSelectedImageIndex(this.data.indexOf(selected)); 105 } 106 } 107 108 /** 109 * Return the current selected image 110 * @return the selected image as {@link ImageEntry} or null 111 */ 112 public ImageEntry getSelectedImage() { 113 if (this.selectedImageIndex > -1) { 114 return data.get(this.selectedImageIndex); 115 } 116 return null; 117 } 118 119 /** 120 * Select the first image of the sequence 121 */ 122 public void selectFirstImage() { 123 if (!data.isEmpty()) { 124 this.setSelectedImageIndex(0); 125 } 126 } 127 128 /** 129 * Select the last image of the sequence 130 */ 131 public void selectLastImage() { 132 this.setSelectedImageIndex(data.size() - 1); 133 } 134 135 /** 136 * Check if there is a next image in the sequence 137 * @return {@code true} is there is a next image, {@code false} otherwise 138 */ 139 public boolean hasNextImage() { 140 return (this.selectedImageIndex != data.size() - 1); 141 } 142 143 /** 144 * Select the next image of the sequence 145 */ 146 public void selectNextImage() { 147 if (this.hasNextImage()) { 148 this.setSelectedImageIndex(this.selectedImageIndex + 1); 149 } 150 } 151 152 /** 153 * Check if there is a previous image in the sequence 154 * @return {@code true} is there is a previous image, {@code false} otherwise 155 */ 156 public boolean hasPreviousImage() { 157 return this.selectedImageIndex - 1 > -1; 158 } 159 160 /** 161 * Select the previous image of the sequence 162 */ 163 public void selectPreviousImage() { 164 if (data.isEmpty()) { 165 return; 166 } 167 this.setSelectedImageIndex(Integer.max(0, this.selectedImageIndex - 1)); 168 } 169 170 /** 171 * Select as the selected the given image 172 * @param image the selected image 173 */ 174 public void setSelectedImage(ImageEntry image) { 175 this.setSelectedImageIndex(this.data.indexOf(image)); 176 } 177 178 /** 179 * Clear the selected image 180 */ 181 public void clearSelectedImage() { 182 this.setSelectedImageIndex(-1); 183 } 184 185 private void setSelectedImageIndex(int index) { 186 this.setSelectedImageIndex(index, false); 187 } 188 189 private void setSelectedImageIndex(int index, boolean forceTrigger) { 190 if (index == this.selectedImageIndex && !forceTrigger) { 191 return; 192 } 193 this.selectedImageIndex = index; 194 listeners.fireEvent(l -> l.selectedImageChanged(this)); 195 } 196 197 /** 198 * Remove the current selected image from the list 199 */ 200 public void removeSelectedImage() { 201 data.remove(this.getSelectedImage()); 202 if (this.selectedImageIndex == data.size()) { 203 this.setSelectedImageIndex(data.size() - 1); 204 } else { 205 this.setSelectedImageIndex(this.selectedImageIndex, true); 206 } 207 } 208 209 /** 210 * Remove the image from the list and trigger update listener 211 * @param img the {@link ImageEntry} to remove 212 */ 213 public void removeImage(ImageEntry img) { 214 data.remove(img); 215 this.notifyImageUpdate(); 216 } 217 218 /** 219 * Update the position of the image and trigger update 220 * @param img 221 * @param newPos 222 */ 223 public void updateImagePosition(ImageEntry img, LatLon newPos) { 224 img.setPos(newPos); 225 this.afterImageUpdated(img); 226 } 227 228 /** 229 * Update the image direction of the image and trigger update 230 * @param img 231 * @param direction 232 */ 233 public void updateImageDirection(ImageEntry img, double direction) { 234 img.setExifImgDir(direction); 235 this.afterImageUpdated(img); 236 } 237 238 /** 239 * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)} 240 */ 241 public void notifyImageUpdate() { 242 listeners.fireEvent(l -> l.imageDataUpdated(this)); 243 } 244 245 private void afterImageUpdated(ImageEntry img) { 246 img.flagNewGpsData(); 247 this.notifyImageUpdate(); 248 } 249 250 /** 251 * Add a listener that listens to image data changes 252 * @param listener the {@link ImageDataUpdateListener} 253 */ 254 public void addImageDataUpdateListener(ImageDataUpdateListener listener) { 255 listeners.addListener(listener); 256 } 257 258 /** 259 * Removes a listener that listens to image data changes 260 * @param listener The listener 261 */ 262 public void removeImageDataUpdateListener(ImageDataUpdateListener listener) { 263 listeners.removeListener(listener); 264 } 265 } -
src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java b/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java index 9badaffd1..2488f409c 100644
a b public class CorrelateGpxWithImages extends AbstractAction { 178 178 break; 179 179 case CANCEL: 180 180 if (yLayer != null) { 181 if (yLayer.data != null) { 182 for (ImageEntry ie : yLayer.data) { 183 ie.discardTmp(); 184 } 181 for (ImageEntry ie : yLayer.getImageData().getImages()) { 182 ie.discardTmp(); 185 183 } 184 186 185 yLayer.updateBufferAndRepaint(); 187 186 } 188 187 break; … … public class CorrelateGpxWithImages extends AbstractAction { 216 215 MainApplication.getMap().mapView.zoomTo(bbox); 217 216 } 218 217 219 if (yLayer.data != null) { 220 for (ImageEntry ie : yLayer.data) { 221 ie.applyTmp(); 222 } 218 219 for (ImageEntry ie : yLayer.getImageData().getImages()) { 220 ie.applyTmp(); 223 221 } 224 222 223 225 224 yLayer.updateBufferAndRepaint(); 226 225 227 226 break; … … public class CorrelateGpxWithImages extends AbstractAction { 645 644 JList<String> imgList = new JList<>(new AbstractListModel<String>() { 646 645 @Override 647 646 public String getElementAt(int i) { 648 return yLayer. data.get(i).getFile().getName();647 return yLayer.getImageData().getImages().get(i).getFile().getName(); 649 648 } 650 649 651 650 @Override 652 651 public int getSize() { 653 return yLayer. data != null ? yLayer.data.size() : 0;652 return yLayer.getImageData().getImages().size(); 654 653 } 655 654 }); 656 655 imgList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 657 656 imgList.getSelectionModel().addListSelectionListener(evt -> { 658 657 int index = imgList.getSelectedIndex(); 659 imgDisp.setImage(yLayer.data.get(index)); 660 Date date = yLayer.data.get(index).getExifTime(); 658 ImageEntry img = yLayer.getImageData().getImages().get(index); 659 imgDisp.setImage(img); 660 Date date = img.getExifTime(); 661 661 if (date != null) { 662 662 DateFormat df = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM); 663 663 lbExifTime.setText(df.format(date)); … … public class CorrelateGpxWithImages extends AbstractAction { 1041 1041 1042 1042 // The selection of images we are about to correlate may have changed. 1043 1043 // So reset all images. 1044 if (yLayer.data != null) { 1045 for (ImageEntry ie: yLayer.data) { 1046 ie.discardTmp(); 1047 } 1044 for (ImageEntry ie: yLayer.getImageData().getImages()) { 1045 ie.discardTmp(); 1048 1046 } 1049 1047 1050 1048 // Construct a list of images that have a date, and sort them on the date. … … public class CorrelateGpxWithImages extends AbstractAction { 1300 1298 * @return matching images 1301 1299 */ 1302 1300 private List<ImageEntry> getSortedImgList(boolean exif, boolean tagged) { 1303 if (yLayer.data == null) { 1304 return Collections.emptyList(); 1305 } 1306 List<ImageEntry> dateImgLst = new ArrayList<>(yLayer.data.size()); 1307 for (ImageEntry e : yLayer.data) { 1301 List<ImageEntry> dateImgLst = new ArrayList<>(yLayer.getImageData().getImages().size()); 1302 for (ImageEntry e : yLayer.getImageData().getImages()) { 1308 1303 if (!e.hasExifTime()) { 1309 1304 continue; 1310 1305 } -
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java index a9bb91f33..47e576e5a 100644
a b import java.io.IOException; 23 23 import java.util.ArrayList; 24 24 import java.util.Arrays; 25 25 import java.util.Collection; 26 import java.util.Collections;27 26 import java.util.HashSet; 28 27 import java.util.LinkedHashSet; 29 28 import java.util.LinkedList; … … import java.util.concurrent.Executors; 34 33 35 34 import javax.swing.Action; 36 35 import javax.swing.Icon; 37 import javax.swing.JLabel;38 36 import javax.swing.JOptionPane; 39 import javax.swing.SwingConstants;40 37 41 38 import org.openstreetmap.josm.actions.LassoModeAction; 42 39 import org.openstreetmap.josm.actions.RenameLayerAction; 43 40 import org.openstreetmap.josm.actions.mapmode.MapMode; 44 41 import org.openstreetmap.josm.actions.mapmode.SelectAction; 45 42 import org.openstreetmap.josm.data.Bounds; 43 import org.openstreetmap.josm.data.ImageData; 44 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener; 46 45 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 47 import org.openstreetmap.josm.gui.ExtendedDialog;48 46 import org.openstreetmap.josm.gui.MainApplication; 49 47 import org.openstreetmap.josm.gui.MapFrame; 50 48 import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener; 51 49 import org.openstreetmap.josm.gui.MapView; 52 50 import org.openstreetmap.josm.gui.NavigatableComponent; 53 51 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 54 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;55 52 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 56 53 import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 57 54 import org.openstreetmap.josm.gui.io.importexport.JpgImporter; … … import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker; 62 59 import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker; 63 60 import org.openstreetmap.josm.gui.layer.Layer; 64 61 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 65 import org.openstreetmap.josm.gui.util.GuiHelper;66 62 import org.openstreetmap.josm.tools.ImageProvider; 67 63 import org.openstreetmap.josm.tools.Logging; 68 64 import org.openstreetmap.josm.tools.Utils; … … import org.openstreetmap.josm.tools.Utils; 71 67 * Layer displaying geottaged pictures. 72 68 */ 73 69 public class GeoImageLayer extends AbstractModifiableLayer implements 74 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener {70 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener { 75 71 76 72 private static List<Action> menuAdditions = new LinkedList<>(); 77 73 78 74 private static volatile List<MapMode> supportedMapModes; 79 75 80 List<ImageEntry>data;76 private final ImageData data; 81 77 GpxLayer gpxLayer; 82 78 83 79 private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker"); 84 80 private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected"); 85 81 86 private int currentPhoto = -1;87 88 82 boolean useThumbs; 89 83 private final ExecutorService thumbsLoaderExecutor = 90 84 Executors.newSingleThreadExecutor(Utils.newThreadFactory("thumbnail-loader-%d", Thread.MIN_PRIORITY)); … … public class GeoImageLayer extends AbstractModifiableLayer implements 151 145 */ 152 146 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer, final String name, boolean useThumbs) { 153 147 super(name != null ? name : tr("Geotagged Images")); 154 if (data != null) { 155 Collections.sort(data); 156 } 157 this.data = data; 148 this.data = new ImageData(data); 158 149 this.gpxLayer = gpxLayer; 159 150 this.useThumbs = useThumbs; 151 this.data.addImageDataUpdateListener(this); 160 152 } 161 153 162 154 /** … … public class GeoImageLayer extends AbstractModifiableLayer implements 218 210 e.extractExif(); 219 211 entries.add(e); 220 212 } 213 221 214 layer = new GeoImageLayer(entries, gpxLayer); 222 215 files.clear(); 223 216 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 292 285 if (layer != null) { 293 286 MainApplication.getLayerManager().addLayer(layer); 294 287 295 if (!canceled && layer.data != null && !layer.data.isEmpty()) {288 if (!canceled && !layer.getImageData().getImages().isEmpty()) { 296 289 boolean noGeotagFound = true; 297 for (ImageEntry e : layer. data) {290 for (ImageEntry e : layer.getImageData().getImages()) { 298 291 if (e.getPos() != null) { 299 292 noGeotagFound = false; 300 293 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 311 304 } 312 305 } 313 306 307 /** 308 * Create a GeoImageLayer asynchronously 309 * @param files 310 * @param gpxLayer 311 */ 314 312 public static void create(Collection<File> files, GpxLayer gpxLayer) { 315 313 MainApplication.worker.execute(new Loader(files, gpxLayer)); 316 314 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 320 318 return ImageProvider.get("dialogs/geoimage", ImageProvider.ImageSizes.LAYER); 321 319 } 322 320 321 /** 322 * Register actions on the layer 323 * @param addition 324 */ 323 325 public static void registerMenuAddition(Action addition) { 324 326 menuAdditions.add(addition); 325 327 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 356 358 private String infoText() { 357 359 int tagged = 0; 358 360 int newdata = 0; 359 int n = 0; 360 if (data != null) { 361 n = data.size(); 362 for (ImageEntry e : data) { 363 if (e.getPos() != null) { 364 tagged++; 365 } 366 if (e.hasNewGpsData()) { 367 newdata++; 368 } 361 int n = data.getImages().size(); 362 for (ImageEntry e : data.getImages()) { 363 if (e.getPos() != null) { 364 tagged++; 365 } 366 if (e.hasNewGpsData()) { 367 newdata++; 369 368 } 370 369 } 371 370 return "<html>" … … public class GeoImageLayer extends AbstractModifiableLayer implements 391 390 */ 392 391 @Override 393 392 public boolean isModified() { 394 if (data != null) { 395 for (ImageEntry e : data) { 396 if (e.hasNewGpsData()) { 397 return true; 398 } 399 } 400 } 401 return false; 393 return this.data.isModified(); 402 394 } 403 395 404 396 @Override … … public class GeoImageLayer extends AbstractModifiableLayer implements 417 409 stopLoadThumbs(); 418 410 l.stopLoadThumbs(); 419 411 420 final ImageEntry selected = l.data != null && l.currentPhoto >= 0 ? l.data.get(l.currentPhoto) : null; 421 422 if (l.data != null) { 423 data.addAll(l.data); 424 } 425 Collections.sort(data); 426 427 // Suppress the double photos. 428 if (data.size() > 1) { 429 ImageEntry cur; 430 ImageEntry prev = data.get(data.size() - 1); 431 for (int i = data.size() - 2; i >= 0; i--) { 432 cur = data.get(i); 433 if (cur.getFile().equals(prev.getFile())) { 434 data.remove(i); 435 } else { 436 prev = cur; 437 } 438 } 439 } 440 441 if (selected != null && !data.isEmpty()) { 442 GuiHelper.runInEDTAndWait(() -> { 443 for (int i = 0; i < data.size(); i++) { 444 if (selected.equals(data.get(i))) { 445 currentPhoto = i; 446 ImageViewerDialog.showImage(this, data.get(i)); 447 break; 448 } 449 } 450 }); 451 } 412 this.data.mergeFrom(l.getImageData()); 452 413 453 414 setName(l.getName()); 454 415 thumbsLoaded &= l.thumbsLoaded; … … public class GeoImageLayer extends AbstractModifiableLayer implements 530 491 tempG.fillRect(0, 0, width, height); 531 492 tempG.setComposite(saveComp); 532 493 533 if (data != null) { 534 for (ImageEntry e : data) { 535 paintImage(e, mv, clip, tempG); 536 } 537 if (currentPhoto >= 0 && currentPhoto < data.size()) { 538 // Make sure the selected image is on top in case multiple images overlap. 539 paintImage(data.get(currentPhoto), mv, clip, tempG); 540 } 494 for (ImageEntry e : this.data.getImages()) { 495 paintImage(e, mv, clip, tempG); 496 } 497 if (this.data.getSelectedImage() != null) { 498 // Make sure the selected image is on top in case multiple images overlap. 499 paintImage(this.data.getSelectedImage(), mv, clip, tempG); 541 500 } 542 501 updateOffscreenBuffer = false; 543 502 } 544 503 g.drawImage(offscreenBuffer, 0, 0, null); 545 } else if (data != null){546 for (ImageEntry e : data ) {504 } else { 505 for (ImageEntry e : data.getImages()) { 547 506 if (e.getPos() == null) { 548 507 continue; 549 508 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 554 513 } 555 514 } 556 515 557 if (currentPhoto >= 0 && currentPhoto < data.size()) { 558 ImageEntry e = data.get(currentPhoto); 559 516 ImageEntry e = data.getSelectedImage(); 517 if (e != null) { 560 518 if (e.getPos() != null) { 561 519 Point p = mv.getPoint(e.getPos()); 562 520 … … public class GeoImageLayer extends AbstractModifiableLayer implements 621 579 622 580 @Override 623 581 public void visitBoundingBox(BoundingXYVisitor v) { 624 for (ImageEntry e : data ) {582 for (ImageEntry e : data.getImages()) { 625 583 v.visit(e.getPos()); 626 584 } 627 585 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 630 588 * Show current photo on map and in image viewer. 631 589 */ 632 590 public void showCurrentPhoto() { 633 clearOtherCurrentPhotos(); 634 if (currentPhoto >= 0) { 635 ImageViewerDialog.showImage(this, data.get(currentPhoto)); 636 } else { 637 ImageViewerDialog.showImage(this, null); 591 if (data.getSelectedImage() != null) { 592 clearOtherCurrentPhotos(); 638 593 } 639 594 updateBufferAndRepaint(); 640 595 } 641 596 642 /**643 * Shows next photo.644 */645 public void showNextPhoto() {646 if (data != null && !data.isEmpty()) {647 currentPhoto++;648 if (currentPhoto >= data.size()) {649 currentPhoto = data.size() - 1;650 }651 } else {652 currentPhoto = -1;653 }654 showCurrentPhoto();655 }656 657 /**658 * Shows previous photo.659 */660 public void showPreviousPhoto() {661 if (data != null && !data.isEmpty()) {662 currentPhoto--;663 if (currentPhoto < 0) {664 currentPhoto = 0;665 }666 } else {667 currentPhoto = -1;668 }669 showCurrentPhoto();670 }671 672 /**673 * Shows first photo.674 */675 public void showFirstPhoto() {676 if (data != null && !data.isEmpty()) {677 currentPhoto = 0;678 } else {679 currentPhoto = -1;680 }681 showCurrentPhoto();682 }683 684 /**685 * Shows last photo.686 */687 public void showLastPhoto() {688 if (data != null && !data.isEmpty()) {689 currentPhoto = data.size() - 1;690 } else {691 currentPhoto = -1;692 }693 showCurrentPhoto();694 }695 696 public void checkPreviousNextButtons() {697 ImageViewerDialog.setNextEnabled(data != null && currentPhoto < data.size() - 1);698 ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);699 }700 701 public void removeCurrentPhoto() {702 if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {703 data.remove(currentPhoto);704 if (currentPhoto >= data.size()) {705 currentPhoto = data.size() - 1;706 }707 showCurrentPhoto();708 }709 }710 711 public void removeCurrentPhotoFromDisk() {712 ImageEntry toDelete;713 if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {714 toDelete = data.get(currentPhoto);715 716 int result = new ExtendedDialog(717 MainApplication.getMainFrame(),718 tr("Delete image file from disk"),719 tr("Cancel"), tr("Delete"))720 .setButtonIcons("cancel", "dialogs/delete")721 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>",722 toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"), SwingConstants.LEFT))723 .toggleEnable("geoimage.deleteimagefromdisk")724 .setCancelButton(1)725 .setDefaultButton(2)726 .showDialog()727 .getValue();728 729 if (result == 2) {730 data.remove(currentPhoto);731 if (currentPhoto >= data.size()) {732 currentPhoto = data.size() - 1;733 }734 735 if (Utils.deleteFile(toDelete.getFile())) {736 Logging.info("File "+toDelete.getFile()+" deleted. ");737 } else {738 JOptionPane.showMessageDialog(739 MainApplication.getMainFrame(),740 tr("Image file could not be deleted."),741 tr("Error"),742 JOptionPane.ERROR_MESSAGE743 );744 }745 746 showCurrentPhoto();747 }748 }749 }750 751 public void copyCurrentPhotoPath() {752 if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {753 ClipboardUtils.copyString(data.get(currentPhoto).getFile().toString());754 }755 }756 757 /**758 * Removes a photo from the list of images by index.759 * @param idx Image index760 * @since 6392761 */762 public void removePhotoByIdx(int idx) {763 if (idx >= 0 && data != null && idx < data.size()) {764 data.remove(idx);765 }766 }767 768 597 /** 769 598 * Check if the position of the mouse event is within the rectangle of the photo icon or thumbnail. 770 * @param idx Image index, range 0 .. size-1599 * @param idx the image index 771 600 * @param evt Mouse event 772 601 * @return {@code true} if the photo matches the mouse position, {@code false} otherwise 773 602 */ 774 603 private boolean isPhotoIdxUnderMouse(int idx, MouseEvent evt) { 775 if (idx >= 0 && data != null && idx < data.size()) { 776 ImageEntry img = data.get(idx); 777 if (img.getPos() != null) { 778 Point imgCenter = MainApplication.getMap().mapView.getPoint(img.getPos()); 779 Rectangle imgRect; 780 if (useThumbs && img.hasThumbnail()) { 781 Dimension imgDim = scaledDimension(img.getThumbnail()); 782 if (imgDim != null) { 783 imgRect = new Rectangle(imgCenter.x - imgDim.width / 2, 784 imgCenter.y - imgDim.height / 2, 785 imgDim.width, imgDim.height); 786 } else { 787 imgRect = null; 788 } 604 ImageEntry img = this.data.getImages().get(idx); 605 if (img.getPos() != null) { 606 Point imgCenter = MainApplication.getMap().mapView.getPoint(img.getPos()); 607 Rectangle imgRect; 608 if (useThumbs && img.hasThumbnail()) { 609 Dimension imgDim = scaledDimension(img.getThumbnail()); 610 if (imgDim != null) { 611 imgRect = new Rectangle(imgCenter.x - imgDim.width / 2, 612 imgCenter.y - imgDim.height / 2, 613 imgDim.width, imgDim.height); 789 614 } else { 790 imgRect = new Rectangle(imgCenter.x - icon.getIconWidth() / 2, 791 imgCenter.y - icon.getIconHeight() / 2, 792 icon.getIconWidth(), icon.getIconHeight()); 793 } 794 if (imgRect != null && imgRect.contains(evt.getPoint())) { 795 return true; 615 imgRect = null; 796 616 } 617 } else { 618 imgRect = new Rectangle(imgCenter.x - icon.getIconWidth() / 2, 619 imgCenter.y - icon.getIconHeight() / 2, 620 icon.getIconWidth(), icon.getIconHeight()); 621 } 622 if (imgRect != null && imgRect.contains(evt.getPoint())) { 623 return true; 797 624 } 798 625 } 799 626 return false; … … public class GeoImageLayer extends AbstractModifiableLayer implements 809 636 * or {@code -1} if there is no image at the mouse position 810 637 */ 811 638 private int getPhotoIdxUnderMouse(MouseEvent evt, boolean cycle) { 812 if (data != null) { 813 if (cycle && currentPhoto >= 0) { 814 // Cycle loop is forward as that is the natural order. 815 // Loop 1: One after current photo up to last one. 816 for (int idx = currentPhoto + 1; idx < data.size(); ++idx) { 817 if (isPhotoIdxUnderMouse(idx, evt)) { 818 return idx; 819 } 639 ImageEntry selectedImage = this.data.getSelectedImage(); 640 int selectedIndex = this.data.getImages().indexOf(selectedImage); 641 642 if (cycle && selectedImage != null) { 643 // Cycle loop is forward as that is the natural order. 644 // Loop 1: One after current photo up to last one. 645 for (int idx = selectedIndex + 1; idx < this.data.getImages().size(); ++idx) { 646 if (isPhotoIdxUnderMouse(idx, evt)) { 647 return idx; 820 648 } 821 // Loop 2: First photo up to current one. 822 for (int idx = 0; idx <= currentPhoto; ++idx) { 823 if (isPhotoIdxUnderMouse(idx, evt)) { 824 return idx; 825 } 826 } 827 } else { 828 // Check for current photo first, i.e. keep it selected if it is under the mouse. 829 if (currentPhoto >= 0 && isPhotoIdxUnderMouse(currentPhoto, evt)) { 830 return currentPhoto; 649 } 650 // Loop 2: First photo up to current one. 651 for (int idx = 0; idx <= selectedIndex; ++idx) { 652 if (isPhotoIdxUnderMouse(idx, evt)) { 653 return idx; 831 654 } 832 // Loop from last to first to prefer topmost image. 833 for (int idx = data.size() - 1; idx >= 0; --idx) { 834 if (isPhotoIdxUnderMouse(idx, evt)) { 835 return idx; 836 } 655 } 656 } else { 657 // Check for current photo first, i.e. keep it selected if it is under the mouse. 658 if (selectedImage != null && isPhotoIdxUnderMouse(selectedIndex, evt)) { 659 return selectedIndex; 660 } 661 // Loop from last to first to prefer topmost image. 662 for (int idx = this.data.getImages().size() - 1; idx >= 0; --idx) { 663 if (isPhotoIdxUnderMouse(idx, evt)) { 664 return idx; 837 665 } 838 666 } 839 667 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 861 689 public ImageEntry getPhotoUnderMouse(MouseEvent evt) { 862 690 int idx = getPhotoIdxUnderMouse(evt); 863 691 if (idx >= 0) { 864 return data.get(idx);692 return this.data.getImages().get(idx); 865 693 } else { 866 694 return null; 867 695 } 868 696 } 869 697 870 /**871 * Clears the currentPhoto, i.e. remove select marker, and optionally repaint.872 * @param repaint Repaint flag873 * @since 6392874 */875 public void clearCurrentPhoto(boolean repaint) {876 currentPhoto = -1;877 if (repaint) {878 updateBufferAndRepaint();879 }880 }881 882 698 /** 883 699 * Clears the currentPhoto of the other GeoImageLayer's. Otherwise there could be multiple selected photos. 884 700 */ … … public class GeoImageLayer extends AbstractModifiableLayer implements 886 702 for (GeoImageLayer layer: 887 703 MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) { 888 704 if (layer != this) { 889 layer. clearCurrentPhoto(false);705 layer.getImageData().clearSelectedImage(); 890 706 } 891 707 } 892 708 } … … public class GeoImageLayer extends AbstractModifiableLayer implements 947 763 public void mouseReleased(MouseEvent ev) { 948 764 if (ev.getButton() != MouseEvent.BUTTON1) 949 765 return; 950 if ( data == null ||!isVisible() || !isMapModeOk())766 if (!isVisible() || !isMapModeOk()) 951 767 return; 952 768 953 769 Point mousePos = ev.getPoint(); … … public class GeoImageLayer extends AbstractModifiableLayer implements 956 772 if (idx >= 0) { 957 773 lastSelPos = mousePos; 958 774 cycleModeArmed = false; 959 currentPhoto = idx; 960 showCurrentPhoto(); 775 data.setSelectedImage(data.getImages().get(idx)); 961 776 } 962 777 } 963 778 }; … … public class GeoImageLayer extends AbstractModifiableLayer implements 1013 828 MapView.removeZoomChangeListener(this); 1014 829 MapFrame.removeMapModeChangeListener(mapModeListener); 1015 830 MainApplication.getLayerManager().removeActiveLayerChangeListener(activeLayerChangeListener); 1016 currentPhoto = -1; 1017 if (data != null) { 1018 data.clear(); 1019 } 1020 data = null; 831 this.data.removeImageDataUpdateListener(this); 1021 832 } 1022 833 1023 834 @Override … … public class GeoImageLayer extends AbstractModifiableLayer implements 1083 894 * @return List of images in layer 1084 895 */ 1085 896 public List<ImageEntry> getImages() { 1086 return data == null ? Collections.<ImageEntry>emptyList() : new ArrayList<>(data); 897 return new ArrayList<>(this.data.getImages()); 898 } 899 900 /** 901 * Returns the image data store being used by this layer 902 * @return imageData 903 * @since xxx 904 */ 905 public ImageData getImageData() { 906 return data; 1087 907 } 1088 908 1089 909 /** … … public class GeoImageLayer extends AbstractModifiableLayer implements 1096 916 1097 917 @Override 1098 918 public void jumpToNextMarker() { 1099 showNextPhoto();919 this.data.selectNextImage(); 1100 920 } 1101 921 1102 922 @Override 1103 923 public void jumpToPreviousMarker() { 1104 showPreviousPhoto();924 this.data.selectPreviousImage(); 1105 925 } 1106 926 1107 927 /** … … public class GeoImageLayer extends AbstractModifiableLayer implements 1128 948 } 1129 949 invalidate(); 1130 950 } 951 952 @Override 953 public void selectedImageChanged(ImageData data) { 954 this.showCurrentPhoto(); 955 } 956 957 @Override 958 public void imageDataUpdated(ImageData data) { 959 updateBufferAndRepaint(); 960 } 1131 961 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java index eb367026f..cba56207b 100644
a b public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 702 702 * @param text text to display on top of the image 703 703 */ 704 704 public void setOsdText(String text) { 705 this.osdText = text; 706 repaint(); 705 if (!text.equals(this.osdText)) { 706 this.osdText = text; 707 repaint(); 708 } 707 709 } 708 710 709 711 @Override -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java index 53cf7ee8a..5fd27fa5a 100644
a b import java.awt.event.KeyEvent; 13 13 import java.awt.event.WindowEvent; 14 14 import java.text.DateFormat; 15 15 import java.text.SimpleDateFormat; 16 import java.util.Objects; 16 17 17 18 import javax.swing.Box; 18 19 import javax.swing.JButton; 20 import javax.swing.JLabel; 21 import javax.swing.JOptionPane; 19 22 import javax.swing.JPanel; 20 23 import javax.swing.JToggleButton; 24 import javax.swing.SwingConstants; 21 25 22 26 import org.openstreetmap.josm.actions.JosmAction; 27 import org.openstreetmap.josm.data.ImageData; 28 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener; 29 import org.openstreetmap.josm.gui.ExtendedDialog; 23 30 import org.openstreetmap.josm.gui.MainApplication; 31 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 24 32 import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action; 25 33 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 26 34 import org.openstreetmap.josm.gui.layer.Layer; … … import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 31 39 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 32 40 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 33 41 import org.openstreetmap.josm.tools.ImageProvider; 42 import org.openstreetmap.josm.tools.Logging; 34 43 import org.openstreetmap.josm.tools.Shortcut; 44 import org.openstreetmap.josm.tools.Utils; 35 45 import org.openstreetmap.josm.tools.date.DateUtils; 36 46 37 47 /** 38 48 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}. 39 49 */ 40 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener {50 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener { 41 51 42 52 private final ImageZoomAction imageZoomAction = new ImageZoomAction(); 43 53 private final ImageCenterViewAction imageCenterViewAction = new ImageCenterViewAction(); … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 79 89 private JButton btnPrevious; 80 90 private JButton btnFirst; 81 91 private JButton btnCollapse; 92 private JButton btnDelete; 93 private JButton btnCopyPath; 94 private JButton btnDeleteFromDisk; 82 95 private JToggleButton tbCentre; 83 96 84 97 private ImageViewerDialog() { … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 87 100 build(); 88 101 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 89 102 MainApplication.getLayerManager().addLayerChangeListener(this); 103 for (Layer l: MainApplication.getLayerManager().getLayers()) { 104 this.registerOnLayer(l); 105 } 90 106 } 91 107 92 108 private static JButton createNavigationButton(JosmAction action, Dimension buttonDim) { … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 106 122 btnFirst = createNavigationButton(imageFirstAction, buttonDim); 107 123 btnPrevious = createNavigationButton(imagePreviousAction, buttonDim); 108 124 109 JButtonbtnDelete = new JButton(imageRemoveAction);125 btnDelete = new JButton(imageRemoveAction); 110 126 btnDelete.setPreferredSize(buttonDim); 111 127 112 JButtonbtnDeleteFromDisk = new JButton(imageRemoveFromDiskAction);128 btnDeleteFromDisk = new JButton(imageRemoveFromDiskAction); 113 129 btnDeleteFromDisk.setPreferredSize(buttonDim); 114 130 115 JButtonbtnCopyPath = new JButton(imageCopyPathAction);131 btnCopyPath = new JButton(imageCopyPathAction); 116 132 btnCopyPath.setPreferredSize(buttonDim); 117 133 118 134 btnNext = createNavigationButton(imageNextAction, buttonDim); … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 189 205 190 206 @Override 191 207 public void actionPerformed(ActionEvent e) { 192 if (current Layer!= null) {193 current Layer.showNextPhoto();208 if (currentData != null) { 209 currentData.selectNextImage(); 194 210 } 195 211 } 196 212 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 204 220 205 221 @Override 206 222 public void actionPerformed(ActionEvent e) { 207 if (current Layer!= null) {208 current Layer.showPreviousPhoto();223 if (currentData != null) { 224 currentData.selectPreviousImage(); 209 225 } 210 226 } 211 227 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 219 235 220 236 @Override 221 237 public void actionPerformed(ActionEvent e) { 222 if (current Layer!= null) {223 current Layer.showFirstPhoto();238 if (currentData != null) { 239 currentData.selectFirstImage(); 224 240 } 225 241 } 226 242 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 234 250 235 251 @Override 236 252 public void actionPerformed(ActionEvent e) { 237 if (current Layer!= null) {238 current Layer.showLastPhoto();253 if (currentData != null) { 254 currentData.selectLastImage(); 239 255 } 240 256 } 241 257 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 277 293 278 294 @Override 279 295 public void actionPerformed(ActionEvent e) { 280 if (current Layer!= null) {281 current Layer.removeCurrentPhoto();296 if (currentData != null) { 297 currentData.removeSelectedImage(); 282 298 } 283 299 } 284 300 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 293 309 294 310 @Override 295 311 public void actionPerformed(ActionEvent e) { 296 if (currentLayer != null) { 297 currentLayer.removeCurrentPhotoFromDisk(); 312 if (currentData != null && currentData.getSelectedImage() != null) { 313 ImageEntry toDelete = currentData.getSelectedImage(); 314 315 int result = new ExtendedDialog( 316 MainApplication.getMainFrame(), 317 tr("Delete image file from disk"), 318 tr("Cancel"), tr("Delete")) 319 .setButtonIcons("cancel", "dialogs/delete") 320 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?" 321 + "<p>The image file will be permanently lost!</h3></html>", 322 toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"), SwingConstants.LEFT)) 323 .toggleEnable("geoimage.deleteimagefromdisk") 324 .setCancelButton(1) 325 .setDefaultButton(2) 326 .showDialog() 327 .getValue(); 328 329 if (result == 2) { 330 currentData.removeSelectedImage(); 331 332 if (Utils.deleteFile(toDelete.getFile())) { 333 Logging.info("File "+toDelete.getFile()+" deleted. "); 334 } else { 335 JOptionPane.showMessageDialog( 336 MainApplication.getMainFrame(), 337 tr("Image file could not be deleted."), 338 tr("Error"), 339 JOptionPane.ERROR_MESSAGE 340 ); 341 } 342 } 298 343 } 299 344 } 300 345 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 308 353 309 354 @Override 310 355 public void actionPerformed(ActionEvent e) { 311 if (current Layer!= null) {312 currentLayer.copyCurrentPhotoPath();356 if (currentData != null) { 357 ClipboardUtils.copyString(currentData.getSelectedImage().getFile().toString()); 313 358 } 314 359 } 315 360 } … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 329 374 330 375 /** 331 376 * Displays image for the given layer. 332 * @param layergeo image layer377 * @param data geo image layer 333 378 * @param entry image entry 334 379 */ 335 public static void showImage(GeoImageLayer layer, ImageEntry entry) { 336 getInstance().displayImage(layer, entry); 337 if (layer != null) { 338 layer.checkPreviousNextButtons(); 339 } else { 340 setPreviousEnabled(false); 341 setNextEnabled(false); 342 } 380 public static void showImage(ImageData data, ImageEntry entry) { 381 getInstance().displayImage(data, entry); 343 382 } 344 383 345 384 /** 346 385 * Enables (or disables) the "Previous" button. 347 386 * @param value {@code true} to enable the button, {@code false} otherwise 348 387 */ 349 public staticvoid setPreviousEnabled(boolean value) {350 getInstance().btnFirst.setEnabled(value);351 getInstance().btnPrevious.setEnabled(value);388 public void setPreviousEnabled(boolean value) { 389 this.btnFirst.setEnabled(value); 390 this.btnPrevious.setEnabled(value); 352 391 } 353 392 354 393 /** 355 394 * Enables (or disables) the "Next" button. 356 395 * @param value {@code true} to enable the button, {@code false} otherwise 357 396 */ 358 public staticvoid setNextEnabled(boolean value) {359 getInstance().btnNext.setEnabled(value);360 getInstance().btnLast.setEnabled(value);397 public void setNextEnabled(boolean value) { 398 this.btnNext.setEnabled(value); 399 this.btnLast.setEnabled(value); 361 400 } 362 401 363 402 /** … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 373 412 return wasEnabled; 374 413 } 375 414 376 private transient GeoImageLayer currentLayer;415 private transient ImageData currentData; 377 416 private transient ImageEntry currentEntry; 378 417 379 418 /** 380 419 * Displays image for the given layer. 381 * @param layer geo image layer420 * @param data the image data 382 421 * @param entry image entry 383 422 */ 384 public void displayImage( GeoImageLayer layer, ImageEntry entry) {423 public void displayImage(ImageData data, ImageEntry entry) { 385 424 boolean imageChanged; 386 425 387 426 synchronized (this) { … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 393 432 MainApplication.getMap().mapView.zoomTo(entry.getPos()); 394 433 } 395 434 396 current Layer = layer;435 currentData = data; 397 436 currentEntry = entry; 398 437 } 399 438 439 400 440 if (entry != null) { 441 Objects.requireNonNull(data, "data cannot be null!"); 442 this.setNextEnabled(data.hasNextImage()); 443 this.setPreviousEnabled(data.hasPreviousImage()); 444 btnDelete.setEnabled(true); 445 btnDeleteFromDisk.setEnabled(true); 446 btnCopyPath.setEnabled(true); 447 401 448 if (imageChanged) { 402 449 // Set only if the image is new to preserve zoom and position if the same image is redisplayed 403 450 // (e.g. to update the OSD). … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 436 483 setTitle(tr("Geotagged Images")); 437 484 imgDisplay.setImage(null); 438 485 imgDisplay.setOsdText(""); 486 this.setNextEnabled(false); 487 this.setPreviousEnabled(false); 488 btnDelete.setEnabled(false); 489 btnDeleteFromDisk.setEnabled(false); 490 btnCopyPath.setEnabled(false); 439 491 return; 440 492 } 441 493 if (!isDialogShowing()) { … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 487 539 return getInstance().currentEntry; 488 540 } 489 541 490 /**491 * Returns the layer associated with the image.492 * @return Layer associated with the image493 * @since 6392494 */495 public static GeoImageLayer getCurrentLayer() {496 return getInstance().currentLayer;497 }498 499 542 /** 500 543 * Returns whether the center view is currently active. 501 544 * @return {@code true} if the center view is active, {@code false} otherwise … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 507 550 508 551 @Override 509 552 public void layerAdded(LayerAddEvent e) { 553 this.registerOnLayer(e.getAddedLayer()); 510 554 showLayer(e.getAddedLayer()); 511 555 } 512 556 513 557 @Override 514 558 public void layerRemoving(LayerRemoveEvent e) { 515 // Clear current image and layer if current layer is deleted 516 if (currentLayer != null && currentLayer.equals(e.getRemovedLayer())) { 517 showImage(null, null); 518 } 519 // Check buttons state in case of layer merging 520 if (currentLayer != null && e.getRemovedLayer() instanceof GeoImageLayer) { 521 currentLayer.checkPreviousNextButtons(); 559 if (e.getRemovedLayer() instanceof GeoImageLayer) { 560 if (((GeoImageLayer) e.getRemovedLayer()).getImageData() == currentData) { 561 displayImage(null, null); 562 } 563 ((GeoImageLayer) e.getRemovedLayer()).getImageData().removeImageDataUpdateListener(this); 522 564 } 523 565 } 524 566 … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 532 574 showLayer(e.getSource().getActiveLayer()); 533 575 } 534 576 577 private void registerOnLayer(Layer layer) { 578 if (layer instanceof GeoImageLayer) { 579 ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this); 580 } 581 } 582 535 583 private void showLayer(Layer newLayer) { 536 if (current Layer== null && newLayer instanceof GeoImageLayer) {537 ((GeoImageLayer) newLayer). showFirstPhoto();584 if (currentData == null && newLayer instanceof GeoImageLayer) { 585 ((GeoImageLayer) newLayer).getImageData().selectFirstImage(); 538 586 } 539 587 } 588 589 @Override 590 public void selectedImageChanged(ImageData data) { 591 showImage(data, data.getSelectedImage()); 592 } 593 594 @Override 595 public void imageDataUpdated(ImageData data) { 596 showImage(data, data.getSelectedImage()); 597 } 540 598 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ShowThumbnailAction.java index 7a7b62791..5f75cad5f 100644
a b public class ShowThumbnailAction extends AbstractAction implements LayerAction { 49 49 * {@code false} otherwise 50 50 */ 51 51 private static boolean enabled(GeoImageLayer layer) { 52 return layer.data != null && !layer.data.isEmpty();52 return !layer.getImageData().getImages().isEmpty(); 53 53 } 54 54 55 55 /** Create actual menu entry and define if it is enabled or not. */ -
src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java index a49bbc8a3..0ead613c5 100644
a b public class ThumbsLoader implements Runnable { 51 51 * @param layer geoimage layer 52 52 */ 53 53 public ThumbsLoader(GeoImageLayer layer) { 54 this(new ArrayList<>(layer. data), layer);54 this(new ArrayList<>(layer.getImageData().getImages()), layer); 55 55 } 56 56 57 57 /** -
new file test/unit/org/openstreetmap/josm/data/ImageDataTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/ImageDataTest.java b/test/unit/org/openstreetmap/josm/data/ImageDataTest.java new file mode 100644 index 000000000..f778aca8c
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data; 3 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertFalse; 6 import static org.junit.Assert.assertNull; 7 import static org.junit.Assert.assertTrue; 8 9 import java.io.File; 10 import java.util.ArrayList; 11 import java.util.Collections; 12 import java.util.List; 13 14 import org.junit.Test; 15 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener; 16 import org.openstreetmap.josm.data.coor.LatLon; 17 import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry; 18 19 import mockit.Expectations; 20 import mockit.Mock; 21 import mockit.MockUp; 22 23 /** 24 * Unit tests for class {@link ImageData}. 25 */ 26 public class ImageDataTest { 27 28 private List<ImageEntry> getOneImage() { 29 ArrayList<ImageEntry> list = new ArrayList<>(); 30 list.add(new ImageEntry(new File("test"))); 31 return list; 32 } 33 34 @Test 35 public void testWithullData() { 36 ImageData data = new ImageData(); 37 assertEquals(0, data.getImages().size()); 38 assertNull(data.getSelectedImage()); 39 data.selectFirstImage(); 40 assertNull(data.getSelectedImage()); 41 data.selectLastImage(); 42 assertNull(data.getSelectedImage()); 43 data.selectFirstImage(); 44 assertNull(data.getSelectedImage()); 45 data.selectPreviousImage(); 46 assertNull(data.getSelectedImage()); 47 assertFalse(data.hasNextImage()); 48 assertFalse(data.hasPreviousImage()); 49 data.removeSelectedImage(); 50 } 51 52 @Test 53 public void testmageEntryWithImages() { 54 assertEquals(1, new ImageData(this.getOneImage()).getImages().size()); 55 } 56 57 @Test 58 public void testSortData() { 59 List<ImageEntry> list = this.getOneImage(); 60 61 new Expectations(Collections.class) {{ 62 Collections.sort(list); 63 }}; 64 65 new ImageData(list); 66 } 67 68 @Test 69 public void testIsModifiedFalse() { 70 assertFalse(new ImageData(this.getOneImage()).isModified()); 71 } 72 73 @Test 74 public void testIsModifiedTrue() { 75 List<ImageEntry> list = this.getOneImage(); 76 77 new Expectations(list.get(0)) {{ 78 list.get(0).hasNewGpsData(); result = true; 79 }}; 80 81 assertTrue(new ImageData(list).isModified()); 82 } 83 84 @Test 85 public void testSelectFirstImage() { 86 List<ImageEntry> list = this.getOneImage(); 87 88 ImageData data = new ImageData(list); 89 data.selectFirstImage(); 90 assertEquals(list.get(0), data.getSelectedImage()); 91 } 92 93 @Test 94 public void testSelectLastImage() { 95 List<ImageEntry> list = this.getOneImage(); 96 list.add(new ImageEntry()); 97 98 ImageData data = new ImageData(list); 99 data.selectLastImage(); 100 assertEquals(list.get(1), data.getSelectedImage()); 101 } 102 103 @Test 104 public void testSelectNextImage() { 105 List<ImageEntry> list = this.getOneImage(); 106 107 ImageData data = new ImageData(list); 108 assertTrue(data.hasNextImage()); 109 data.selectNextImage(); 110 assertEquals(list.get(0), data.getSelectedImage()); 111 assertFalse(data.hasNextImage()); 112 data.selectNextImage(); 113 assertEquals(list.get(0), data.getSelectedImage()); 114 } 115 116 @Test 117 public void testSelectPreviousImage() { 118 List<ImageEntry> list = this.getOneImage(); 119 list.add(new ImageEntry()); 120 121 ImageData data = new ImageData(list); 122 assertFalse(data.hasPreviousImage()); 123 data.selectLastImage(); 124 assertTrue(data.hasPreviousImage()); 125 data.selectPreviousImage(); 126 assertEquals(list.get(0), data.getSelectedImage()); 127 data.selectPreviousImage(); 128 assertEquals(list.get(0), data.getSelectedImage()); 129 } 130 131 @Test 132 public void testSetSelectedImage() { 133 List<ImageEntry> list = this.getOneImage(); 134 135 ImageData data = new ImageData(list); 136 data.setSelectedImage(list.get(0)); 137 assertEquals(list.get(0), data.getSelectedImage()); 138 } 139 140 @Test 141 public void testClearSelectedImage() { 142 List<ImageEntry> list = this.getOneImage(); 143 144 ImageData data = new ImageData(list); 145 data.setSelectedImage(list.get(0)); 146 data.clearSelectedImage(); 147 assertNull(data.getSelectedImage()); 148 } 149 150 @Test 151 public void testSelectionListener() { 152 List<ImageEntry> list = this.getOneImage(); 153 ImageData data = new ImageData(list); 154 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 155 @Override 156 public void selectedImageChanged(ImageData data) {} 157 @Override 158 public void imageDataUpdated(ImageData data) {} 159 }; 160 new Expectations(listener) {{ 161 listener.selectedImageChanged(data); times = 1; 162 }}; 163 data.addImageDataUpdateListener(listener); 164 data.selectFirstImage(); 165 data.selectFirstImage(); 166 } 167 168 @Test 169 public void testRemoveSelectedImage() { 170 List<ImageEntry> list = this.getOneImage(); 171 ImageData data = new ImageData(list); 172 data.selectFirstImage(); 173 data.removeSelectedImage(); 174 assertEquals(0, data.getImages().size()); 175 assertNull(data.getSelectedImage()); 176 } 177 178 @Test 179 public void testRemoveSelectedWithImageTriggerListener() { 180 List<ImageEntry> list = this.getOneImage(); 181 list.add(new ImageEntry()); 182 ImageData data = new ImageData(list); 183 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 184 @Override 185 public void selectedImageChanged(ImageData data) {} 186 @Override 187 public void imageDataUpdated(ImageData data) {} 188 }; 189 new Expectations(listener) {{ 190 listener.selectedImageChanged(data); times = 2; 191 }}; 192 data.addImageDataUpdateListener(listener); 193 data.selectFirstImage(); 194 data.removeSelectedImage(); 195 } 196 197 @Test 198 public void testRemoveImageAndTriggerListener() { 199 List<ImageEntry> list = this.getOneImage(); 200 ImageData data = new ImageData(list); 201 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 202 @Override 203 public void selectedImageChanged(ImageData data) {} 204 @Override 205 public void imageDataUpdated(ImageData data) {} 206 }; 207 new Expectations(listener) {{ 208 listener.imageDataUpdated(data); times = 1; 209 }}; 210 data.addImageDataUpdateListener(listener); 211 data.removeImage(list.get(0)); 212 assertEquals(0, data.getImages().size()); 213 } 214 215 @Test 216 public void testMergeFrom() { 217 ImageEntry image = new ImageEntry(new File("test2")); 218 List<ImageEntry> list1 = this.getOneImage(); 219 list1.add(image); 220 List<ImageEntry> list2 = this.getOneImage(); 221 list2.add(new ImageEntry(new File("test3"))); 222 223 ImageData data = new ImageData(list1); 224 data.setSelectedImage(list1.get(0)); 225 ImageData data2 = new ImageData(list2); 226 227 new MockUp<Collections>() { 228 @Mock 229 public void sort(List<ImageEntry> o) { 230 list1.remove(image); 231 list1.add(image); 232 } 233 }; 234 235 data.mergeFrom(data2); 236 assertEquals(3, data.getImages().size()); 237 assertEquals(list1.get(0), data.getSelectedImage()); 238 } 239 240 @Test 241 public void testMergeFromSelectedImage() { 242 ImageEntry image = new ImageEntry(new File("test2")); 243 List<ImageEntry> list1 = this.getOneImage(); 244 list1.add(image); 245 List<ImageEntry> list2 = this.getOneImage(); 246 247 ImageData data = new ImageData(list1); 248 ImageData data2 = new ImageData(list2); 249 data2.setSelectedImage(list2.get(0)); 250 251 data.mergeFrom(data2); 252 assertEquals(3, data.getImages().size()); 253 assertEquals(list2.get(0), data.getSelectedImage()); 254 } 255 256 257 @Test 258 public void testUpdatePosition() { 259 List<ImageEntry> list = this.getOneImage(); 260 ImageData data = new ImageData(list); 261 262 new Expectations(list.get(0)) {{ 263 list.get(0).setPos((LatLon) any); 264 list.get(0).flagNewGpsData(); 265 }}; 266 data.updateImagePosition(list.get(0), new LatLon(0, 0)); 267 } 268 269 @Test 270 public void testUpdateDirection() { 271 List<ImageEntry> list = this.getOneImage(); 272 ImageData data = new ImageData(list); 273 274 new Expectations(list.get(0)) {{ 275 list.get(0).setExifImgDir(0.0); 276 list.get(0).flagNewGpsData(); 277 }}; 278 data.updateImageDirection(list.get(0), 0); 279 } 280 281 @Test 282 public void testTriggerListenerOnUpdate() { 283 List<ImageEntry> list = this.getOneImage(); 284 ImageData data = new ImageData(list); 285 286 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 287 @Override 288 public void selectedImageChanged(ImageData data) {} 289 @Override 290 public void imageDataUpdated(ImageData data) {} 291 }; 292 new Expectations(listener) {{ 293 listener.imageDataUpdated(data); times = 1; 294 }}; 295 296 data.addImageDataUpdateListener(listener); 297 data.updateImageDirection(list.get(0), 0); 298 } 299 300 @Test 301 public void testManuallyTriggerUpdateListener() { 302 List<ImageEntry> list = this.getOneImage(); 303 ImageData data = new ImageData(list); 304 305 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 306 @Override 307 public void selectedImageChanged(ImageData data) {} 308 @Override 309 public void imageDataUpdated(ImageData data) {} 310 }; 311 new Expectations(listener) {{ 312 listener.imageDataUpdated(data); times = 1; 313 }}; 314 315 data.addImageDataUpdateListener(listener); 316 data.notifyImageUpdate(); 317 } 318 }
