Ticket #14181: GeoImageLayer_select.patch

File GeoImageLayer_select.patch, 16.4 KB (added by holgermappt, 9 years ago)
  • src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

     
    1616import java.awt.RenderingHints;
    1717import java.awt.event.MouseAdapter;
    1818import java.awt.event.MouseEvent;
     19import java.awt.event.MouseMotionAdapter;
    1920import java.awt.image.BufferedImage;
    2021import java.beans.PropertyChangeEvent;
    2122import java.beans.PropertyChangeListener;
     
    9798    boolean updateOffscreenBuffer = true;
    9899
    99100    private MouseAdapter mouseAdapter;
     101    private MouseMotionAdapter mouseMotionAdapter;
    100102    private MapModeChangeListener mapModeListener;
    101103
     104    /** Mouse position where the last image was selected. */
     105    private Point lastSelPos = null;
    102106    /**
     107     * Image cycle mode flag.
     108     * It is possible that a mouse button release triggers multiple
     109     * mouseReleased() events. To prevent the cycling in such a case we wait
     110     * for the next mouse button press event before it is cycled to the next
     111     * image.
     112     */
     113    private boolean cycleModeArmed = false;
     114
     115    /**
    103116     * Constructs a new {@code GeoImageLayer}.
    104117     * @param data The list of images to display
    105118     * @param gpxLayer The associated GPX layer
     
    467480                (int) Math.round(f * thumb.getHeight(null)));
    468481    }
    469482
     483    /**
     484     * Paint one image.
     485     * @param e Image to be painted
     486     * @param mv Map view
     487     * @param clip Bounding rectangle of the current clipping area
     488     * @param tempG Temporary offscreen buffer
     489     */
     490    private void paintImage(ImageEntry e, MapView mv, Rectangle clip, Graphics2D tempG) {
     491        if (e.getPos() == null) {
     492            return;
     493        }
     494        Point p = mv.getPoint(e.getPos());
     495        if (e.hasThumbnail()) {
     496            Dimension d = scaledDimension(e.getThumbnail());
     497            if (d != null) {
     498                Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
     499                if (clip.intersects(target)) {
     500                    tempG.drawImage(e.getThumbnail(), target.x, target.y, target.width, target.height, null);
     501                }
     502            }
     503        } else { // thumbnail not loaded yet
     504            icon.paintIcon(mv, tempG,
     505                p.x - icon.getIconWidth() / 2,
     506                p.y - icon.getIconHeight() / 2);
     507        }
     508    }
     509
    470510    @Override
    471511    public void paint(Graphics2D g, MapView mv, Bounds bounds) {
    472512        int width = mv.getWidth();
     
    494534
    495535                if (data != null) {
    496536                    for (ImageEntry e : data) {
    497                         if (e.getPos() == null) {
    498                             continue;
    499                         }
    500                         Point p = mv.getPoint(e.getPos());
    501                         if (e.hasThumbnail()) {
    502                             Dimension d = scaledDimension(e.getThumbnail());
    503                             if (d != null) {
    504                                 Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
    505                                 if (clip.intersects(target)) {
    506                                     tempG.drawImage(e.getThumbnail(), target.x, target.y, target.width, target.height, null);
    507                                 }
    508                             }
    509                         } else { // thumbnail not loaded yet
    510                             icon.paintIcon(mv, tempG,
    511                                     p.x - icon.getIconWidth() / 2,
    512                                     p.y - icon.getIconHeight() / 2);
    513                         }
     537                        paintImage(e, mv, clip, tempG);
    514538                    }
     539                    if (currentPhoto >= 0 && currentPhoto < data.size()) {
     540                        // Make sure the selected image is on top in case multiple images overlap.
     541                        paintImage(data.get(currentPhoto), mv, clip, tempG);
     542                    }
    515543                }
    516544                updateOffscreenBuffer = false;
    517545            }
     
    601629    }
    602630
    603631    /**
     632     * Show current photo on map and in image viewer.
     633     */
     634    public void showCurrentPhoto() {
     635        clearOtherCurrentPhotos();
     636        if (currentPhoto >= 0) {
     637            ImageViewerDialog.showImage(this, data.get(currentPhoto));
     638        } else {
     639            ImageViewerDialog.showImage(this, null);
     640        }
     641        updateOffscreenBuffer = true;
     642        Main.map.repaint();
     643    }
     644
     645    /**
    604646     * Shows next photo.
    605647     */
    606648    public void showNextPhoto() {
     
    609651            if (currentPhoto >= data.size()) {
    610652                currentPhoto = data.size() - 1;
    611653            }
    612             ImageViewerDialog.showImage(this, data.get(currentPhoto));
    613654        } else {
    614655            currentPhoto = -1;
    615656        }
    616         Main.map.repaint();
     657        showCurrentPhoto();
    617658    }
    618659
    619660    /**
     
    625666            if (currentPhoto < 0) {
    626667                currentPhoto = 0;
    627668            }
    628             ImageViewerDialog.showImage(this, data.get(currentPhoto));
    629669        } else {
    630670            currentPhoto = -1;
    631671        }
    632         Main.map.repaint();
     672        showCurrentPhoto();
    633673    }
    634674
    635675    /**
     
    638678    public void showFirstPhoto() {
    639679        if (data != null && !data.isEmpty()) {
    640680            currentPhoto = 0;
    641             ImageViewerDialog.showImage(this, data.get(currentPhoto));
    642681        } else {
    643682            currentPhoto = -1;
    644683        }
    645         Main.map.repaint();
     684        showCurrentPhoto();
    646685    }
    647686
    648687    /**
     
    651690    public void showLastPhoto() {
    652691        if (data != null && !data.isEmpty()) {
    653692            currentPhoto = data.size() - 1;
    654             ImageViewerDialog.showImage(this, data.get(currentPhoto));
    655693        } else {
    656694            currentPhoto = -1;
    657695        }
    658         Main.map.repaint();
     696        showCurrentPhoto();
    659697    }
    660698
    661699    public void checkPreviousNextButtons() {
     
    669707            if (currentPhoto >= data.size()) {
    670708                currentPhoto = data.size() - 1;
    671709            }
    672             if (currentPhoto >= 0) {
    673                 ImageViewerDialog.showImage(this, data.get(currentPhoto));
    674             } else {
    675                 ImageViewerDialog.showImage(this, null);
    676             }
    677             updateOffscreenBuffer = true;
    678             Main.map.repaint();
     710            showCurrentPhoto();
    679711        }
    680712    }
    681713
     
    702734                if (currentPhoto >= data.size()) {
    703735                    currentPhoto = data.size() - 1;
    704736                }
    705                 if (currentPhoto >= 0) {
    706                     ImageViewerDialog.showImage(this, data.get(currentPhoto));
    707                 } else {
    708                     ImageViewerDialog.showImage(this, null);
    709                 }
    710737
    711738                if (Utils.deleteFile(toDelete.getFile())) {
    712739                    Main.info("File "+toDelete.getFile()+" deleted. ");
     
    719746                            );
    720747                }
    721748
    722                 updateOffscreenBuffer = true;
    723                 Main.map.repaint();
     749                showCurrentPhoto();
    724750            }
    725751        }
    726752    }
     
    743769    }
    744770
    745771    /**
    746      * Returns the image that matches the position of the mouse event.
     772     * Check if the position of the mouse event is within the rectangle of the photo icon or thumbnail.
     773     * @param idx Image index, range 0 .. size-1
    747774     * @param evt Mouse event
    748      * @return Image at mouse position, or {@code null} if there is no image at the mouse position
    749      * @since 6392
     775     * @return {@code true} if the photo matches the mouse position,
     776     *         {@code false} otherwise
    750777     */
    751     public ImageEntry getPhotoUnderMouse(MouseEvent evt) {
    752         if (data != null) {
    753             for (int idx = data.size() - 1; idx >= 0; --idx) {
    754                 ImageEntry img = data.get(idx);
    755                 if (img.getPos() == null) {
    756                     continue;
    757                 }
    758                 Point p = Main.map.mapView.getPoint(img.getPos());
    759                 Rectangle r;
     778    private boolean isPhotoIdxUnderMouse(int idx, MouseEvent evt) {
     779        if (idx >= 0 && data != null && idx < data.size()) {
     780            ImageEntry img = data.get(idx);
     781            if (img.getPos() != null) {
     782                Point imgCenter = Main.map.mapView.getPoint(img.getPos());
     783                Rectangle imgRect;
    760784                if (useThumbs && img.hasThumbnail()) {
    761                     Dimension d = scaledDimension(img.getThumbnail());
    762                     if (d != null)
    763                         r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
    764                     else
    765                         r = null;
     785                    Dimension imgDim = scaledDimension(img.getThumbnail());
     786                    if (imgDim != null) {
     787                        imgRect = new Rectangle(imgCenter.x - imgDim.width / 2,
     788                                                imgCenter.y - imgDim.height / 2,
     789                                                imgDim.width,
     790                                                imgDim.height);
     791                    } else {
     792                        imgRect = null;
     793                    }
    766794                } else {
    767                     r = new Rectangle(p.x - icon.getIconWidth() / 2,
    768                                       p.y - icon.getIconHeight() / 2,
    769                                       icon.getIconWidth(),
    770                                       icon.getIconHeight());
     795                    imgRect = new Rectangle(imgCenter.x - icon.getIconWidth() / 2,
     796                                            imgCenter.y - icon.getIconHeight() / 2,
     797                                            icon.getIconWidth(),
     798                                            icon.getIconHeight());
    771799                }
    772                 if (r != null && r.contains(evt.getPoint())) {
    773                     return img;
     800                if (imgRect != null && imgRect.contains(evt.getPoint())) {
     801                    return true;
    774802                }
    775803            }
    776804        }
    777         return null;
     805        return false;
    778806    }
    779807
    780808    /**
     809     * Returns index of the image that matches the position of the mouse event.
     810     * @param evt    Mouse event
     811     * @param cycle  Set to {@code true} to cycle through the photos at the
     812     *               current mouse position if multiple icons or thumbnails
     813     *               overlap. If set to {@code false} the topmost photo will
     814     *               be used.
     815     * @return       Image index at mouse position, range 0 .. size-1,
     816     *               or {@code -1} if there is no image at the mouse position
     817     */
     818    private int getPhotoIdxUnderMouse(MouseEvent evt, boolean cycle) {
     819        if (data != null) {
     820            if (cycle && currentPhoto >= 0) {
     821                // Cycle loop is forward as that is the natural order.
     822                // Loop 1: One after current photo up to last one.
     823                for (int idx = currentPhoto + 1; idx < data.size(); ++idx) {
     824                    if (isPhotoIdxUnderMouse(idx, evt)) {
     825                        return idx;
     826                    }
     827                }
     828                // Loop 2: First photo up to current one.
     829                for (int idx = 0; idx <= currentPhoto; ++idx) {
     830                    if (isPhotoIdxUnderMouse(idx, evt)) {
     831                        return idx;
     832                    }
     833                }
     834            } else {
     835                // Check for current photo first, i.e. keep it selected if it
     836                // is under the mouse.
     837                if (currentPhoto >= 0 && isPhotoIdxUnderMouse(currentPhoto, evt)) {
     838                    return currentPhoto;
     839                }
     840                // Loop from last to first to prefer topmost image.
     841                for (int idx = data.size() - 1; idx >= 0; --idx) {
     842                    if (isPhotoIdxUnderMouse(idx, evt)) {
     843                        return idx;
     844                    }
     845                }
     846            }
     847        }
     848        return -1;
     849    }
     850
     851    /**
     852     * Returns index of the image that matches the position of the mouse event.
     853     * The topmost photo is picked if multiple icons or thumbnails overlap.
     854     * @param evt Mouse event
     855     * @return Image index at mouse position, range 0 .. size-1,
     856     *         or {@code -1} if there is no image at the mouse position
     857     */
     858    private int getPhotoIdxUnderMouse(MouseEvent evt) {
     859        return getPhotoIdxUnderMouse(evt, false);
     860    }
     861
     862    /**
     863     * Returns the image that matches the position of the mouse event.
     864     * The topmost photo is picked of multiple icons or thumbnails overlap.
     865     * @param evt Mouse event
     866     * @return Image at mouse position, or {@code null} if there is no image at the mouse position
     867     * @since 6392
     868     */
     869    public ImageEntry getPhotoUnderMouse(MouseEvent evt) {
     870        int idx = getPhotoIdxUnderMouse(evt);
     871        if (idx >= 0) {
     872            return data.get(idx);
     873        } else {
     874            return null;
     875        }
     876    }
     877
     878    /**
    781879     * Clears the currentPhoto, i.e. remove select marker, and optionally repaint.
    782880     * @param repaint Repaint flag
    783881     * @since 6392
     
    848946                    return;
    849947                if (isVisible() && isMapModeOk()) {
    850948                    Main.map.mapView.repaint();
     949                    cycleModeArmed = true;
    851950                }
    852951            }
    853952
     
    858957                if (data == null || !isVisible() || !isMapModeOk())
    859958                    return;
    860959
    861                 for (int i = data.size() - 1; i >= 0; --i) {
    862                     ImageEntry e = data.get(i);
    863                     if (e.getPos() == null) {
    864                         continue;
    865                     }
    866                     Point p = Main.map.mapView.getPoint(e.getPos());
    867                     Rectangle r;
    868                     if (useThumbs && e.hasThumbnail()) {
    869                         Dimension d = scaledDimension(e.getThumbnail());
    870                         if (d != null)
    871                             r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
    872                         else
    873                             r = null;
    874                     } else {
    875                         r = new Rectangle(p.x - icon.getIconWidth() / 2,
    876                                 p.y - icon.getIconHeight() / 2,
    877                                 icon.getIconWidth(),
    878                                 icon.getIconHeight());
    879                     }
    880                     if (r != null && r.contains(ev.getPoint())) {
    881                         clearOtherCurrentPhotos();
    882                         currentPhoto = i;
    883                         ImageViewerDialog.showImage(GeoImageLayer.this, e);
    884                         Main.map.repaint();
    885                         break;
    886                     }
     960                Point mousePos = ev.getPoint();
     961                boolean cycle = cycleModeArmed && lastSelPos != null && lastSelPos.equals(mousePos);
     962                int idx = getPhotoIdxUnderMouse(ev, cycle);
     963                if (idx >= 0) {
     964                    lastSelPos = mousePos;
     965                    cycleModeArmed = false;
     966                    currentPhoto = idx;
     967                    showCurrentPhoto();
    887968                }
    888969            }
    889970        };
    890971
     972        mouseMotionAdapter = new MouseMotionAdapter() {
     973            @Override
     974            public void mouseMoved(MouseEvent evt) {
     975                lastSelPos = null;
     976            }
     977
     978            @Override
     979            public void mouseDragged(MouseEvent evt) {
     980                lastSelPos = null;
     981            }
     982        };
     983
    891984        mapModeListener = (oldMapMode, newMapMode) -> {
    892985            if (newMapMode == null || isSupportedMapMode(newMapMode)) {
    893986                Main.map.mapView.addMouseListener(mouseAdapter);
     987                Main.map.mapView.addMouseMotionListener(mouseMotionAdapter);
    894988            } else {
    895989                Main.map.mapView.removeMouseListener(mouseAdapter);
     990                Main.map.mapView.removeMouseMotionListener(mouseMotionAdapter);
    896991            }
    897992        };
    898993
     
    9171012                if (e.getRemovedLayer() == GeoImageLayer.this) {
    9181013                    stopLoadThumbs();
    9191014                    Main.map.mapView.removeMouseListener(mouseAdapter);
     1015                    Main.map.mapView.removeMouseMotionListener(mouseMotionAdapter);
    9201016                    MapFrame.removeMapModeChangeListener(mapModeListener);
    9211017                    currentPhoto = -1;
    9221018                    if (data != null) {