Ticket #17050: refactor_geoimagelayer.4.patch
| File refactor_geoimagelayer.4.patch, 61.9 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..6dab9ae5f
- + 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 /** 28 * Called when the selection change 29 * @param data the image data 30 */ 31 void selectedImageChanged(ImageData data); 32 } 33 34 private final List<ImageEntry> data; 35 36 private int selectedImageIndex = -1; 37 38 private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create(); 39 40 /** 41 * Construct a new image container without images 42 */ 43 public ImageData() { 44 this(null); 45 } 46 47 /** 48 * Construct a new image container with a list of images 49 * @param data the list of {@link ImageEntry} 50 */ 51 public ImageData(List<ImageEntry> data) { 52 if (data != null) { 53 Collections.sort(data); 54 this.data = data; 55 } else { 56 this.data = new ArrayList<>(); 57 } 58 } 59 60 /** 61 * Returns the images 62 * @return the images 63 */ 64 public List<ImageEntry> getImages() { 65 return this.data; 66 } 67 68 /** 69 * Determines if one image has modified GPS data. 70 * @return {@code true} if data has been modified; {@code false}, otherwise 71 */ 72 public boolean isModified() { 73 for (ImageEntry e : data) { 74 if (e.hasNewGpsData()) { 75 return true; 76 } 77 } 78 return false; 79 } 80 81 /** 82 * Merge 2 ImageData 83 * @param data {@link ImageData} 84 */ 85 public void mergeFrom(ImageData data) { 86 this.data.addAll(data.getImages()); 87 Collections.sort(this.data); 88 89 final ImageEntry selected = data.getSelectedImage(); 90 91 // Suppress the double photos. 92 if (this.data.size() > 1) { 93 ImageEntry cur; 94 ImageEntry prev = this.data.get(this.data.size() - 1); 95 for (int i = this.data.size() - 2; i >= 0; i--) { 96 cur = this.data.get(i); 97 if (cur.getFile().equals(prev.getFile())) { 98 this.data.remove(i); 99 } else { 100 prev = cur; 101 } 102 } 103 } 104 if (selected != null) { 105 this.setSelectedImageIndex(this.data.indexOf(selected)); 106 } 107 } 108 109 /** 110 * Return the current selected image 111 * @return the selected image as {@link ImageEntry} or null 112 */ 113 public ImageEntry getSelectedImage() { 114 if (this.selectedImageIndex > -1) { 115 return data.get(this.selectedImageIndex); 116 } 117 return null; 118 } 119 120 /** 121 * Select the first image of the sequence 122 */ 123 public void selectFirstImage() { 124 if (!data.isEmpty()) { 125 this.setSelectedImageIndex(0); 126 } 127 } 128 129 /** 130 * Select the last image of the sequence 131 */ 132 public void selectLastImage() { 133 this.setSelectedImageIndex(data.size() - 1); 134 } 135 136 /** 137 * Check if there is a next image in the sequence 138 * @return {@code true} is there is a next image, {@code false} otherwise 139 */ 140 public boolean hasNextImage() { 141 return (this.selectedImageIndex != data.size() - 1); 142 } 143 144 /** 145 * Select the next image of the sequence 146 */ 147 public void selectNextImage() { 148 if (this.hasNextImage()) { 149 this.setSelectedImageIndex(this.selectedImageIndex + 1); 150 } 151 } 152 153 /** 154 * Check if there is a previous image in the sequence 155 * @return {@code true} is there is a previous image, {@code false} otherwise 156 */ 157 public boolean hasPreviousImage() { 158 return this.selectedImageIndex - 1 > -1; 159 } 160 161 /** 162 * Select the previous image of the sequence 163 */ 164 public void selectPreviousImage() { 165 if (data.isEmpty()) { 166 return; 167 } 168 this.setSelectedImageIndex(Integer.max(0, this.selectedImageIndex - 1)); 169 } 170 171 /** 172 * Select as the selected the given image 173 * @param image the selected image 174 */ 175 public void setSelectedImage(ImageEntry image) { 176 this.setSelectedImageIndex(this.data.indexOf(image)); 177 } 178 179 /** 180 * Clear the selected image 181 */ 182 public void clearSelectedImage() { 183 this.setSelectedImageIndex(-1); 184 } 185 186 private void setSelectedImageIndex(int index) { 187 this.setSelectedImageIndex(index, false); 188 } 189 190 private void setSelectedImageIndex(int index, boolean forceTrigger) { 191 if (index == this.selectedImageIndex && !forceTrigger) { 192 return; 193 } 194 this.selectedImageIndex = index; 195 listeners.fireEvent(l -> l.selectedImageChanged(this)); 196 } 197 198 /** 199 * Remove the current selected image from the list 200 */ 201 public void removeSelectedImage() { 202 data.remove(this.getSelectedImage()); 203 if (this.selectedImageIndex == data.size()) { 204 this.setSelectedImageIndex(data.size() - 1); 205 } else { 206 this.setSelectedImageIndex(this.selectedImageIndex, true); 207 } 208 } 209 210 /** 211 * Remove the image from the list and trigger update listener 212 * @param img the {@link ImageEntry} to remove 213 */ 214 public void removeImage(ImageEntry img) { 215 data.remove(img); 216 this.notifyImageUpdate(); 217 } 218 219 /** 220 * Update the position of the image and trigger update 221 * @param img the image to update 222 * @param newPos the new position 223 */ 224 public void updateImagePosition(ImageEntry img, LatLon newPos) { 225 img.setPos(newPos); 226 this.afterImageUpdated(img); 227 } 228 229 /** 230 * Update the image direction of the image and trigger update 231 * @param img the image to update 232 * @param direction the new direction 233 */ 234 public void updateImageDirection(ImageEntry img, double direction) { 235 img.setExifImgDir(direction); 236 this.afterImageUpdated(img); 237 } 238 239 /** 240 * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)} 241 */ 242 public void notifyImageUpdate() { 243 listeners.fireEvent(l -> l.imageDataUpdated(this)); 244 } 245 246 private void afterImageUpdated(ImageEntry img) { 247 img.flagNewGpsData(); 248 this.notifyImageUpdate(); 249 } 250 251 /** 252 * Add a listener that listens to image data changes 253 * @param listener the {@link ImageDataUpdateListener} 254 */ 255 public void addImageDataUpdateListener(ImageDataUpdateListener listener) { 256 listeners.addListener(listener); 257 } 258 259 /** 260 * Removes a listener that listens to image data changes 261 * @param listener The listener 262 */ 263 public void removeImageDataUpdateListener(ImageDataUpdateListener listener) { 264 listeners.removeListener(listener); 265 } 266 } -
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..a4c032612 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 the list of image files to display 310 * @param gpxLayer the gpx layer 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 the action to be added 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..a79374ff2
- + 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 158 @Override 159 public void imageDataUpdated(ImageData data) {} 160 }; 161 new Expectations(listener) {{ 162 listener.selectedImageChanged(data); times = 1; 163 }}; 164 data.addImageDataUpdateListener(listener); 165 data.selectFirstImage(); 166 data.selectFirstImage(); 167 } 168 169 @Test 170 public void testRemoveSelectedImage() { 171 List<ImageEntry> list = this.getOneImage(); 172 ImageData data = new ImageData(list); 173 data.selectFirstImage(); 174 data.removeSelectedImage(); 175 assertEquals(0, data.getImages().size()); 176 assertNull(data.getSelectedImage()); 177 } 178 179 @Test 180 public void testRemoveSelectedWithImageTriggerListener() { 181 List<ImageEntry> list = this.getOneImage(); 182 list.add(new ImageEntry()); 183 ImageData data = new ImageData(list); 184 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 185 @Override 186 public void selectedImageChanged(ImageData data) {} 187 188 @Override 189 public void imageDataUpdated(ImageData data) {} 190 }; 191 new Expectations(listener) {{ 192 listener.selectedImageChanged(data); times = 2; 193 }}; 194 data.addImageDataUpdateListener(listener); 195 data.selectFirstImage(); 196 data.removeSelectedImage(); 197 } 198 199 @Test 200 public void testRemoveImageAndTriggerListener() { 201 List<ImageEntry> list = this.getOneImage(); 202 ImageData data = new ImageData(list); 203 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 204 @Override 205 public void selectedImageChanged(ImageData data) {} 206 207 @Override 208 public void imageDataUpdated(ImageData data) {} 209 }; 210 new Expectations(listener) {{ 211 listener.imageDataUpdated(data); times = 1; 212 }}; 213 data.addImageDataUpdateListener(listener); 214 data.removeImage(list.get(0)); 215 assertEquals(0, data.getImages().size()); 216 } 217 218 @Test 219 public void testMergeFrom() { 220 ImageEntry image = new ImageEntry(new File("test2")); 221 List<ImageEntry> list1 = this.getOneImage(); 222 list1.add(image); 223 List<ImageEntry> list2 = this.getOneImage(); 224 list2.add(new ImageEntry(new File("test3"))); 225 226 ImageData data = new ImageData(list1); 227 data.setSelectedImage(list1.get(0)); 228 ImageData data2 = new ImageData(list2); 229 230 new MockUp<Collections>() { 231 @Mock 232 public void sort(List<ImageEntry> o) { 233 list1.remove(image); 234 list1.add(image); 235 } 236 }; 237 238 data.mergeFrom(data2); 239 assertEquals(3, data.getImages().size()); 240 assertEquals(list1.get(0), data.getSelectedImage()); 241 } 242 243 @Test 244 public void testMergeFromSelectedImage() { 245 ImageEntry image = new ImageEntry(new File("test2")); 246 List<ImageEntry> list1 = this.getOneImage(); 247 list1.add(image); 248 List<ImageEntry> list2 = this.getOneImage(); 249 250 ImageData data = new ImageData(list1); 251 ImageData data2 = new ImageData(list2); 252 data2.setSelectedImage(list2.get(0)); 253 254 data.mergeFrom(data2); 255 assertEquals(3, data.getImages().size()); 256 assertEquals(list2.get(0), data.getSelectedImage()); 257 } 258 259 260 @Test 261 public void testUpdatePosition() { 262 List<ImageEntry> list = this.getOneImage(); 263 ImageData data = new ImageData(list); 264 265 new Expectations(list.get(0)) {{ 266 list.get(0).setPos((LatLon) any); 267 list.get(0).flagNewGpsData(); 268 }}; 269 data.updateImagePosition(list.get(0), new LatLon(0, 0)); 270 } 271 272 @Test 273 public void testUpdateDirection() { 274 List<ImageEntry> list = this.getOneImage(); 275 ImageData data = new ImageData(list); 276 277 new Expectations(list.get(0)) {{ 278 list.get(0).setExifImgDir(0.0); 279 list.get(0).flagNewGpsData(); 280 }}; 281 data.updateImageDirection(list.get(0), 0); 282 } 283 284 @Test 285 public void testTriggerListenerOnUpdate() { 286 List<ImageEntry> list = this.getOneImage(); 287 ImageData data = new ImageData(list); 288 289 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 290 @Override 291 public void selectedImageChanged(ImageData data) {} 292 293 @Override 294 public void imageDataUpdated(ImageData data) {} 295 }; 296 new Expectations(listener) {{ 297 listener.imageDataUpdated(data); times = 1; 298 }}; 299 300 data.addImageDataUpdateListener(listener); 301 data.updateImageDirection(list.get(0), 0); 302 } 303 304 @Test 305 public void testManuallyTriggerUpdateListener() { 306 List<ImageEntry> list = this.getOneImage(); 307 ImageData data = new ImageData(list); 308 309 ImageDataUpdateListener listener = new ImageDataUpdateListener() { 310 @Override 311 public void selectedImageChanged(ImageData data) {} 312 313 @Override 314 public void imageDataUpdated(ImageData data) {} 315 }; 316 new Expectations(listener) {{ 317 listener.imageDataUpdated(data); times = 1; 318 }}; 319 320 data.addImageDataUpdateListener(listener); 321 data.notifyImageUpdate(); 322 } 323 }
