Ticket #20795: 20795-v2.patch
| File 20795-v2.patch, 18.1 KB (added by , 4 years ago) |
|---|
-
test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java
### Eclipse Workspace Patch 1.0 #P josm
12 12 13 13 import org.junit.jupiter.api.BeforeAll; 14 14 import org.junit.jupiter.api.BeforeEach; 15 import org.junit.jupiter.api.MethodOrderer;16 15 import org.junit.jupiter.api.MethodOrderer.MethodName; 17 16 import org.junit.jupiter.api.Test; 18 17 import org.junit.jupiter.api.TestInstance; -
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
81 81 GpxLayer gpxLayer; 82 82 GpxLayer gpxFauxLayer; 83 83 84 private CorrelateGpxWithImages gpxCorrelateAction; 85 84 86 private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker"); 85 87 private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected"); 86 88 … … 405 407 entries.add(LayerListDialog.getInstance().createMergeLayerAction(this)); 406 408 entries.add(new RenameLayerAction(null, this)); 407 409 entries.add(SeparatorLayerAction.INSTANCE); 408 entries.add( new CorrelateGpxWithImages(this));410 entries.add(getGpxCorrelateAction()); 409 411 entries.add(new ShowThumbnailAction(this)); 410 412 if (!menuAdditions.isEmpty()) { 411 413 entries.add(SeparatorLayerAction.INSTANCE); … … 851 853 public synchronized void destroy() { 852 854 super.destroy(); 853 855 stopLoadThumbs(); 856 if (gpxCorrelateAction != null) { 857 gpxCorrelateAction.destroy(); 858 gpxCorrelateAction = null; 859 } 854 860 MapView mapView = MainApplication.getMap().mapView; 855 861 mapView.removeMouseListener(mouseAdapter); 856 862 mapView.removeMouseMotionListener(mouseMotionAdapter); … … 944 950 } 945 951 946 952 /** 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 /** 947 964 * Returns a faux GPX layer built from the images or the associated GPX layer. 948 965 * @return A faux GPX layer or the associated GPX layer 949 966 * @since 14802 -
src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java
168 168 } 169 169 } 170 170 } 171 if (trkTag ) {171 if (trkTag && prevWp != null) { 172 172 ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, true); 173 173 } 174 174 return ret; -
src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
21 21 import java.awt.event.ItemListener; 22 22 import java.awt.event.WindowAdapter; 23 23 import java.awt.event.WindowEvent; 24 import java.beans.PropertyChangeEvent; 25 import java.beans.PropertyChangeListener; 24 26 import java.io.File; 25 27 import java.io.IOException; 26 28 import java.io.InputStream; … … 41 43 import java.util.Objects; 42 44 import java.util.TimeZone; 43 45 import java.util.concurrent.TimeUnit; 46 import java.util.function.Consumer; 44 47 import java.util.stream.Collectors; 45 48 46 49 import javax.swing.AbstractAction; … … 100 103 import org.openstreetmap.josm.io.nmea.NmeaReader; 101 104 import org.openstreetmap.josm.spi.preferences.Config; 102 105 import org.openstreetmap.josm.spi.preferences.IPreferences; 106 import org.openstreetmap.josm.tools.Destroyable; 103 107 import org.openstreetmap.josm.tools.GBC; 104 108 import org.openstreetmap.josm.tools.ImageProvider; 105 109 import org.openstreetmap.josm.tools.JosmRuntimeException; … … 113 117 * Then it correlates the images of the layer with that GPX file. 114 118 * @since 2566 115 119 */ 116 public class CorrelateGpxWithImages extends AbstractAction {120 public class CorrelateGpxWithImages extends AbstractAction implements Destroyable { 117 121 118 private static final List<GpxData> loadedGpxData = new ArrayList<>(); 122 private static MutableComboBoxModel<GpxDataWrapper> gpxModel; 123 private static boolean forceTags; 119 124 120 125 private final transient GeoImageLayer yLayer; 121 126 private transient GpxTimezone timezone; 122 127 private transient GpxTimeOffset delta; 123 private static boolean forceTags;124 128 125 129 /** 126 130 * Constructs a new {@code CorrelateGpxWithImages} action. … … 130 134 super(tr("Correlate to GPX")); 131 135 new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true); 132 136 this.yLayer = layer; 133 MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());134 137 } 135 138 136 139 private final class SyncDialogWindowListener extends WindowAdapter { … … 233 236 } 234 237 235 238 private static class GpxDataWrapper { 236 private finalString name;239 private String name; 237 240 private final GpxData data; 238 241 private final File file; 239 242 … … 243 246 this.file = file; 244 247 } 245 248 249 void setName(String name) { 250 this.name = name; 251 forEachLayer(CorrelateGpxWithImages::repaintCombobox); 252 } 253 246 254 @Override 247 255 public String toString() { 248 256 return name; … … 249 257 } 250 258 } 251 259 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 252 271 private ExtendedDialog syncDialog; 253 private MutableComboBoxModel<GpxDataWrapper> gpxModel;254 272 private JPanel outerPanel; 255 273 private JosmComboBox<GpxDataWrapper> cbGpx; 256 274 private JosmTextField tfTimezone; … … 280 298 281 299 try { 282 300 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); 298 302 GpxData data = null; 299 303 try (InputStream iStream = Compression.getUncompressedFileInputStream(sel)) { 300 304 IGpxReader reader = gpxFilter.accept(sel) ? new GpxReader(iStream) : new NmeaReader(iStream); … … 322 326 return; 323 327 } 324 328 325 loadedGpxData.add(data);326 if (gpxModel.getElementAt(0).file == null) {327 gpxModel.removeElementAt(0);328 }329 329 GpxDataWrapper elem = new GpxDataWrapper(sel.getName(), data, sel); 330 330 gpxModel.addElement(elem); 331 331 gpxModel.setSelectedItem(elem); 332 statusBarUpdater.matchAndUpdateStatusBar(); 332 333 } finally { 333 334 outerPanel.setCursor(Cursor.getDefaultCursor()); 334 335 } … … 527 528 528 529 forceTags = cForce.isSelected(); // This setting is not supposed to be saved permanently 529 530 530 statusBarUpdater. updateStatusBar();531 statusBarUpdater.matchAndUpdateStatusBar(); 531 532 yLayer.updateBufferAndRepaint(); 532 533 } 533 534 } … … 764 765 isOk = true; 765 766 766 767 } 767 statusBarUpdater. updateStatusBar();768 statusBarUpdater.matchAndUpdateStatusBar(); 768 769 yLayer.updateBufferAndRepaint(); 769 770 } 770 771 … … 787 788 } 788 789 } 789 790 790 private class GpxLayerAddedListener implements LayerChangeListener {791 private static class GpxLayerAddedListener implements LayerChangeListener { 791 792 @Override 792 793 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); 803 803 } 804 804 } 805 805 … … 814 814 } 815 815 } 816 816 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 817 831 @Override 818 832 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 } 828 845 } 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); 835 851 } 852 MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener()); 836 853 } 837 854 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 845 855 JPanel panelCb = new JPanel(); 846 856 847 857 panelCb.add(new JLabel(tr("GPX track: "))); … … 1007 1017 cbExifImg.addItemListener(statusBarUpdaterWithRepaint); 1008 1018 cbTaggedImg.addItemListener(statusBarUpdaterWithRepaint); 1009 1019 1010 statusBarUpdater. updateStatusBar();1020 statusBarUpdater.matchAndUpdateStatusBar(); 1011 1021 yLayer.updateBufferAndRepaint(); 1012 1022 1013 1023 outerPanel = new JPanel(new BorderLayout()); … … 1014 1024 outerPanel.add(statusBar, BorderLayout.PAGE_END); 1015 1025 1016 1026 if (!GraphicsEnvironment.isHeadless()) { 1027 forEachLayer(CorrelateGpxWithImages::closeDialog); 1017 1028 syncDialog = new ExtendedDialog( 1018 1029 MainApplication.getMainFrame(), 1019 1030 tr("Correlate images with GPX track"), … … 1031 1042 } 1032 1043 } 1033 1044 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 1034 1059 private final transient StatusBarUpdater statusBarUpdater = new StatusBarUpdater(false); 1035 1060 private final transient StatusBarUpdater statusBarUpdaterWithRepaint = new StatusBarUpdater(true); 1036 1061 … … 1043 1068 1044 1069 @Override 1045 1070 public void insertUpdate(DocumentEvent ev) { 1046 updateStatusBar();1071 matchAndUpdateStatusBar(); 1047 1072 } 1048 1073 1049 1074 @Override 1050 1075 public void removeUpdate(DocumentEvent ev) { 1051 updateStatusBar();1076 matchAndUpdateStatusBar(); 1052 1077 } 1053 1078 1054 1079 @Override … … 1058 1083 1059 1084 @Override 1060 1085 public void itemStateChanged(ItemEvent e) { 1061 updateStatusBar();1086 matchAndUpdateStatusBar(); 1062 1087 } 1063 1088 1064 1089 @Override 1065 1090 public void actionPerformed(ActionEvent e) { 1066 updateStatusBar();1091 matchAndUpdateStatusBar(); 1067 1092 } 1068 1093 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 } 1073 1100 } 1074 1101 } 1075 1102 1076 private String statusText() {1103 private String matchAndGetStatusText() { 1077 1104 try { 1078 1105 timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim()); 1079 1106 delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim()); … … 1196 1223 lblMatches.setText(statusBarText.getText() + "<br>" + trn("(Time difference of {0} day)", 1197 1224 "Time difference of {0} days", Math.abs(dayOffset), Math.abs(dayOffset))); 1198 1225 1199 statusBarUpdater. updateStatusBar();1226 statusBarUpdater.matchAndUpdateStatusBar(); 1200 1227 yLayer.updateBufferAndRepaint(); 1201 1228 } 1202 1229 } … … 1250 1277 static class NoGpxTimestamps extends Exception { 1251 1278 } 1252 1279 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 1253 1295 /** 1254 1296 * Tries to auto-guess the timezone and offset. 1255 1297 * … … 1315 1357 tfTimezone.getDocument().addDocumentListener(statusBarUpdater); 1316 1358 tfOffset.getDocument().addDocumentListener(statusBarUpdater); 1317 1359 1318 statusBarUpdater. updateStatusBar();1360 statusBarUpdater.matchAndUpdateStatusBar(); 1319 1361 yLayer.updateBufferAndRepaint(); 1320 1362 } 1321 1363 } … … 1343 1385 private GpxDataWrapper selectedGPX(boolean complain) { 1344 1386 Object item = gpxModel.getSelectedItem(); 1345 1387 1346 if (item == null || ((GpxDataWrapper) item). file== null) {1388 if (item == null || ((GpxDataWrapper) item).data == null) { 1347 1389 if (complain) { 1348 1390 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), tr("You should select a GPX track"), 1349 1391 tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE); … … 1353 1395 return (GpxDataWrapper) item; 1354 1396 } 1355 1397 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 1356 1408 }
