Changeset 15333 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2019-09-02T22:35:07+02:00 (5 years ago)
Author:
Don-vip
Message:

see #11905 - Option to move multiple images at once (core part, patch by francois2, modified)

Location:
trunk/src/org/openstreetmap/josm
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r15046 r15333  
    540540            if (dp < initialMoveThreshold)
    541541                return; // ignore small drags
    542             initialMoveThresholdExceeded = true; //no more ingnoring uintil nex mouse press
     542            initialMoveThresholdExceeded = true; //no more ignoring until next mouse press
    543543        }
    544544        if (e.getPoint().equals(lastMousePos))
  • trunk/src/org/openstreetmap/josm/data/ImageData.java

    r14590 r15333  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
    35
    46import java.util.ArrayList;
    57import java.util.Collections;
    68import java.util.List;
     9import java.util.stream.Collectors;
    710
    811import org.openstreetmap.josm.data.coor.LatLon;
     
    3437    private final List<ImageEntry> data;
    3538
    36     private int selectedImageIndex = -1;
     39    private final List<Integer> selectedImagesIndex = new ArrayList<>();
    3740
    3841    private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
     
    5659            this.data = new ArrayList<>();
    5760        }
     61        selectedImagesIndex.add(-1);
    5862    }
    5963
     
    107111
    108112    /**
    109      * Return the current selected image
    110      * @return the selected image as {@link ImageEntry} or null
     113     * Return the first currently selected image
     114     * @return the first selected image as {@link ImageEntry} or null
     115     * @see #getSelectedImages
    111116     */
    112117    public ImageEntry getSelectedImage() {
     118        int selectedImageIndex = selectedImagesIndex.isEmpty() ? -1 : selectedImagesIndex.get(0);
    113119        if (selectedImageIndex > -1) {
    114120            return data.get(selectedImageIndex);
    115121        }
    116122        return null;
     123    }
     124
     125    /**
     126     * Return the current selected images
     127     * @return the selected images as list {@link ImageEntry}
     128     * @since 15333
     129     */
     130    public List<ImageEntry> getSelectedImages() {
     131        return selectedImagesIndex.stream().filter(i -> i > -1).map(data::get).collect(Collectors.toList());
    117132    }
    118133
     
    138153     */
    139154    public boolean hasNextImage() {
    140         return selectedImageIndex != data.size() - 1;
     155        return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) != data.size() - 1);
    141156    }
    142157
     
    146161    public void selectNextImage() {
    147162        if (hasNextImage()) {
    148             setSelectedImageIndex(selectedImageIndex + 1);
     163            setSelectedImageIndex(selectedImagesIndex.get(0) + 1);
    149164        }
    150165    }
     
    155170     */
    156171    public boolean hasPreviousImage() {
    157         return selectedImageIndex - 1 > -1;
     172        return (selectedImagesIndex.size() == 1 && selectedImagesIndex.get(0) - 1 > -1);
    158173    }
    159174
     
    165180            return;
    166181        }
    167         setSelectedImageIndex(Integer.max(0, selectedImageIndex - 1));
     182        setSelectedImageIndex(Integer.max(0, selectedImagesIndex.get(0) - 1));
    168183    }
    169184
     
    177192
    178193    /**
    179      * Clear the selected image
     194     * Add image to the list of selected images
     195     * @param image {@link ImageEntry} the image to add
     196     * @since 15333
     197     */
     198    public void addImageToSelection(ImageEntry image) {
     199        int index = data.indexOf(image);
     200        if (selectedImagesIndex.get(0) == -1) {
     201            setSelectedImage(image);
     202        } else if (!selectedImagesIndex.contains(index)) {
     203            selectedImagesIndex.add(index);
     204            listeners.fireEvent(l -> l.selectedImageChanged(this));
     205        }
     206    }
     207
     208    /**
     209     * Remove the image from the list of selected images
     210     * @param image {@link ImageEntry} the image to remove
     211     * @since 15333
     212     */
     213    public void removeImageToSelection(ImageEntry image) {
     214        int index = data.indexOf(image);
     215        selectedImagesIndex.remove(selectedImagesIndex.indexOf(index));
     216        if (selectedImagesIndex.isEmpty()) {
     217            selectedImagesIndex.add(-1);
     218        }
     219        listeners.fireEvent(l -> l.selectedImageChanged(this));
     220    }
     221
     222    /**
     223     * Clear the selected image(s)
    180224     */
    181225    public void clearSelectedImage() {
     
    188232
    189233    private void setSelectedImageIndex(int index, boolean forceTrigger) {
    190         if (index == selectedImageIndex && !forceTrigger) {
     234        if (selectedImagesIndex.size() > 1) {
     235            selectedImagesIndex.clear();
     236            selectedImagesIndex.add(-1);
     237        }
     238        if (index == selectedImagesIndex.get(0) && !forceTrigger) {
    191239            return;
    192240        }
    193         selectedImageIndex = index;
     241        selectedImagesIndex.set(0, index);
    194242        listeners.fireEvent(l -> l.selectedImageChanged(this));
    195243    }
     
    199247     */
    200248    public void removeSelectedImage() {
    201         data.remove(getSelectedImage());
    202         if (selectedImageIndex == data.size()) {
     249        List<ImageEntry> selected = getSelectedImages();
     250        if (selected.size() > 1) {
     251            throw new IllegalStateException(tr("Multiple images have been selected"));
     252        }
     253        if (selected.isEmpty()) {
     254            return;
     255        }
     256        data.remove(getSelectedImages().get(0));
     257        if (selectedImagesIndex.get(0) == data.size()) {
    203258            setSelectedImageIndex(data.size() - 1);
    204259        } else {
    205             setSelectedImageIndex(selectedImageIndex, true);
    206         }
     260            setSelectedImageIndex(selectedImagesIndex.get(0), true);
     261        }
     262    }
     263
     264    /**
     265     * Determines if the image is selected
     266     * @param image the {@link ImageEntry} image
     267     * @return {@code true} is the image is selected, {@code false} otherwise
     268     * @since 15333
     269     */
     270    public boolean isImageSelected(ImageEntry image) {
     271        int index = data.indexOf(image);
     272        return selectedImagesIndex.contains(index);
    207273    }
    208274
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r15007 r15333  
    9999    /** Mouse position where the last image was selected. */
    100100    private Point lastSelPos;
     101    /** The mouse point */
     102    private Point startPoint;
    101103
    102104    /**
     
    153155        this.useThumbs = useThumbs;
    154156        this.data.addImageDataUpdateListener(this);
     157    }
     158
     159    private final class ImageMouseListener extends MouseAdapter {
     160        private boolean isMapModeOk() {
     161            MapMode mapMode = MainApplication.getMap().mapMode;
     162            return mapMode == null || isSupportedMapMode(mapMode);
     163        }
     164
     165        @Override
     166        public void mousePressed(MouseEvent e) {
     167            if (e.getButton() != MouseEvent.BUTTON1)
     168                return;
     169            if (isVisible() && isMapModeOk()) {
     170                cycleModeArmed = true;
     171                invalidate();
     172                startPoint = e.getPoint();
     173            }
     174        }
     175
     176        @Override
     177        public void mouseReleased(MouseEvent ev) {
     178            if (ev.getButton() != MouseEvent.BUTTON1)
     179                return;
     180            if (!isVisible() || !isMapModeOk())
     181                return;
     182            if (!cycleModeArmed) {
     183                return;
     184            }
     185
     186            Rectangle hitBoxClick = new Rectangle((int) startPoint.getX() - 10, (int) startPoint.getY() - 10, 15, 15);
     187            if (!hitBoxClick.contains(ev.getPoint())) {
     188                return;
     189            }
     190
     191            Point mousePos = ev.getPoint();
     192            boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
     193            final boolean isShift = (ev.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK;
     194            final boolean isCtrl = (ev.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == MouseEvent.CTRL_DOWN_MASK;
     195            int idx = getPhotoIdxUnderMouse(ev, cycle);
     196            if (idx >= 0) {
     197                lastSelPos = mousePos;
     198                cycleModeArmed = false;
     199                ImageEntry img = data.getImages().get(idx);
     200                if (isShift) {
     201                    if (isCtrl && !data.getSelectedImages().isEmpty()) {
     202                        int idx2 = data.getImages().indexOf(data.getSelectedImages().get(data.getSelectedImages().size() - 1));
     203                        int startIndex = Math.min(idx, idx2);
     204                        int endIndex = Math.max(idx, idx2);
     205                        for (int i = startIndex; i <= endIndex; i++) {
     206                            data.addImageToSelection(data.getImages().get(i));
     207                        }
     208                    } else {
     209                        if (data.isImageSelected(img)) {
     210                            data.removeImageToSelection(img);
     211                        } else {
     212                            data.addImageToSelection(img);
     213                        }
     214                    }
     215                } else {
     216                    data.setSelectedImage(img);
     217                }
     218            }
     219        }
    155220    }
    156221
     
    497562                    paintImage(e, mv, clip, tempG);
    498563                }
    499                 if (data.getSelectedImage() != null) {
     564                for (ImageEntry img: this.data.getSelectedImages()) {
    500565                    // Make sure the selected image is on top in case multiple images overlap.
    501                     paintImage(data.getSelectedImage(), mv, clip, tempG);
     566                    paintImage(img, mv, clip, tempG);
    502567                }
    503568                updateOffscreenBuffer = false;
     
    516581        }
    517582
    518         ImageEntry e = data.getSelectedImage();
    519         if (e != null && e.getPos() != null) {
    520             Point p = mv.getPoint(e.getPos());
    521 
    522             int imgWidth;
    523             int imgHeight;
    524             if (useThumbs && e.hasThumbnail()) {
    525                 Dimension d = scaledDimension(e.getThumbnail());
    526                 if (d != null) {
    527                     imgWidth = d.width;
    528                     imgHeight = d.height;
     583        for (ImageEntry e: data.getSelectedImages()) {
     584            if (e != null && e.getPos() != null) {
     585                Point p = mv.getPoint(e.getPos());
     586
     587                int imgWidth;
     588                int imgHeight;
     589                if (useThumbs && e.hasThumbnail()) {
     590                    Dimension d = scaledDimension(e.getThumbnail());
     591                    if (d != null) {
     592                        imgWidth = d.width;
     593                        imgHeight = d.height;
     594                    } else {
     595                        imgWidth = -1;
     596                        imgHeight = -1;
     597                    }
    529598                } else {
    530                     imgWidth = -1;
    531                     imgHeight = -1;
    532                 }
    533             } else {
    534                 imgWidth = selectedIcon.getIconWidth();
    535                 imgHeight = selectedIcon.getIconHeight();
    536             }
    537 
    538             if (e.getExifImgDir() != null) {
    539                 // Multiplier must be larger than sqrt(2)/2=0.71.
    540                 double arrowlength = Math.max(25, Math.max(imgWidth, imgHeight) * 0.85);
    541                 double arrowwidth = arrowlength / 1.4;
    542 
    543                 double dir = e.getExifImgDir();
    544                 // Rotate 90 degrees CCW
    545                 double headdir = (dir < 90) ? dir + 270 : dir - 90;
    546                 double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90;
    547                 double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90;
    548 
    549                 double ptx = p.x + Math.cos(Utils.toRadians(headdir)) * arrowlength;
    550                 double pty = p.y + Math.sin(Utils.toRadians(headdir)) * arrowlength;
    551 
    552                 double ltx = p.x + Math.cos(Utils.toRadians(leftdir)) * arrowwidth/2;
    553                 double lty = p.y + Math.sin(Utils.toRadians(leftdir)) * arrowwidth/2;
    554 
    555                 double rtx = p.x + Math.cos(Utils.toRadians(rightdir)) * arrowwidth/2;
    556                 double rty = p.y + Math.sin(Utils.toRadians(rightdir)) * arrowwidth/2;
    557 
    558                 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    559                 g.setColor(new Color(255, 255, 255, 192));
    560                 int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
    561                 int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
    562                 g.fillPolygon(xar, yar, 4);
    563                 g.setColor(Color.black);
    564                 g.setStroke(new BasicStroke(1.2f));
    565                 g.drawPolyline(xar, yar, 3);
    566             }
    567 
    568             if (useThumbs && e.hasThumbnail()) {
    569                 g.setColor(new Color(128, 0, 0, 122));
    570                 g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight);
    571             } else {
    572                 selectedIcon.paintIcon(mv, g,
    573                         p.x - imgWidth / 2,
    574                         p.y - imgHeight / 2);
     599                    imgWidth = selectedIcon.getIconWidth();
     600                    imgHeight = selectedIcon.getIconHeight();
     601                }
     602
     603                if (e.getExifImgDir() != null) {
     604                    // Multiplier must be larger than sqrt(2)/2=0.71.
     605                    double arrowlength = Math.max(25, Math.max(imgWidth, imgHeight) * 0.85);
     606                    double arrowwidth = arrowlength / 1.4;
     607
     608                    double dir = e.getExifImgDir();
     609                    // Rotate 90 degrees CCW
     610                    double headdir = (dir < 90) ? dir + 270 : dir - 90;
     611                    double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90;
     612                    double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90;
     613
     614                    double ptx = p.x + Math.cos(Utils.toRadians(headdir)) * arrowlength;
     615                    double pty = p.y + Math.sin(Utils.toRadians(headdir)) * arrowlength;
     616
     617                    double ltx = p.x + Math.cos(Utils.toRadians(leftdir)) * arrowwidth/2;
     618                    double lty = p.y + Math.sin(Utils.toRadians(leftdir)) * arrowwidth/2;
     619
     620                    double rtx = p.x + Math.cos(Utils.toRadians(rightdir)) * arrowwidth/2;
     621                    double rty = p.y + Math.sin(Utils.toRadians(rightdir)) * arrowwidth/2;
     622
     623                    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     624                    g.setColor(new Color(255, 255, 255, 192));
     625                    int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
     626                    int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
     627                    g.fillPolygon(xar, yar, 4);
     628                    g.setColor(Color.black);
     629                    g.setStroke(new BasicStroke(1.2f));
     630                    g.drawPolyline(xar, yar, 3);
     631                }
     632
     633                if (useThumbs && e.hasThumbnail()) {
     634                    g.setColor(new Color(128, 0, 0, 122));
     635                    g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight);
     636                } else {
     637                    selectedIcon.paintIcon(mv, g,
     638                            p.x - imgWidth / 2,
     639                            p.y - imgHeight / 2);
     640                }
    575641            }
    576642        }
     
    743809    @Override
    744810    public void hookUpMapView() {
    745         mouseAdapter = new MouseAdapter() {
    746             private boolean isMapModeOk() {
    747                 MapMode mapMode = MainApplication.getMap().mapMode;
    748                 return mapMode == null || isSupportedMapMode(mapMode);
    749             }
    750 
    751             @Override
    752             public void mousePressed(MouseEvent e) {
    753                 if (e.getButton() != MouseEvent.BUTTON1)
    754                     return;
    755                 if (isVisible() && isMapModeOk()) {
    756                     cycleModeArmed = true;
    757                     invalidate();
    758                 }
    759             }
    760 
    761             @Override
    762             public void mouseReleased(MouseEvent ev) {
    763                 if (ev.getButton() != MouseEvent.BUTTON1)
    764                     return;
    765                 if (!isVisible() || !isMapModeOk())
    766                     return;
    767 
    768                 Point mousePos = ev.getPoint();
    769                 boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
    770                 int idx = getPhotoIdxUnderMouse(ev, cycle);
    771                 if (idx >= 0) {
    772                     lastSelPos = mousePos;
    773                     cycleModeArmed = false;
    774                     data.setSelectedImage(data.getImages().get(idx));
    775                 }
    776             }
    777         };
     811        mouseAdapter = new ImageMouseListener();
    778812
    779813        mouseMotionAdapter = new MouseMotionAdapter() {
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

    r15204 r15333  
    6767    private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener();
    6868
     69    private String emptyText;
    6970    private String osdText;
    7071
     
    699700
    700701    /**
     702     * Set the message displayed when there is no image to display.
     703     * By default it display a simple No image
     704     * @param emptyText the string to display
     705     * @since 15333
     706     */
     707    public void setEmptyText(String emptyText) {
     708        this.emptyText = emptyText;
     709    }
     710
     711    /**
    701712     * Sets the On-Screen-Display text.
    702713     * @param text text to display on top of the image
     
    730741        if (entry == null) {
    731742            g.setColor(Color.black);
    732             String noImageStr = tr("No image");
     743            if (emptyText == null) {
     744                emptyText = tr("No image");
     745            }
     746            String noImageStr = emptyText;
    733747            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
    734748            g.drawString(noImageStr,
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    r15219 r15333  
    1414import java.text.DateFormat;
    1515import java.text.SimpleDateFormat;
     16import java.util.Collections;
     17import java.util.List;
    1618import java.util.Optional;
    1719
     
    377379     * @param data geo image data
    378380     * @param entry image entry
    379      */
     381     * @deprecated Use {@link #displayImage}
     382     */
     383    @Deprecated
    380384    public static void showImage(ImageData data, ImageEntry entry) {
    381385        getInstance().displayImage(data, entry);
     
    417421
    418422    /**
    419      * Displays image for the given layer.
     423     * Displays a single image for the given layer.
    420424     * @param data the image data
    421425     * @param entry image entry
     426     * @see #displayImages
    422427     */
    423428    public void displayImage(ImageData data, ImageEntry entry) {
     429        displayImages(data, Collections.singletonList(entry));
     430    }
     431
     432    /**
     433     * Displays images for the given layer.
     434     * @param data the image data
     435     * @param entries image entries
     436     * @since 15333
     437     */
     438    public void displayImages(ImageData data, List<ImageEntry> entries) {
    424439        boolean imageChanged;
     440        ImageEntry entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
    425441
    426442        synchronized (this) {
     
    491507            btnDeleteFromDisk.setEnabled(false);
    492508            btnCopyPath.setEnabled(false);
     509            if (entries != null && entries.size() > 1) {
     510                imgDisplay.setEmptyText(tr("Multiple images selected"));
     511                btnFirst.setEnabled(!isFirstImageSelected(data));
     512                btnLast.setEnabled(!isLastImageSelected(data));
     513            }
     514            imgDisplay.setImage(null);
     515            imgDisplay.setOsdText("");
    493516            return;
    494517        }
     
    504527    }
    505528
     529    private static boolean isLastImageSelected(ImageData data) {
     530        return data.isImageSelected(data.getImages().get(data.getImages().size() - 1));
     531    }
     532
     533    private static boolean isFirstImageSelected(ImageData data) {
     534        return data.isImageSelected(data.getImages().get(0));
     535    }
     536
    506537    /**
    507538     * When an image is closed, really close it and do not pop
     
    562593            ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData();
    563594            if (removedData == currentData) {
    564                 displayImage(null, null);
     595                displayImages(null, null);
    565596            }
    566597            removedData.removeImageDataUpdateListener(this);
     
    592623    @Override
    593624    public void selectedImageChanged(ImageData data) {
    594         showImage(data, data.getSelectedImage());
     625        displayImages(data, data.getSelectedImages());
    595626    }
    596627
    597628    @Override
    598629    public void imageDataUpdated(ImageData data) {
    599         showImage(data, data.getSelectedImage());
     630        displayImages(data, data.getSelectedImages());
    600631    }
    601632}
Note: See TracChangeset for help on using the changeset viewer.