Ticket #20795: 20795-v2.patch

File 20795-v2.patch, 18.1 KB (added by Bjoeni, 4 years ago)
  • test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java

    ### Eclipse Workspace Patch 1.0
    #P josm
     
    1212
    1313import org.junit.jupiter.api.BeforeAll;
    1414import org.junit.jupiter.api.BeforeEach;
    15 import org.junit.jupiter.api.MethodOrderer;
    1615import org.junit.jupiter.api.MethodOrderer.MethodName;
    1716import org.junit.jupiter.api.Test;
    1817import org.junit.jupiter.api.TestInstance;
  • src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

     
    8181    GpxLayer gpxLayer;
    8282    GpxLayer gpxFauxLayer;
    8383
     84    private CorrelateGpxWithImages gpxCorrelateAction;
     85
    8486    private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
    8587    private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
    8688
     
    405407        entries.add(LayerListDialog.getInstance().createMergeLayerAction(this));
    406408        entries.add(new RenameLayerAction(null, this));
    407409        entries.add(SeparatorLayerAction.INSTANCE);
    408         entries.add(new CorrelateGpxWithImages(this));
     410        entries.add(getGpxCorrelateAction());
    409411        entries.add(new ShowThumbnailAction(this));
    410412        if (!menuAdditions.isEmpty()) {
    411413            entries.add(SeparatorLayerAction.INSTANCE);
     
    851853    public synchronized void destroy() {
    852854        super.destroy();
    853855        stopLoadThumbs();
     856        if (gpxCorrelateAction != null) {
     857            gpxCorrelateAction.destroy();
     858            gpxCorrelateAction = null;
     859        }
    854860        MapView mapView = MainApplication.getMap().mapView;
    855861        mapView.removeMouseListener(mouseAdapter);
    856862        mapView.removeMouseMotionListener(mouseMotionAdapter);
     
    944950    }
    945951
    946952    /**
     953     * Returns the gpxCorrelateAction
     954     * @return the gpxCorrelateAction
     955     */
     956    public CorrelateGpxWithImages getGpxCorrelateAction() {
     957        if (gpxCorrelateAction == null) {
     958            gpxCorrelateAction = new CorrelateGpxWithImages(this);
     959        }
     960        return gpxCorrelateAction;
     961    }
     962
     963    /**
    947964     * Returns a faux GPX layer built from the images or the associated GPX layer.
    948965     * @return A faux GPX layer or the associated GPX layer
    949966     * @since 14802
  • src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java

     
    168168                }
    169169            }
    170170        }
    171         if (trkTag) {
     171        if (trkTag && prevWp != null) {
    172172            ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, true);
    173173        }
    174174        return ret;
  • src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

     
    2121import java.awt.event.ItemListener;
    2222import java.awt.event.WindowAdapter;
    2323import java.awt.event.WindowEvent;
     24import java.beans.PropertyChangeEvent;
     25import java.beans.PropertyChangeListener;
    2426import java.io.File;
    2527import java.io.IOException;
    2628import java.io.InputStream;
     
    4143import java.util.Objects;
    4244import java.util.TimeZone;
    4345import java.util.concurrent.TimeUnit;
     46import java.util.function.Consumer;
    4447import java.util.stream.Collectors;
    4548
    4649import javax.swing.AbstractAction;
     
    100103import org.openstreetmap.josm.io.nmea.NmeaReader;
    101104import org.openstreetmap.josm.spi.preferences.Config;
    102105import org.openstreetmap.josm.spi.preferences.IPreferences;
     106import org.openstreetmap.josm.tools.Destroyable;
    103107import org.openstreetmap.josm.tools.GBC;
    104108import org.openstreetmap.josm.tools.ImageProvider;
    105109import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    113117 * Then it correlates the images of the layer with that GPX file.
    114118 * @since 2566
    115119 */
    116 public class CorrelateGpxWithImages extends AbstractAction {
     120public class CorrelateGpxWithImages extends AbstractAction implements Destroyable {
    117121
    118     private static final List<GpxData> loadedGpxData = new ArrayList<>();
     122    private static MutableComboBoxModel<GpxDataWrapper> gpxModel;
     123    private static boolean forceTags;
    119124
    120125    private final transient GeoImageLayer yLayer;
    121126    private transient GpxTimezone timezone;
    122127    private transient GpxTimeOffset delta;
    123     private static boolean forceTags;
    124128
    125129    /**
    126130     * Constructs a new {@code CorrelateGpxWithImages} action.
     
    130134        super(tr("Correlate to GPX"));
    131135        new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true);
    132136        this.yLayer = layer;
    133         MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
    134137    }
    135138
    136139    private final class SyncDialogWindowListener extends WindowAdapter {
     
    233236    }
    234237
    235238    private static class GpxDataWrapper {
    236         private final String name;
     239        private String name;
    237240        private final GpxData data;
    238241        private final File file;
    239242
     
    243246            this.file = file;
    244247        }
    245248
     249        void setName(String name) {
     250            this.name = name;
     251            forEachLayer(CorrelateGpxWithImages::repaintCombobox);
     252        }
     253
    246254        @Override
    247255        public String toString() {
    248256            return name;
     
    249257        }
    250258    }
    251259
     260    private static class NoGpxDataWrapper extends GpxDataWrapper {
     261        NoGpxDataWrapper() {
     262            super(null, null, null);
     263        }
     264
     265        @Override
     266        public String toString() {
     267            return tr("<No GPX track loaded yet>");
     268        }
     269    }
     270
    252271    private ExtendedDialog syncDialog;
    253     private MutableComboBoxModel<GpxDataWrapper> gpxModel;
    254272    private JPanel outerPanel;
    255273    private JosmComboBox<GpxDataWrapper> cbGpx;
    256274    private JosmTextField tfTimezone;
     
    280298
    281299            try {
    282300                outerPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    283                 for (int i = gpxModel.getSize() - 1; i >= 0; i--) {
    284                     GpxDataWrapper wrapper = gpxModel.getElementAt(i);
    285                     if (sel.equals(wrapper.file)) {
    286                         gpxModel.setSelectedItem(wrapper);
    287                         if (!sel.getName().equals(wrapper.name)) {
    288                             JOptionPane.showMessageDialog(
    289                                     MainApplication.getMainFrame(),
    290                                     tr("File {0} is loaded yet under the name \"{1}\"", sel.getName(), wrapper.name),
    291                                     tr("Error"),
    292                                     JOptionPane.ERROR_MESSAGE
    293                             );
    294                         }
    295                         return;
    296                     }
    297                 }
     301                removeDuplicates(sel);
    298302                GpxData data = null;
    299303                try (InputStream iStream = Compression.getUncompressedFileInputStream(sel)) {
    300304                    IGpxReader reader = gpxFilter.accept(sel) ? new GpxReader(iStream) : new NmeaReader(iStream);
     
    322326                    return;
    323327                }
    324328
    325                 loadedGpxData.add(data);
    326                 if (gpxModel.getElementAt(0).file == null) {
    327                     gpxModel.removeElementAt(0);
    328                 }
    329329                GpxDataWrapper elem = new GpxDataWrapper(sel.getName(), data, sel);
    330330                gpxModel.addElement(elem);
    331331                gpxModel.setSelectedItem(elem);
     332                statusBarUpdater.matchAndUpdateStatusBar();
    332333            } finally {
    333334                outerPanel.setCursor(Cursor.getDefaultCursor());
    334335            }
     
    527528
    528529                forceTags = cForce.isSelected(); // This setting is not supposed to be saved permanently
    529530
    530                 statusBarUpdater.updateStatusBar();
     531                statusBarUpdater.matchAndUpdateStatusBar();
    531532                yLayer.updateBufferAndRepaint();
    532533            }
    533534        }
     
    764765                isOk = true;
    765766
    766767            }
    767             statusBarUpdater.updateStatusBar();
     768            statusBarUpdater.matchAndUpdateStatusBar();
    768769            yLayer.updateBufferAndRepaint();
    769770        }
    770771
     
    787788        }
    788789    }
    789790
    790     private class GpxLayerAddedListener implements LayerChangeListener {
     791    private static class GpxLayerAddedListener implements LayerChangeListener {
    791792        @Override
    792793        public void layerAdded(LayerAddEvent e) {
    793             if (syncDialog != null && syncDialog.isVisible()) {
    794                 Layer layer = e.getAddedLayer();
    795                 if (layer instanceof GpxLayer) {
    796                     GpxLayer gpx = (GpxLayer) layer;
    797                     GpxDataWrapper gdw = new GpxDataWrapper(gpx.getName(), gpx.data, gpx.data.storageFile);
    798                     if (gpxModel.getElementAt(0).file == null) {
    799                         gpxModel.removeElementAt(0);
    800                     }
    801                     gpxModel.addElement(gdw);
    802                 }
     794            Layer layer = e.getAddedLayer();
     795            if (layer instanceof GpxLayer) {
     796                GpxLayer gpx = (GpxLayer) layer;
     797                File file = gpx.data.storageFile;
     798                removeDuplicates(file);
     799                GpxDataWrapper gdw = new GpxDataWrapper(gpx.getName(), gpx.data, file);
     800                gpx.addPropertyChangeListener(new GpxLayerRenamedListener(gdw));
     801                gpxModel.addElement(gdw);
     802                forEachLayer(CorrelateGpxWithImages::repaintCombobox);
    803803            }
    804804        }
    805805
     
    814814        }
    815815    }
    816816
     817    private static class GpxLayerRenamedListener implements PropertyChangeListener {
     818        private GpxDataWrapper gdw;
     819        GpxLayerRenamedListener(GpxDataWrapper gdw) {
     820            this.gdw = gdw;
     821        }
     822
     823        @Override
     824        public void propertyChange(PropertyChangeEvent e) {
     825            if (Layer.NAME_PROP.equals(e.getPropertyName())) {
     826                gdw.setName(e.getNewValue().toString());
     827            }
     828        }
     829    }
     830
    817831    @Override
    818832    public void actionPerformed(ActionEvent ae) {
    819         // Construct the list of loaded GPX tracks
    820         gpxModel = new DefaultComboBoxModel<>();
    821         GpxDataWrapper defaultItem = null;
    822         for (GpxLayer cur : MainApplication.getLayerManager().getLayersOfType(GpxLayer.class).stream()
    823                 .filter(GpxLayer::isLocalFile).collect(Collectors.toList())) {
    824             GpxDataWrapper gdw = new GpxDataWrapper(cur.getName(), cur.data, cur.data.storageFile);
    825             gpxModel.addElement(gdw);
    826             if (cur == yLayer.gpxLayer || (defaultItem == null && gdw.file != null)) {
    827                 defaultItem = gdw;
     833        NoGpxDataWrapper nogdw = new NoGpxDataWrapper();
     834        if (gpxModel == null) {
     835            // Construct the list of loaded GPX tracks
     836            gpxModel = new DefaultComboBoxModel<>();
     837            GpxDataWrapper defaultItem = null;
     838            for (GpxLayer cur : MainApplication.getLayerManager().getLayersOfType(GpxLayer.class)) {
     839                GpxDataWrapper gdw = new GpxDataWrapper(cur.getName(), cur.data, cur.data.storageFile);
     840                cur.addPropertyChangeListener(new GpxLayerRenamedListener(gdw));
     841                gpxModel.addElement(gdw);
     842                if (cur == yLayer.gpxLayer || defaultItem == null) {
     843                    defaultItem = gdw;
     844                }
    828845            }
    829         }
    830         for (GpxData data : loadedGpxData) {
    831             GpxDataWrapper gdw = new GpxDataWrapper(data.storageFile.getName(), data, data.storageFile);
    832             gpxModel.addElement(gdw);
    833             if (defaultItem == null && gdw.file != null) { // select first GPX track associated to a file
    834                 defaultItem = gdw;
     846
     847            if (gpxModel.getSize() == 0) {
     848                gpxModel.addElement(nogdw);
     849            } else if (defaultItem != null) {
     850                gpxModel.setSelectedItem(defaultItem);
    835851            }
     852            MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
    836853        }
    837854
    838         GpxDataWrapper nogdw = new GpxDataWrapper(tr("<No GPX track loaded yet>"), null, null);
    839         if (gpxModel.getSize() == 0) {
    840             gpxModel.addElement(nogdw);
    841         } else if (defaultItem != null) {
    842             gpxModel.setSelectedItem(defaultItem);
    843         }
    844 
    845855        JPanel panelCb = new JPanel();
    846856
    847857        panelCb.add(new JLabel(tr("GPX track: ")));
     
    10071017        cbExifImg.addItemListener(statusBarUpdaterWithRepaint);
    10081018        cbTaggedImg.addItemListener(statusBarUpdaterWithRepaint);
    10091019
    1010         statusBarUpdater.updateStatusBar();
     1020        statusBarUpdater.matchAndUpdateStatusBar();
    10111021        yLayer.updateBufferAndRepaint();
    10121022
    10131023        outerPanel = new JPanel(new BorderLayout());
     
    10141024        outerPanel.add(statusBar, BorderLayout.PAGE_END);
    10151025
    10161026        if (!GraphicsEnvironment.isHeadless()) {
     1027            forEachLayer(CorrelateGpxWithImages::closeDialog);
    10171028            syncDialog = new ExtendedDialog(
    10181029                    MainApplication.getMainFrame(),
    10191030                    tr("Correlate images with GPX track"),
     
    10311042        }
    10321043    }
    10331044
     1045    private static void removeDuplicates(File file) {
     1046        for (int i = gpxModel.getSize() - 1; i >= 0; i--) {
     1047            GpxDataWrapper wrapper = gpxModel.getElementAt(i);
     1048            if (wrapper instanceof NoGpxDataWrapper || (file != null && file.equals(wrapper.file))) {
     1049                gpxModel.removeElement(wrapper);
     1050            }
     1051        }
     1052    }
     1053
     1054    private static void forEachLayer(Consumer<CorrelateGpxWithImages> action) {
     1055        MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)
     1056                .forEach(geo -> action.accept(geo.getGpxCorrelateAction()));
     1057    }
     1058
    10341059    private final transient StatusBarUpdater statusBarUpdater = new StatusBarUpdater(false);
    10351060    private final transient StatusBarUpdater statusBarUpdaterWithRepaint = new StatusBarUpdater(true);
    10361061
     
    10431068
    10441069        @Override
    10451070        public void insertUpdate(DocumentEvent ev) {
    1046             updateStatusBar();
     1071            matchAndUpdateStatusBar();
    10471072        }
    10481073
    10491074        @Override
    10501075        public void removeUpdate(DocumentEvent ev) {
    1051             updateStatusBar();
     1076            matchAndUpdateStatusBar();
    10521077        }
    10531078
    10541079        @Override
     
    10581083
    10591084        @Override
    10601085        public void itemStateChanged(ItemEvent e) {
    1061             updateStatusBar();
     1086            matchAndUpdateStatusBar();
    10621087        }
    10631088
    10641089        @Override
    10651090        public void actionPerformed(ActionEvent e) {
    1066             updateStatusBar();
     1091            matchAndUpdateStatusBar();
    10671092        }
    10681093
    1069         public void updateStatusBar() {
    1070             statusBarText.setText(statusText());
    1071             if (doRepaint) {
    1072                 yLayer.updateBufferAndRepaint();
     1094        public void matchAndUpdateStatusBar() {
     1095            if (syncDialog != null && syncDialog.isVisible()) {
     1096                statusBarText.setText(matchAndGetStatusText());
     1097                if (doRepaint) {
     1098                    yLayer.updateBufferAndRepaint();
     1099                }
    10731100            }
    10741101        }
    10751102
    1076         private String statusText() {
     1103        private String matchAndGetStatusText() {
    10771104            try {
    10781105                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
    10791106                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
     
    11961223                    lblMatches.setText(statusBarText.getText() + "<br>" + trn("(Time difference of {0} day)",
    11971224                            "Time difference of {0} days", Math.abs(dayOffset), Math.abs(dayOffset)));
    11981225
    1199                     statusBarUpdater.updateStatusBar();
     1226                    statusBarUpdater.matchAndUpdateStatusBar();
    12001227                    yLayer.updateBufferAndRepaint();
    12011228                }
    12021229            }
     
    12501277    static class NoGpxTimestamps extends Exception {
    12511278    }
    12521279
     1280    void closeDialog() {
     1281        if (syncDialog != null) {
     1282            syncDialog.setVisible(false);
     1283            new SyncDialogWindowListener().windowDeactivated(null);
     1284            syncDialog.dispose();
     1285            syncDialog = null;
     1286        }
     1287    }
     1288
     1289    void repaintCombobox() {
     1290        if (cbGpx != null) {
     1291            cbGpx.repaint();
     1292        }
     1293    }
     1294
    12531295    /**
    12541296     * Tries to auto-guess the timezone and offset.
    12551297     *
     
    13151357            tfTimezone.getDocument().addDocumentListener(statusBarUpdater);
    13161358            tfOffset.getDocument().addDocumentListener(statusBarUpdater);
    13171359
    1318             statusBarUpdater.updateStatusBar();
     1360            statusBarUpdater.matchAndUpdateStatusBar();
    13191361            yLayer.updateBufferAndRepaint();
    13201362        }
    13211363    }
     
    13431385    private GpxDataWrapper selectedGPX(boolean complain) {
    13441386        Object item = gpxModel.getSelectedItem();
    13451387
    1346         if (item == null || ((GpxDataWrapper) item).file == null) {
     1388        if (item == null || ((GpxDataWrapper) item).data == null) {
    13471389            if (complain) {
    13481390                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), tr("You should select a GPX track"),
    13491391                        tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE);
     
    13531395        return (GpxDataWrapper) item;
    13541396    }
    13551397
     1398    @Override
     1399    public void destroy() {
     1400        if (cbGpx != null) {
     1401            // Force the JCombobox to remove its eventListener from the static GpxDataWrapper
     1402            cbGpx.setModel(new DefaultComboBoxModel<GpxDataWrapper>());
     1403            cbGpx = null;
     1404        }
     1405        closeDialog();
     1406    }
     1407
    13561408}