]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Version 12, December 2010
authoractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 09:49:41 +0000 (10:49 +0100)
committeractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 09:49:41 +0000 (10:49 +0100)
132 files changed:
tim/prune/App.java
tim/prune/FunctionLibrary.java
tim/prune/GpsPruner.java
tim/prune/config/Config.java
tim/prune/copyright.txt
tim/prune/correlate/AudioCorrelator.java [new file with mode: 0644]
tim/prune/correlate/AudioTimestampSelector.java [new file with mode: 0644]
tim/prune/correlate/CardStack.java [new file with mode: 0644]
tim/prune/correlate/Correlator.java [new file with mode: 0644]
tim/prune/correlate/MediaPreviewTableModel.java [moved from tim/prune/correlate/PhotoPreviewTableModel.java with 74% similarity]
tim/prune/correlate/MediaPreviewTableRow.java [moved from tim/prune/correlate/PhotoPreviewTableRow.java with 70% similarity]
tim/prune/correlate/MediaSelectionTableModel.java [new file with mode: 0644]
tim/prune/correlate/MediaSelectionTableRow.java [new file with mode: 0644]
tim/prune/correlate/OptionsChangedListener.java
tim/prune/correlate/PhotoCorrelator.java
tim/prune/correlate/PhotoSelectionTableModel.java [deleted file]
tim/prune/correlate/PhotoSelectionTableRow.java [deleted file]
tim/prune/correlate/PointMediaPair.java [moved from tim/prune/correlate/PointPair.java with 63% similarity]
tim/prune/data/AudioFile.java [new file with mode: 0644]
tim/prune/data/AudioList.java [new file with mode: 0644]
tim/prune/data/DataPoint.java
tim/prune/data/Field.java
tim/prune/data/FieldList.java
tim/prune/data/FieldType.java [deleted file]
tim/prune/data/MediaFile.java [new file with mode: 0644]
tim/prune/data/MediaList.java [new file with mode: 0644]
tim/prune/data/Photo.java
tim/prune/data/PhotoList.java
tim/prune/data/Selection.java
tim/prune/data/Timestamp.java
tim/prune/data/Track.java
tim/prune/data/TrackInfo.java
tim/prune/function/AboutScreen.java
tim/prune/function/AddMapSourceDialog.java
tim/prune/function/ConnectToPointFunction.java [new file with mode: 0644]
tim/prune/function/DisconnectAudioFunction.java [new file with mode: 0644]
tim/prune/function/DisconnectPhotoFunction.java [new file with mode: 0644]
tim/prune/function/DownloadOsmFunction.java [new file with mode: 0644]
tim/prune/function/GetWikipediaFunction.java [new file with mode: 0644]
tim/prune/function/GetWikipediaXmlHandler.java [new file with mode: 0644]
tim/prune/function/PhotoPopupFunction.java [new file with mode: 0644]
tim/prune/function/PlayAudioFunction.java [new file with mode: 0644]
tim/prune/function/RemoveAudioFunction.java [new file with mode: 0644]
tim/prune/function/RemovePhotoFunction.java [new file with mode: 0644]
tim/prune/function/SaveConfig.java
tim/prune/function/SearchWikipediaNames.java [new file with mode: 0644]
tim/prune/function/SetLanguage.java
tim/prune/function/SetLineWidth.java [new file with mode: 0644]
tim/prune/function/StopAudioFunction.java [new file with mode: 0644]
tim/prune/function/compress/ClosePointsAlgorithm.java
tim/prune/function/compress/CompressTrackFunction.java
tim/prune/function/compress/DuplicatePointAlgorithm.java
tim/prune/function/compress/SingletonAlgorithm.java
tim/prune/function/compress/WackyPointAlgorithm.java
tim/prune/function/distance/DistanceFunction.java
tim/prune/function/gpsies/GenericDownloaderFunction.java [new file with mode: 0644]
tim/prune/function/gpsies/GetGpsiesFunction.java
tim/prune/function/gpsies/GpsiesTrack.java
tim/prune/function/gpsies/GpsiesXmlHandler.java
tim/prune/function/gpsies/TrackListModel.java
tim/prune/function/gpsies/UploadGpsiesFunction.java
tim/prune/gui/AudioListener.java [new file with mode: 0644]
tim/prune/gui/DetailsDisplay.java
tim/prune/gui/DialogCloser.java [new file with mode: 0644]
tim/prune/gui/IconManager.java
tim/prune/gui/MediaListModel.java [new file with mode: 0644]
tim/prune/gui/MenuManager.java
tim/prune/gui/PhotoListModel.java [deleted file]
tim/prune/gui/PhotoThumbnail.java
tim/prune/gui/SelectorDisplay.java
tim/prune/gui/images/play_audio.gif [new file with mode: 0644]
tim/prune/gui/images/show_details_icon.gif [new file with mode: 0644]
tim/prune/gui/images/stop_audio.gif [new file with mode: 0644]
tim/prune/gui/map/DiskTileCacher.java
tim/prune/gui/map/MapCanvas.java
tim/prune/gui/map/MapSource.java
tim/prune/jpeg/ExifGateway.java
tim/prune/jpeg/ExternalExifLibrary.java
tim/prune/jpeg/drew/ExifReader.java
tim/prune/jpeg/drew/JpegSegmentReader.java
tim/prune/lang/prune-texts_af.properties
tim/prune/lang/prune-texts_cz.properties
tim/prune/lang/prune-texts_de.properties
tim/prune/lang/prune-texts_de_CH.properties
tim/prune/lang/prune-texts_en.properties
tim/prune/lang/prune-texts_es.properties
tim/prune/lang/prune-texts_fa.properties
tim/prune/lang/prune-texts_fr.properties
tim/prune/lang/prune-texts_hu.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_in.properties
tim/prune/lang/prune-texts_it.properties
tim/prune/lang/prune-texts_ja.properties
tim/prune/lang/prune-texts_ko.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_nl.properties
tim/prune/lang/prune-texts_pl.properties
tim/prune/lang/prune-texts_pt.properties
tim/prune/lang/prune-texts_ro.properties
tim/prune/lang/prune-texts_tr.properties
tim/prune/lang/prune-texts_zh.properties
tim/prune/load/AudioFileFilter.java [new file with mode: 0644]
tim/prune/load/AudioLoader.java [new file with mode: 0644]
tim/prune/load/GenericFileFilter.java
tim/prune/load/JpegFileFilter.java [new file with mode: 0644]
tim/prune/load/JpegLoader.java
tim/prune/load/MediaHelper.java [new file with mode: 0644]
tim/prune/load/MediaSorter.java [moved from tim/prune/load/PhotoSorter.java with 75% similarity]
tim/prune/load/TrackNameList.java
tim/prune/load/xml/GpxHandler.java
tim/prune/load/xml/GpxTag.java [new file with mode: 0644]
tim/prune/load/xml/GzipFileLoader.java
tim/prune/load/xml/XmlFileLoader.java
tim/prune/load/xml/XmlHandler.java
tim/prune/load/xml/ZipFileLoader.java
tim/prune/readme.txt
tim/prune/save/FileSaver.java
tim/prune/save/GpsSaver.java
tim/prune/save/GpxExporter.java
tim/prune/save/KmlExporter.java
tim/prune/save/PointTypeSelector.java
tim/prune/save/PovExporter.java
tim/prune/save/SvgExporter.java
tim/prune/save/SvgFragment.java
tim/prune/undo/UndoConnectMedia.java [new file with mode: 0644]
tim/prune/undo/UndoConnectPhoto.java [deleted file]
tim/prune/undo/UndoCorrelateAudios.java [new file with mode: 0644]
tim/prune/undo/UndoCorrelatePhotos.java
tim/prune/undo/UndoDeleteAudio.java [new file with mode: 0644]
tim/prune/undo/UndoDeletePhoto.java
tim/prune/undo/UndoDisconnectMedia.java [new file with mode: 0644]
tim/prune/undo/UndoDisconnectPhoto.java [deleted file]
tim/prune/undo/UndoLoad.java
tim/prune/undo/UndoLoadAudios.java [new file with mode: 0644]

index 88cafea3cd1901e23949ccba07579f3bdeabe8d4..6fab5833af1c56ecddb5b506a9c4b03a2de31bbd 100644 (file)
@@ -10,10 +10,12 @@ import javax.swing.JFrame;
 import javax.swing.JOptionPane;
 
 import tim.prune.data.Altitude;
+import tim.prune.data.AudioFile;
 import tim.prune.data.Checker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.LatLonRectangle;
+import tim.prune.data.MediaFile;
 import tim.prune.data.NumberUtils;
 import tim.prune.data.Photo;
 import tim.prune.data.PhotoList;
@@ -31,6 +33,7 @@ import tim.prune.gui.UndoManager;
 import tim.prune.gui.Viewport;
 import tim.prune.load.FileLoader;
 import tim.prune.load.JpegLoader;
+import tim.prune.load.MediaHelper;
 import tim.prune.load.TrackNameList;
 import tim.prune.save.ExifSaver;
 import tim.prune.save.FileSaver;
@@ -48,7 +51,7 @@ public class App
        private TrackInfo _trackInfo = null;
        private int _lastSavePosition = 0;
        private MenuManager _menuManager = null;
-       private SidebarController __sidebarController = null;
+       private SidebarController _sidebarController = null;
        private FileLoader _fileLoader = null;
        private JpegLoader _jpegLoader = null;
        private FileSaver _fileSaver = null;
@@ -638,7 +641,7 @@ public class App
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
                Altitude.Format inAltFormat, SourceInfo inSourceInfo)
        {
-               informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo, null);
+               informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo, null, null);
        }
 
        /**
@@ -651,6 +654,24 @@ public class App
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
                Altitude.Format inAltFormat, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
+       {
+               // no link array given
+               informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo,
+                       inTrackNameList, null);
+       }
+
+       /**
+        * Receive loaded data and determine whether to filter on tracks or not
+        * @param inFieldArray array of fields
+        * @param inDataArray array of data
+        * @param inAltFormat altitude format
+        * @param inSourceInfo information about the source of the data
+        * @param inTrackNameList information about the track names
+        * @param inLinkArray array of links to photo/audio files
+        */
+       public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
+               Altitude.Format inAltFormat, SourceInfo inSourceInfo,
+               TrackNameList inTrackNameList, String[] inLinkArray)
        {
                // Check whether loaded array can be properly parsed into a Track
                Track loadedTrack = new Track();
@@ -667,6 +688,22 @@ public class App
                        JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
                                I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
                }
+               // Attach photos and/or audio files to points
+               if (inLinkArray != null)
+               {
+                       for (int i=0; i<inLinkArray.length; i++)
+                       {
+                               if (inLinkArray[i] != null)
+                               {
+                                       MediaFile mf = MediaHelper.createMediaFile(inLinkArray[i]);
+                                       if (mf != null) {
+                                               loadedTrack.getPoint(i).attachMedia(mf);
+                                               mf.setOriginalStatus(MediaFile.Status.TAGGED);
+                                               mf.setCurrentStatus(MediaFile.Status.TAGGED);
+                                       }
+                               }
+                       }
+               }
                // Look at TrackNameList, decide whether to filter or not
                if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
                {
@@ -680,6 +717,7 @@ public class App
                }
        }
 
+
        /**
         * Receive loaded data and optionally merge with current Track
         * @param inLoadedTrack loaded track
@@ -705,8 +743,13 @@ public class App
                        if (answer == JOptionPane.YES_OPTION)
                        {
                                // append data to current Track
-                               _undoStack.add(new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints()));
+                               UndoLoad undo = new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints());
+                               undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+                               _undoStack.add(undo);
                                _track.combine(inLoadedTrack);
+                               // Add photos and audios (if any in loaded track) to list(s)
+                               MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
+                               MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
                                // set source information
                                inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
                                _trackInfo.getFileInfo().addSource(inSourceInfo);
@@ -718,26 +761,35 @@ public class App
                                if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
                                        photos = _trackInfo.getPhotoList().cloneList();
                                }
-                               _undoStack.add(new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos));
+                               UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
+                               undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+                               _undoStack.add(undo);
                                _lastSavePosition = _undoStack.size();
                                _trackInfo.getSelection().clearAll();
                                _track.load(inLoadedTrack);
                                inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
                                _trackInfo.getFileInfo().replaceSource(inSourceInfo);
-                               if (photos != null) {
-                                       _trackInfo.getPhotoList().removeCorrelatedPhotos();
-                               }
+                               _trackInfo.getPhotoList().removeCorrelatedPhotos();
+                               _trackInfo.getAudioList().removeCorrelatedAudios();
+                               // Add photos and audios (if any in loaded track) to list(s)
+                               MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
+                               MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
                        }
                }
                else
                {
                        // Currently no data held, so transfer received data
-                       _undoStack.add(new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null));
+                       UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
+                       undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+                       _undoStack.add(undo);
                        _lastSavePosition = _undoStack.size();
                        _trackInfo.getSelection().clearAll();
                        _track.load(inLoadedTrack);
                        inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
                        _trackInfo.getFileInfo().addSource(inSourceInfo);
+                       // Add photos and audios (if any in loaded track) to list(s)
+                       MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
+                       MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
                }
                UpdateMessageBroker.informSubscribers();
                // Update status bar
@@ -805,92 +857,7 @@ public class App
                        // MAYBE: Improve message when photo(s) fail to load (eg already added)
                        UpdateMessageBroker.informSubscribers();
                        // update menu
-                       _menuManager.informFileLoaded();
-               }
-       }
-
-
-       /**
-        * Connect the current photo to the current point
-        */
-       public void connectPhotoToPoint()
-       {
-               Photo photo = _trackInfo.getCurrentPhoto();
-               DataPoint point = _trackInfo.getCurrentPoint();
-               if (photo != null && point != null)
-               {
-                       if (point.getPhoto() == null)
-                       {
-                               // point doesn't currently have a photo, so just connect it
-                               _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
-                               photo.setDataPoint(point);
-                               point.setPhoto(photo);
-                               UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
-                               UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
-                       }
-               }
-       }
-
-
-       /**
-        * Disconnect the current photo from its point
-        */
-       public void disconnectPhotoFromPoint()
-       {
-               Photo photo = _trackInfo.getCurrentPhoto();
-               if (photo != null && photo.getDataPoint() != null)
-               {
-                       DataPoint point = photo.getDataPoint();
-                       _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName()));
-                       // disconnect
-                       photo.setDataPoint(null);
-                       point.setPhoto(null);
-                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
-                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.disconnect"));
-               }
-       }
-
-
-       /**
-        * Remove the current photo, if any
-        */
-       public void deleteCurrentPhoto()
-       {
-               // Delete the current photo, and optionally its point too, keeping undo information
-               Photo currentPhoto = _trackInfo.getCurrentPhoto();
-               if (currentPhoto != null)
-               {
-                       // Photo is selected, see if it has a point or not
-                       boolean photoDeleted = false;
-                       UndoDeletePhoto undoAction = null;
-                       if (currentPhoto.getDataPoint() == null)
-                       {
-                               // no point attached, so just delete photo
-                               undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
-                                       null, -1);
-                               photoDeleted = _trackInfo.deleteCurrentPhoto(false);
-                       }
-                       else
-                       {
-                               // point is attached, so need to confirm point deletion
-                               undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
-                                       currentPhoto.getDataPoint(), _trackInfo.getTrack().getPointIndex(currentPhoto.getDataPoint()));
-                               int response = JOptionPane.showConfirmDialog(_frame,
-                                       I18nManager.getText("dialog.deletephoto.deletepoint"),
-                                       I18nManager.getText("dialog.deletephoto.title"),
-                                       JOptionPane.YES_NO_CANCEL_OPTION);
-                               boolean deletePointToo = (response == JOptionPane.YES_OPTION);
-                               // Cancel delete if cancel pressed or dialog closed
-                               if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION)
-                               {
-                                       photoDeleted = _trackInfo.deleteCurrentPhoto(deletePointToo);
-                               }
-                       }
-                       // Add undo information to stack if necessary
-                       if (photoDeleted)
-                       {
-                               _undoStack.add(undoAction);
-                       }
+                       if (numPointsAdded > 0) _menuManager.informFileLoaded();
                }
        }
 
@@ -1057,7 +1024,7 @@ public class App
         */
        public void setSidebarController(SidebarController inController)
        {
-               __sidebarController = inController;
+               _sidebarController = inController;
        }
 
        /**
@@ -1065,6 +1032,6 @@ public class App
         */
        public void toggleSidebars()
        {
-               __sidebarController.toggle();
+               _sidebarController.toggle();
        }
 }
index 92947f01a4885ddda15d0ef9d7934057c615359b..93532b5cfa4dd5f32cfb5d54c4621c01e9969330 100644 (file)
@@ -1,5 +1,6 @@
 package tim.prune;
 
+import tim.prune.correlate.AudioCorrelator;
 import tim.prune.correlate.PhotoCorrelator;
 import tim.prune.function.*;
 import tim.prune.function.charts.Charter;
@@ -9,6 +10,7 @@ import tim.prune.function.edit.PointNameEditor;
 import tim.prune.function.gpsies.GetGpsiesFunction;
 import tim.prune.function.gpsies.UploadGpsiesFunction;
 import tim.prune.function.srtm.LookupSrtmFunction;
+import tim.prune.load.AudioLoader;
 import tim.prune.load.GpsLoader;
 import tim.prune.save.GpsSaver;
 import tim.prune.save.GpxExporter;
@@ -33,6 +35,9 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_REARRANGE_PHOTOS = null;
        public static GenericFunction FUNCTION_COMPRESS = null;
        public static GenericFunction FUNCTION_LOOKUP_SRTM = null;
+       public static GenericFunction FUNCTION_LOOKUP_WIKIPEDIA = null;
+       public static GenericFunction FUNCTION_SEARCH_WIKIPEDIA = null;
+       public static GenericFunction FUNCTION_DOWNLOAD_OSM = null;
        public static GenericFunction FUNCTION_ADD_TIME_OFFSET  = null;
        public static GenericFunction FUNCTION_ADD_ALTITUDE_OFFSET  = null;
        public static GenericFunction FUNCTION_CONVERT_NAMES_TO_TIMES  = null;
@@ -40,9 +45,14 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_PASTE_COORDINATES = null;
        public static GenericFunction FUNCTION_FIND_WAYPOINT = null;
        public static GenericFunction FUNCTION_DUPLICATE_POINT = null;
+       public static GenericFunction FUNCTION_CONNECT_TO_POINT = null;
+       public static GenericFunction FUNCTION_DISCONNECT_PHOTO = null;
+       public static GenericFunction FUNCTION_REMOVE_PHOTO = null;
+       public static GenericFunction FUNCTION_DISCONNECT_AUDIO = null;
        public static GenericFunction FUNCTION_CORRELATE_PHOTOS = null;
        public static GenericFunction FUNCTION_ROTATE_PHOTO_LEFT = null;
        public static GenericFunction FUNCTION_ROTATE_PHOTO_RIGHT = null;
+       public static GenericFunction FUNCTION_PHOTO_POPUP = null;
        public static GenericFunction FUNCTION_IGNORE_EXIF_THUMB = null;
        public static GenericFunction FUNCTION_CHARTS = null;
        public static GenericFunction FUNCTION_3D     = null;
@@ -50,11 +60,17 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_FULL_RANGE_DETAILS = null;
        public static GenericFunction FUNCTION_GET_GPSIES = null;
        public static GenericFunction FUNCTION_UPLOAD_GPSIES = null;
+       public static GenericFunction FUNCTION_LOAD_AUDIO = null;
+       public static GenericFunction FUNCTION_REMOVE_AUDIO = null;
+       public static GenericFunction FUNCTION_CORRELATE_AUDIOS = null;
+       public static GenericFunction FUNCTION_PLAY_AUDIO = null;
+       public static GenericFunction FUNCTION_STOP_AUDIO = null;
        public static GenericFunction FUNCTION_SET_MAP_BG = null;
        public static GenericFunction FUNCTION_SET_DISK_CACHE = null;
        public static GenericFunction FUNCTION_SET_PATHS  = null;
        public static GenericFunction FUNCTION_SET_KMZ_IMAGE_SIZE = null;
        public static GenericFunction FUNCTION_SET_COLOURS = null;
+       public static GenericFunction FUNCTION_SET_LINE_WIDTH = null;
        public static GenericFunction FUNCTION_SET_LANGUAGE = null;
        public static GenericFunction FUNCTION_HELP   = null;
        public static GenericFunction FUNCTION_SHOW_KEYS = null;
@@ -80,6 +96,9 @@ public abstract class FunctionLibrary
                FUNCTION_REARRANGE_PHOTOS = new RearrangePhotosFunction(inApp);
                FUNCTION_COMPRESS = new CompressTrackFunction(inApp);
                FUNCTION_LOOKUP_SRTM = new LookupSrtmFunction(inApp);
+               FUNCTION_LOOKUP_WIKIPEDIA = new GetWikipediaFunction(inApp);
+               FUNCTION_SEARCH_WIKIPEDIA = new SearchWikipediaNames(inApp);
+               FUNCTION_DOWNLOAD_OSM = new DownloadOsmFunction(inApp);
                FUNCTION_ADD_TIME_OFFSET = new AddTimeOffset(inApp);
                FUNCTION_ADD_ALTITUDE_OFFSET = new AddAltitudeOffset(inApp);
                FUNCTION_CONVERT_NAMES_TO_TIMES = new ConvertNamesToTimes(inApp);
@@ -87,9 +106,13 @@ public abstract class FunctionLibrary
                FUNCTION_PASTE_COORDINATES = new PasteCoordinates(inApp);
                FUNCTION_FIND_WAYPOINT = new FindWaypoint(inApp);
                FUNCTION_DUPLICATE_POINT = new DuplicatePoint(inApp);
+               FUNCTION_CONNECT_TO_POINT = new ConnectToPointFunction(inApp);
+               FUNCTION_DISCONNECT_PHOTO = new DisconnectPhotoFunction(inApp);
+               FUNCTION_REMOVE_PHOTO = new RemovePhotoFunction(inApp);
                FUNCTION_CORRELATE_PHOTOS = new PhotoCorrelator(inApp);
                FUNCTION_ROTATE_PHOTO_LEFT = new RotatePhoto(inApp, false);
                FUNCTION_ROTATE_PHOTO_RIGHT = new RotatePhoto(inApp, true);
+               FUNCTION_PHOTO_POPUP = new PhotoPopupFunction(inApp);
                FUNCTION_IGNORE_EXIF_THUMB = new IgnoreExifThumb(inApp);
                FUNCTION_CHARTS = new Charter(inApp);
                FUNCTION_3D     = new ShowThreeDFunction(inApp);
@@ -97,11 +120,18 @@ public abstract class FunctionLibrary
                FUNCTION_FULL_RANGE_DETAILS = new FullRangeDetails(inApp);
                FUNCTION_GET_GPSIES = new GetGpsiesFunction(inApp);
                FUNCTION_UPLOAD_GPSIES = new UploadGpsiesFunction(inApp);
+               FUNCTION_LOAD_AUDIO = new AudioLoader(inApp);
+               FUNCTION_REMOVE_AUDIO = new RemoveAudioFunction(inApp);
+               FUNCTION_CORRELATE_AUDIOS = new AudioCorrelator(inApp);
+               FUNCTION_PLAY_AUDIO = new PlayAudioFunction(inApp);
+               FUNCTION_STOP_AUDIO = new StopAudioFunction(inApp);
+               FUNCTION_DISCONNECT_AUDIO = new DisconnectAudioFunction(inApp);
                FUNCTION_SET_MAP_BG = new SetMapBgFunction(inApp);
                FUNCTION_SET_DISK_CACHE = new DiskCacheConfig(inApp);
                FUNCTION_SET_PATHS = new SetPathsFunction(inApp);
                FUNCTION_SET_KMZ_IMAGE_SIZE = new SetKmzImageSize(inApp);
                FUNCTION_SET_COLOURS = new SetColours(inApp);
+               FUNCTION_SET_LINE_WIDTH = new SetLineWidth(inApp);
                FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
                FUNCTION_HELP   = new HelpScreen(inApp);
                FUNCTION_SHOW_KEYS = new ShowKeysScreen(inApp);
index 124240455681dd9b04289ce075f2a4e035856407..217d7d429ceee7036d72058d6987527e63b27758 100644 (file)
@@ -35,9 +35,9 @@ import tim.prune.gui.profile.ProfileChart;
 public class GpsPruner
 {
        /** Version number of application, used in about screen and for version check */
-       public static final String VERSION_NUMBER = "11.2";
+       public static final String VERSION_NUMBER = "12";
        /** Build number, just used for about screen */
-       public static final String BUILD_NUMBER = "205a";
+       public static final String BUILD_NUMBER = "223";
        /** Static reference to App object */
        private static App APP = null;
 
@@ -67,12 +67,7 @@ public class GpsPruner
                for (int i=0; i<args.length; i++)
                {
                        String arg = args[i];
-                       if (arg.startsWith("--locale="))
-                       {
-                               localeCode = arg.substring(9);
-                               locale = getLanguage(localeCode);
-                       }
-                       else if (arg.startsWith("--lang="))
+                       if (arg.startsWith("--lang="))
                        {
                                localeCode = arg.substring(7);
                                locale = getLanguage(localeCode);
@@ -104,7 +99,7 @@ public class GpsPruner
                if (showUsage) {
                        System.out.println("Possible parameters:"
                                + "\n   --configfile=<file> used to specify a configuration file"
-                               + "\n   --lang=<code> or --locale=<code>  used to specify language"
+                               + "\n   --lang=<code>       used to specify language code such as DE"
                                + "\n   --langfile=<file>   used to specify an alternative language file\n");
                }
                // Initialise configuration if selected
@@ -164,7 +159,7 @@ public class GpsPruner
                {
                        return new Locale(inString);
                }
-               else if (inString.length() == 5)
+               else if (inString.length() == 5 && inString.charAt(2) == '_')
                {
                        return new Locale(inString.substring(0, 2), inString.substring(3));
                }
index 14231383fc03d860b4eea332575837e6cede4a0d..0873b0f505be1ded6b2985b6da0509631890964e 100644 (file)
@@ -59,6 +59,8 @@ public abstract class Config
        public static final String KEY_EXIFTOOL_PATH = "prune.exiftoolpath";
        /** Key for colour scheme */
        public static final String KEY_COLOUR_SCHEME = "prune.colourscheme";
+       /** Key for line width used for drawing */
+       public static final String KEY_LINE_WIDTH = "prune.linewidth";
        /** Key for kml track colour */
        public static final String KEY_KML_TRACK_COLOUR = "prune.kmltrackcolour";
 
index a24ac608ab7c99cb329f8a6ad0a2ad3f57065fea..c25b77ef923fe6b703213fad87bf7b5d127da7f2 100644 (file)
@@ -1,5 +1,5 @@
 The source code of Prune is copyright 2006-2010 activityworkshop.net
-and distributed under the terms of the Gnu GPL version 2.
+and is distributed under the terms of the Gnu GPL version 2.
 
 Portions of the package jpeg.drew (if included in this package) were taken
 from Drew Noakes' "Metadata extractor" v2.3.1 which is
diff --git a/tim/prune/correlate/AudioCorrelator.java b/tim/prune/correlate/AudioCorrelator.java
new file mode 100644 (file)
index 0000000..57ecb7f
--- /dev/null
@@ -0,0 +1,279 @@
+package tim.prune.correlate;
+
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.AudioFile;
+import tim.prune.data.AudioList;
+import tim.prune.data.DataPoint;
+import tim.prune.data.MediaFile;
+import tim.prune.data.MediaList;
+import tim.prune.data.TimeDifference;
+import tim.prune.data.Timestamp;
+import tim.prune.undo.UndoCorrelateAudios;
+
+/**
+ * Class to manage the automatic correlation of audio files to points
+ * which is very similar to the PhotoCorrelator apart from the clip lengths
+ */
+public class AudioCorrelator extends Correlator
+{
+       private AudioTimestampSelector _fileTimesSelector = null, _correlTimesSelector = null;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public AudioCorrelator(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.correlateaudios";
+       }
+
+       /** @return type key */
+       protected String getMediaTypeKey() {
+               return "audio";
+       }
+
+       /** @return photo list*/
+       protected MediaList getMediaList() {
+               return _app.getTrackInfo().getAudioList();
+       }
+
+
+       /**
+        * @return first gui panel including timestamp specification (beginning, middle, end)
+        */
+       protected JPanel makeFirstPanel()
+       {
+               // First panel for timestamp stuff
+               JPanel card1 = new JPanel();
+               card1.setLayout(new FlowLayout(FlowLayout.CENTER));
+               JPanel grid1 = new JPanel();
+               grid1.setLayout(new GridLayout(0, 1));
+               _fileTimesSelector = new AudioTimestampSelector("dialog.correlate.filetimes", "dialog.correlate.filetimes2");
+               grid1.add(_fileTimesSelector);
+               _correlTimesSelector = new AudioTimestampSelector("dialog.correlate.correltimes", null);
+               grid1.add(_correlTimesSelector);
+               card1.add(grid1);
+               return card1;
+       }
+
+
+       /**
+        * @return array of boolean flags denoting availability of cards
+        */
+       protected boolean[] getCardEnabledFlags()
+       {
+               boolean[] cards = super.getCardEnabledFlags();
+               cards[0] = getAudioLengthAvailability(_app.getTrackInfo().getAudioList());
+               return cards;
+       }
+
+       /**
+        * @param inAudios AudioList object
+        * @return true if there are any audio lengths available
+        */
+       private static boolean getAudioLengthAvailability(AudioList inAudios)
+       {
+               for (int i=0; i<inAudios.getNumMedia(); i++)
+               {
+                       AudioFile a = inAudios.getAudio(i);
+                       if (a.getLengthInSeconds() > 0) {return true;}
+               }
+               return false;
+       }
+
+       /**
+        * Create a preview of the correlate action using the selected time difference
+        * @param inTimeDiff TimeDifference to use for preview
+        * @param inShowWarning true to show warning if all points out of range
+        */
+       protected void createPreview(TimeDifference inTimeDiff, boolean inShowWarning)
+       {
+               TimeDifference timeLimit = parseTimeLimit();
+               double angDistLimit = parseDistanceLimit();
+               MediaPreviewTableModel model = new MediaPreviewTableModel("dialog.correlate.select.audioname");
+               AudioList audios = _app.getTrackInfo().getAudioList();
+               // Loop through audios deciding whether to set correlate flag or not
+               int numAudios = audios.getNumAudios();
+               for (int i=0; i<numAudios; i++)
+               {
+                       AudioFile audio = audios.getAudio(i);
+                       PointMediaPair pair = getPointPairForMedia(_app.getTrackInfo().getTrack(), audio, inTimeDiff);
+                       MediaPreviewTableRow row = new MediaPreviewTableRow(pair);
+                       // Don't try to correlate audios which don't have points either side
+                       boolean correlateAudio = pair.isValid();
+                       // Don't select audios which already have a point
+                       if (audio.getCurrentStatus() != AudioFile.Status.NOT_CONNECTED) {correlateAudio = false;}
+                       // Check time limits, distance limits
+                       if (timeLimit != null && correlateAudio) {
+                               long numSecs = pair.getMinSeconds();
+                               correlateAudio = (numSecs <= timeLimit.getTotalSeconds());
+                       }
+                       if (angDistLimit > 0.0 && correlateAudio)
+                       {
+                               final double angDistPair = DataPoint.calculateRadiansBetween(pair.getPointBefore(), pair.getPointAfter());
+                               double frac = pair.getFraction();
+                               if (frac > 0.5) {frac = 1 - frac;}
+                               final double angDistPhoto = angDistPair * frac;
+                               correlateAudio = (angDistPhoto < angDistLimit);
+                       }
+                       // Don't select audios which are already correlated to the same point
+                       if (pair.getSecondsBefore() == 0L && pair.getPointBefore().isDuplicate(audio.getDataPoint())) {
+                               correlateAudio = false;
+                       }
+                       row.setCorrelateFlag(correlateAudio);
+                       model.addRow(row);
+               }
+               _previewTable.setModel(model);
+               // Set distance units
+               model.setDistanceUnits(getSelectedDistanceUnits());
+               // Set column widths
+               _previewTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+               final int[] colWidths = {150, 160, 100, 100, 50};
+               for (int i=0; i<model.getColumnCount(); i++) {
+                       _previewTable.getColumnModel().getColumn(i).setPreferredWidth(colWidths[i]);
+               }
+               // check if any audios found
+               _okButton.setEnabled(model.hasAnySelected());
+               if (inShowWarning && !model.hasAnySelected())
+               {
+                       JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.correlate.alloutsiderange"),
+                               I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
+               }
+       }
+
+       /**
+        * @return modified timestamp of specified media object
+        */
+       protected Timestamp getMediaTimestamp(MediaFile inMedia)
+       {
+               Timestamp tstamp = super.getMediaTimestamp(inMedia);
+               try {
+                       AudioFile audio = (AudioFile) inMedia;
+                       int audioLength = audio.getLengthInSeconds();
+                       // Each option is worth half the length of the audio clip, so need to divide by 2
+                       int secsToAdd = audioLength *
+                               (_correlTimesSelector.getSelectedOption() - _fileTimesSelector.getSelectedOption()) / 2;
+                       if (audioLength > 0 && secsToAdd != 0) {
+                               tstamp = tstamp.createPlusOffset(secsToAdd);
+                       }
+               }
+               catch (ClassCastException cce) {}
+               return tstamp;
+       }
+
+       /**
+        * Finish the correlation by modifying the track
+        * and passing the Undo information back to the App
+        */
+       protected void finishCorrelation()
+       {
+               // TODO: Probably should be able to combine this into the Correlator?
+               PointMediaPair[] pointPairs = getPointPairs();
+               if (pointPairs == null || pointPairs.length <= 0) {return;}
+
+               // begin to construct undo information
+               UndoCorrelateAudios undo = new UndoCorrelateAudios(_app.getTrackInfo());
+               // loop over Audios
+               int arraySize = pointPairs.length;
+               int i = 0, numAudios = 0;
+               int numPointsToCreate = 0;
+               PointMediaPair pair = null;
+               for (i=0; i<arraySize; i++)
+               {
+                       pair = pointPairs[i];
+                       if (pair != null && pair.isValid())
+                       {
+                               if (pair.getMinSeconds() == 0L)
+                               {
+                                       // exact match
+                                       AudioFile pointAudio = pair.getPointBefore().getAudio();
+                                       if (pointAudio == null)
+                                       {
+                                               // photo coincides with audioless point so connect the two
+                                               pair.getPointBefore().setAudio((AudioFile) pair.getMedia());
+                                               pair.getMedia().setDataPoint(pair.getPointBefore());
+                                       }
+                                       else if (pointAudio.equals(pair.getMedia())) {
+                                               // photo is already connected, nothing to do
+                                       }
+                                       else {
+                                               // point is already connected to a different audio, so need to clone point
+                                               numPointsToCreate++;
+                                       }
+                               }
+                               else
+                               {
+                                       // audio time falls between two points, so need to interpolate new one
+                                       numPointsToCreate++;
+                               }
+                               numAudios++;
+                       }
+               }
+               // Second loop, to create points if necessary
+               if (numPointsToCreate > 0)
+               {
+                       // make new array for added points
+                       DataPoint[] addedPoints = new DataPoint[numPointsToCreate];
+                       int pointNum = 0;
+                       DataPoint pointToAdd = null;
+                       for (i=0; i<arraySize; i++)
+                       {
+                               pair = pointPairs[i];
+                               if (pair != null && pair.isValid())
+                               {
+                                       pointToAdd = null;
+                                       if (pair.getMinSeconds() == 0L && pair.getPointBefore().getAudio() != null
+                                        && !pair.getPointBefore().getAudio().equals(pair.getMedia()))
+                                       {
+                                               // clone point
+                                               pointToAdd = pair.getPointBefore().clonePoint();
+                                       }
+                                       else if (pair.getMinSeconds() > 0L)
+                                       {
+                                               // interpolate point
+                                               pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction());
+                                       }
+                                       if (pointToAdd != null)
+                                       {
+                                               // link audio to point
+                                               pointToAdd.setAudio((AudioFile) pair.getMedia());
+                                               pair.getMedia().setDataPoint(pointToAdd);
+                                               // set to start of segment so not joined in track
+                                               pointToAdd.setSegmentStart(true);
+                                               // add to point array
+                                               addedPoints[pointNum] = pointToAdd;
+                                               pointNum++;
+                                       }
+                               }
+                       }
+                       // expand track
+                       _app.getTrackInfo().getTrack().appendPoints(addedPoints);
+               }
+
+               // send undo information back to controlling app
+               undo.setNumAudiosCorrelated(numAudios);
+               _app.completeFunction(undo, ("" + numAudios + " "
+                        + (numAudios==1?I18nManager.getText("confirm.correlateaudios.single"):I18nManager.getText("confirm.correlateaudios.multi"))));
+               // observers already informed by track update if new points created
+               if (numPointsToCreate == 0) {
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+               }
+       }
+}
diff --git a/tim/prune/correlate/AudioTimestampSelector.java b/tim/prune/correlate/AudioTimestampSelector.java
new file mode 100644 (file)
index 0000000..ee0ce0d
--- /dev/null
@@ -0,0 +1,75 @@
+package tim.prune.correlate;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.I18nManager;
+
+/**
+ * GUI element to allow the selection of timestamp options
+ * for audio file correlation
+ */
+public class AudioTimestampSelector extends JPanel
+{
+       /** Array of radio buttons */
+       private JRadioButton[] _radios = new JRadioButton[3];
+
+
+       /**
+        * Constructor
+        * @param inTopLabelKey key for description label at top
+        * @param inLowerLabelKey key for description label at bottom, if any
+        */
+       public AudioTimestampSelector(String inTopLabelKey, String inLowerLabelKey)
+       {
+               createComponents(inTopLabelKey, inLowerLabelKey);
+               setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+               );
+       }
+
+       /**
+        * Create the GUI components
+        * @param inTopLabelKey key for description label at top
+        * @param inLowerLabelKey key for description label at bottom, if any
+        */
+       private void createComponents(String inTopLabelKey, String inLowerLabelKey)
+       {
+               setLayout(new BorderLayout());
+               add(new JLabel(I18nManager.getText(inTopLabelKey)), BorderLayout.NORTH);
+               // panel for the radio buttons
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(0, 3, 15, 3));
+               final String[] keys = {"beginning", "middle", "end"};
+               ButtonGroup group = new ButtonGroup();
+               for (int i=0; i<3; i++)
+               {
+                       _radios[i] = new JRadioButton(I18nManager.getText("dialog.correlate.timestamp." + keys[i]));
+                       group.add(_radios[i]);
+                       gridPanel.add(_radios[i]);
+               }
+               _radios[0].setSelected(true);
+               add(gridPanel, BorderLayout.CENTER);
+               if (inLowerLabelKey != null) {
+                       add(new JLabel(I18nManager.getText(inLowerLabelKey)), BorderLayout.SOUTH);
+               }
+       }
+
+       /**
+        * Get the option selected by the user
+        * @return 0 for beginning, 1 for middle or 2 for end
+        */
+       public int getSelectedOption()
+       {
+               for (int i=0; i<_radios.length; i++)
+                       if (_radios[i].isSelected()) {return i;}
+               return 0;
+       }
+}
diff --git a/tim/prune/correlate/CardStack.java b/tim/prune/correlate/CardStack.java
new file mode 100644 (file)
index 0000000..555c544
--- /dev/null
@@ -0,0 +1,64 @@
+package tim.prune.correlate;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+
+import javax.swing.JPanel;
+
+/**
+ * Panel to act as a card stack
+ */
+public class CardStack extends JPanel
+{
+       private int _numCards = 0;
+       private int _currCard = 0;
+       private CardLayout _layout = null;
+       private static final String cardName = "card";
+
+       /**
+        * Constructor
+        */
+       public CardStack()
+       {
+               _layout = new CardLayout();
+               setLayout(_layout);
+       }
+
+       /**
+        * Add a card to the stack
+        * @param inComponent component to add
+        */
+       public void addCard(Component inComponent)
+       {
+               super.add(inComponent, cardName + _numCards);
+               _numCards++;
+       }
+
+       /**
+        * @return current card index, starting from 0
+        */
+       public int getCurrentCardIndex()
+       {
+               return _currCard;
+       }
+
+       /**
+        * @return number of cards in the stack
+        */
+       public int getNumCards()
+       {
+               return _numCards;
+       }
+
+       /**
+        * Show the specified card
+        * @param inIndex index of card, starting from 0
+        */
+       public void showCard(int inIndex)
+       {
+               if (inIndex >= 0 && inIndex < _numCards) {
+                       _currCard = inIndex;
+                       _layout.show(this, cardName + inIndex);
+               }
+       }
+}
diff --git a/tim/prune/correlate/Correlator.java b/tim/prune/correlate/Correlator.java
new file mode 100644 (file)
index 0000000..248be18
--- /dev/null
@@ -0,0 +1,682 @@
+package tim.prune.correlate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Distance;
+import tim.prune.data.Field;
+import tim.prune.data.MediaFile;
+import tim.prune.data.MediaList;
+import tim.prune.data.TimeDifference;
+import tim.prune.data.Timestamp;
+import tim.prune.data.Track;
+
+/**
+ * Abstract superclass of the two correlator functions
+ */
+public abstract class Correlator extends GenericFunction
+{
+       protected JDialog _dialog;
+       private CardStack _cards = null;
+       private JLabel _tipLabel = null;
+       private JTable _selectionTable = null;
+       protected JTable _previewTable = null;
+       private boolean _previewEnabled = false; // flag required to enable preview function on final panel
+       private boolean[] _cardEnabled = null; // flag for each card
+       private JTextField _offsetHourBox = null, _offsetMinBox = null, _offsetSecBox = null;
+       private JRadioButton _mediaLaterOption = null, _pointLaterOption = null;
+       private JRadioButton _timeLimitRadio = null, _distLimitRadio = null;
+       private JTextField _limitMinBox = null, _limitSecBox = null;
+       private JTextField _limitDistBox = null;
+       private JComboBox _distUnitsDropdown = null;
+       private JButton _nextButton = null, _backButton = null;
+       protected JButton _okButton = null;
+
+       /**
+        * Constructor
+        * @param inApp App object to report actions to
+        */
+       public Correlator(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return type key eg photo, audio
+        */
+       protected abstract String getMediaTypeKey();
+
+       /**
+        * @return media list
+        */
+       protected abstract MediaList getMediaList();
+
+       /**
+        * Begin the function by initialising and showing the dialog
+        */
+       public void begin()
+       {
+               // Check whether track has timestamps, exit if not
+               if (!_app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP))
+               {
+                       JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.correlate.notimestamps"),
+                               I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
+                       return;
+               }
+               // Show warning if no uncorrelated audios
+               if (!getMediaList().hasUncorrelatedMedia())
+               {
+                       Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
+                       if (JOptionPane.showOptionDialog(_parentFrame,
+                                       I18nManager.getText("dialog.correlate.nouncorrelated" + getMediaTypeKey() + "s"),
+                                       I18nManager.getText(getNameKey()), JOptionPane.YES_NO_OPTION,
+                                       JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+                               == JOptionPane.NO_OPTION)
+                       {
+                               return;
+                       }
+               }
+               // Create dialog if necessary
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.getContentPane().add(makeDialogContents());
+                       _dialog.pack();
+               }
+               // Go to first available card
+               int card = 0;
+               _cardEnabled = null;
+               while (!isCardEnabled(card)) {card++;}
+               _cards.showCard(card);
+               showCard(0); // does set up and next/prev enabling
+               _okButton.setEnabled(false);
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Make contents of correlate dialog
+        * @return JPanel containing gui elements
+        */
+       private JPanel makeDialogContents()
+       {
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BorderLayout());
+
+               // Card panel in the middle
+               _cards = new CardStack();
+
+               // First panel (not required by photo correlator)
+               JPanel card1 = makeFirstPanel();
+               if (card1 == null) {card1 = new JPanel();}
+               _cards.addCard(card1);
+
+               // Second panel for selection of linked media
+               _cards.addCard(makeSecondPanel());
+
+               // Third panel for options and preview
+               _cards.addCard(makeThirdPanel());
+               mainPanel.add(_cards, BorderLayout.CENTER);
+
+               // Button panel at the bottom
+               JPanel buttonPanel = new JPanel();
+               _backButton = new JButton(I18nManager.getText("button.back"));
+               _backButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               showCard(-1);
+                       }
+               });
+               _backButton.setEnabled(false);
+               buttonPanel.add(_backButton);
+               _nextButton = new JButton(I18nManager.getText("button.next"));
+               _nextButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               showCard(1);
+                       }
+               });
+               buttonPanel.add(_nextButton);
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener()
+                       {
+                               public void actionPerformed(ActionEvent e)
+                               {
+                                       finishCorrelation();
+                                       _dialog.dispose();
+                               }
+                       });
+               _okButton.setEnabled(false);
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return mainPanel;
+       }
+
+       /**
+        * Construct a table model for the photo / audio selection table
+        * @return table model
+        */
+       protected MediaSelectionTableModel makeSelectionTableModel()
+       {
+               MediaList mediaList = getMediaList();
+               MediaSelectionTableModel model = new MediaSelectionTableModel(
+                       "dialog.correlate.select." + getMediaTypeKey() + "name",
+                       "dialog.correlate.select." + getMediaTypeKey() + "later");
+               int numMedia = mediaList.getNumMedia();
+               for (int i=0; i<numMedia; i++)
+               {
+                       MediaFile media = mediaList.getMedia(i);
+                       // For working out time differences, can't use media which already had point information
+                       if (media.getDataPoint() != null && media.getDataPoint().hasTimestamp()
+                               && media.getOriginalStatus() == MediaFile.Status.NOT_CONNECTED)
+                       {
+                               // Calculate time difference, add to table model
+                               long timeDiff = getMediaTimestamp(media).getSecondsSince(media.getDataPoint().getTimestamp());
+                               model.addMedia(media, timeDiff);
+                       }
+               }
+               return model;
+       }
+
+       /**
+        * Group the two radio buttons together with a ButtonGroup
+        * @param inButton1 first radio button
+        * @param inButton2 second radio button
+        */
+       protected static void groupRadioButtons(JRadioButton inButton1, JRadioButton inButton2)
+       {
+               ButtonGroup buttonGroup = new ButtonGroup();
+               buttonGroup.add(inButton1);
+               buttonGroup.add(inButton2);
+               inButton1.setSelected(true);
+       }
+
+
+       /**
+        * Try to parse the given string
+        * @param inText String to parse
+        * @return value if parseable, 0 otherwise
+        */
+       protected static int getValue(String inText)
+       {
+               int value = 0;
+               try {
+                       value = Integer.parseInt(inText);
+               }
+               catch (NumberFormatException nfe) {}
+               return value;
+       }
+
+
+       /**
+        * @param inFirstTimestamp timestamp of first photo / audio object, or null if not available
+        * @return time difference of local time zone from UTC when the first photo was taken
+        */
+       private static TimeDifference getTimezoneOffset(Timestamp inFirstTimestamp)
+       {
+               Calendar cal = null;
+               // Use first timestamp if available
+               if (inFirstTimestamp != null) {
+                       cal = inFirstTimestamp.getCalendar();
+               }
+               else {
+                       // No photo or no timestamp, just use current time
+                       cal = Calendar.getInstance();
+               }
+               // Both time zone offset and dst offset are based on milliseconds, so convert to seconds
+               TimeDifference timeDiff = new TimeDifference((cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000);
+               return timeDiff;
+       }
+
+
+       /**
+        * Calculate the median index to select from the table
+        * @param inModel table model
+        * @return index of entry to select from table
+        */
+       protected static int getMedianIndex(MediaSelectionTableModel inModel)
+       {
+               // make sortable list
+               TreeSet<TimeIndexPair> set = new TreeSet<TimeIndexPair>();
+               // loop through rows of table adding to list
+               int numRows = inModel.getRowCount();
+               int i;
+               for (i=0; i<numRows; i++)
+               {
+                       MediaSelectionTableRow row = inModel.getRow(i);
+                       set.add(new TimeIndexPair(row.getTimeDiff().getTotalSeconds(), i));
+               }
+               // pull out middle entry and return index
+               TimeIndexPair pair = null;
+               Iterator<TimeIndexPair> iterator = set.iterator();
+               for (i=0; i<(numRows+1)/2; i++)
+               {
+                       pair = iterator.next();
+               }
+               return pair.getIndex();
+       }
+
+
+       /**
+        * Disable the ok button
+        */
+       public void disableOkButton()
+       {
+               if (_okButton != null) {
+                       _okButton.setEnabled(false);
+               }
+       }
+
+       /**
+        * @return gui components for first panel, or null if empty
+        */
+       protected JPanel makeFirstPanel() {
+               return null;
+       }
+
+       /**
+        * Make the second panel for the selection screen
+        * @return JPanel object containing gui elements
+        */
+       private JPanel makeSecondPanel()
+       {
+               JPanel card = new JPanel();
+               card.setLayout(new BorderLayout(10, 10));
+               card.add(new JLabel(I18nManager.getText(
+                       "dialog.correlate." + getMediaTypeKey() + "select.intro")), BorderLayout.NORTH);
+               // table doesn't have model yet - that will be attached later
+               _selectionTable = new JTable();
+               JScrollPane photoScrollPane = new JScrollPane(_selectionTable);
+               photoScrollPane.setPreferredSize(new Dimension(400, 100));
+               card.add(photoScrollPane, BorderLayout.CENTER);
+               return card;
+       }
+
+
+       /**
+        * Make contents of third panel including options and preview
+        * @return JPanel containing gui elements
+        */
+       private JPanel makeThirdPanel()
+       {
+               OptionsChangedListener optionsChangedListener = new OptionsChangedListener(this);
+               // Second panel for options
+               JPanel card2 = new JPanel();
+               card2.setLayout(new BorderLayout());
+               JPanel card2Top = new JPanel();
+               card2Top.setLayout(new BoxLayout(card2Top, BoxLayout.Y_AXIS));
+               _tipLabel = new JLabel(I18nManager.getText("dialog.correlate.options.tip"));
+               _tipLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
+               card2Top.add(_tipLabel);
+               JLabel introLabel = new JLabel(I18nManager.getText("dialog.correlate.options.intro"));
+               introLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
+               card2Top.add(introLabel);
+               // time offset section
+               JPanel offsetPanel = new JPanel();
+               offsetPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.offsetpanel")));
+               offsetPanel.setLayout(new BoxLayout(offsetPanel, BoxLayout.Y_AXIS));
+               JPanel offsetPanelTop = new JPanel();
+               offsetPanelTop.setLayout(new FlowLayout());
+               offsetPanelTop.setBorder(null);
+               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset") + ": "));
+               _offsetHourBox = new JTextField(3);
+               _offsetHourBox.addKeyListener(optionsChangedListener);
+               offsetPanelTop.add(_offsetHourBox);
+               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
+               _offsetMinBox = new JTextField(3);
+               _offsetMinBox.addKeyListener(optionsChangedListener);
+               offsetPanelTop.add(_offsetMinBox);
+               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
+               _offsetSecBox = new JTextField(3);
+               _offsetSecBox.addKeyListener(optionsChangedListener);
+               offsetPanelTop.add(_offsetSecBox);
+               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
+               offsetPanel.add(offsetPanelTop);
+
+               // radio buttons for photo / point later
+               JPanel offsetPanelBot = new JPanel();
+               offsetPanelBot.setLayout(new FlowLayout());
+               offsetPanelBot.setBorder(null);
+               _mediaLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options." + getMediaTypeKey() + "later"));
+               _pointLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options.pointlaterphoto"));
+               _mediaLaterOption.addItemListener(optionsChangedListener);
+               _pointLaterOption.addItemListener(optionsChangedListener);
+               ButtonGroup laterGroup = new ButtonGroup();
+               laterGroup.add(_mediaLaterOption);
+               laterGroup.add(_pointLaterOption);
+               offsetPanelBot.add(_mediaLaterOption);
+               offsetPanelBot.add(_pointLaterOption);
+               offsetPanel.add(offsetPanelBot);
+               offsetPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               card2Top.add(offsetPanel);
+
+               // time limits section
+               JPanel limitsPanel = new JPanel();
+               limitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.limitspanel")));
+               limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
+               JPanel timeLimitPanel = new JPanel();
+               timeLimitPanel.setLayout(new FlowLayout());
+               JRadioButton noTimeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.notimelimit"));
+               noTimeLimitRadio.addItemListener(optionsChangedListener);
+               timeLimitPanel.add(noTimeLimitRadio);
+               _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + " : ");
+               _timeLimitRadio.addItemListener(optionsChangedListener);
+               timeLimitPanel.add(_timeLimitRadio);
+               groupRadioButtons(noTimeLimitRadio, _timeLimitRadio);
+               _limitMinBox = new JTextField(3);
+               _limitMinBox.addKeyListener(optionsChangedListener);
+               timeLimitPanel.add(_limitMinBox);
+               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
+               _limitSecBox = new JTextField(3);
+               _limitSecBox.addKeyListener(optionsChangedListener);
+               timeLimitPanel.add(_limitSecBox);
+               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
+               limitsPanel.add(timeLimitPanel);
+               // distance limits
+               JPanel distLimitPanel = new JPanel();
+               distLimitPanel.setLayout(new FlowLayout());
+               JRadioButton noDistLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.nodistancelimit"));
+               noDistLimitRadio.addItemListener(optionsChangedListener);
+               distLimitPanel.add(noDistLimitRadio);
+               _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit"));
+               _distLimitRadio.addItemListener(optionsChangedListener);
+               distLimitPanel.add(_distLimitRadio);
+               groupRadioButtons(noDistLimitRadio, _distLimitRadio);
+               _limitDistBox = new JTextField(4);
+               _limitDistBox.addKeyListener(optionsChangedListener);
+               distLimitPanel.add(_limitDistBox);
+               String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
+                       I18nManager.getText("units.miles")};
+               _distUnitsDropdown = new JComboBox(distUnitsOptions);
+               _distUnitsDropdown.addItemListener(optionsChangedListener);
+               distLimitPanel.add(_distUnitsDropdown);
+               limitsPanel.add(distLimitPanel);
+               limitsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               card2Top.add(limitsPanel);
+
+               // preview button
+               JButton previewButton = new JButton(I18nManager.getText("button.preview"));
+               previewButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               createPreview(true);
+                       }
+               });
+               card2Top.add(previewButton);
+               card2.add(card2Top, BorderLayout.NORTH);
+               // preview
+               _previewTable = new JTable(new MediaPreviewTableModel("dialog.correlate.select." + getMediaTypeKey() + "name"));
+               JScrollPane previewScrollPane = new JScrollPane(_previewTable);
+               previewScrollPane.setPreferredSize(new Dimension(300, 100));
+               card2.add(previewScrollPane, BorderLayout.CENTER);
+               return card2;
+       }
+
+
+       /**
+        * Go to the next or previous card in the stack
+        * @param increment 1 for next, -1 for previous card
+        */
+       private void showCard(int increment)
+       {
+               int currCard = _cards.getCurrentCardIndex();
+               int next = currCard + increment;
+               if (!isCardEnabled(next)) {
+                       next += increment;
+               }
+               setupCard(next);
+               _backButton.setEnabled(next > 0 && (isCardEnabled(next-1) || isCardEnabled(next-2)));
+               _nextButton.setEnabled(next < (_cards.getNumCards()-1));
+               _cards.showCard(next);
+       }
+
+       /**
+        * @param inCardNum index of card
+        * @return true if specified card is enabled
+        */
+       private boolean isCardEnabled(int inCardNum)
+       {
+               if (_cardEnabled == null) {_cardEnabled = getCardEnabledFlags();}
+               return (inCardNum >= 0 && inCardNum < _cardEnabled.length && _cardEnabled[inCardNum]);
+       }
+
+       /**
+        * @return array of boolean flags denoting availability of cards
+        */
+       protected boolean[] getCardEnabledFlags()
+       {
+               // by default first is off and third is always on; second depends on selection table
+               return new boolean[] {false, makeSelectionTableModel().getRowCount() > 0, true};
+       }
+
+       /**
+        * Set up the specified card
+        * @param inCardNum index of card
+        */
+       protected void setupCard(int inCardNum)
+       {
+               _previewEnabled = false;
+               if (inCardNum == 1)
+               {
+                       // set up photo selection card
+                       MediaSelectionTableModel model = makeSelectionTableModel();
+                       _selectionTable.setModel(model);
+                       for (int i=0; i<model.getColumnCount(); i++) {
+                               _selectionTable.getColumnModel().getColumn(i).setPreferredWidth(i==3?50:150);
+                       }
+                       // Calculate median time difference, select corresponding row of table
+                       int preselectedIndex = model.getRowCount() < 3 ? 0 : getMedianIndex(model);
+                       _selectionTable.getSelectionModel().setSelectionInterval(preselectedIndex, preselectedIndex);
+                       _nextButton.requestFocus();
+               }
+               else if (inCardNum == 2)
+               {
+                       // set up the options/preview card - first check for given time difference
+                       TimeDifference timeDiff = null;
+                       if (isCardEnabled(1))
+                       {
+                               int rowNum = _selectionTable.getSelectedRow();
+                               if (rowNum < 0) {rowNum = 0;}
+                               MediaSelectionTableRow selectedRow =
+                                       ((MediaSelectionTableModel) _selectionTable.getModel()).getRow(rowNum);
+                               timeDiff = selectedRow.getTimeDiff();
+                       }
+                       setupPreviewCard(timeDiff, getMediaList().getMedia(0));
+               }
+               // enable ok button if any photos have been selected
+               _okButton.setEnabled(inCardNum == 2 && ((MediaPreviewTableModel) _previewTable.getModel()).hasAnySelected());
+       }
+
+       /**
+        * Parse the time limit values entered and validate them
+        * @return TimeDifference object describing limit
+        */
+       protected TimeDifference parseTimeLimit()
+       {
+               if (!_timeLimitRadio.isSelected()) {return null;}
+               int mins = getValue(_limitMinBox.getText());
+               _limitMinBox.setText("" + mins);
+               int secs = getValue(_limitSecBox.getText());
+               _limitSecBox.setText("" + secs);
+               if (mins <= 0 && secs <= 0) {return null;}
+               return new TimeDifference(0, mins, secs, true);
+       }
+
+       /**
+        * Parse the distance limit value entered and validate
+        * @return angular distance in radians
+        */
+       protected double parseDistanceLimit()
+       {
+               double value = -1.0;
+               if (_distLimitRadio.isSelected())
+               {
+                       try {
+                               value = Double.parseDouble(_limitDistBox.getText());
+                       }
+                       catch (NumberFormatException nfe) {}
+               }
+               if (value <= 0.0) {
+                       _limitDistBox.setText("0");
+                       return -1.0;
+               }
+               _limitDistBox.setText("" + value);
+               return Distance.convertDistanceToRadians(value, getSelectedDistanceUnits());
+       }
+
+
+       /**
+        * @return the selected distance units from the dropdown
+        */
+       protected Distance.Units getSelectedDistanceUnits()
+       {
+               final Distance.Units[] distUnits = {Distance.Units.KILOMETRES, Distance.Units.METRES, Distance.Units.MILES};
+               return distUnits[_distUnitsDropdown.getSelectedIndex()];
+       }
+
+       /**
+        * Create a preview of the correlate action using the selected time difference
+        * @param inFromButton true if triggered from button press, false if automatic
+        */
+       public void createPreview(boolean inFromButton)
+       {
+               // Exit if still on first panel
+               if (!_previewEnabled) {return;}
+               // Create a TimeDifference based on the edit boxes
+               int numHours = getValue(_offsetHourBox.getText());
+               int numMins = getValue(_offsetMinBox.getText());
+               int numSecs = getValue(_offsetSecBox.getText());
+               boolean isPos = _mediaLaterOption.isSelected();
+               createPreview(new TimeDifference(numHours, numMins, numSecs, isPos), inFromButton);
+       }
+
+       /**
+        * Set up the final card using the given time difference and show it
+        * @param inTimeDiff time difference to use for time offsets
+        * @param inFirstMedia first media object to use for calculating timezone
+        */
+       protected void setupPreviewCard(TimeDifference inTimeDiff, MediaFile inFirstMedia)
+       {
+               _previewEnabled = false;
+               TimeDifference timeDiff = inTimeDiff;
+               if (timeDiff == null)
+               {
+                       // No time difference available, so calculate based on computer's time zone
+                       Timestamp tstamp = null;
+                       if (inFirstMedia != null) {
+                               tstamp = inFirstMedia.getTimestamp();
+                       }
+                       timeDiff = getTimezoneOffset(tstamp);
+               }
+               // Use time difference to set edit boxes
+               _offsetHourBox.setText("" + timeDiff.getNumHours());
+               _offsetMinBox.setText("" + timeDiff.getNumMinutes());
+               _offsetSecBox.setText("" + timeDiff.getNumSeconds());
+               _mediaLaterOption.setSelected(timeDiff.getIsPositive());
+               _pointLaterOption.setSelected(!timeDiff.getIsPositive());
+               _previewEnabled = true;
+               createPreview(timeDiff, true);
+       }
+
+       /**
+        * Create a preview of the correlate action using the selected time difference
+        * @param inTimeDiff TimeDifference to use for preview
+        * @param inShowWarning true to show warning if all points out of range
+        */
+       protected abstract void createPreview(TimeDifference inTimeDiff, boolean inShowWarning);
+
+
+       /**
+        * Get the timestamp of the given media
+        * @param inMedia media object
+        * @return normally just returns the media timestamp, overridden by audio correlator
+        */
+       protected Timestamp getMediaTimestamp(MediaFile inMedia)
+       {
+               return inMedia.getTimestamp();
+       }
+
+       /**
+        * Get the point pair surrounding the given media item
+        * @param inTrack track object
+        * @param inMedia media object
+        * @param inOffset time offset to apply
+        * @return point pair resulting from correlation
+        */
+       protected PointMediaPair getPointPairForMedia(Track inTrack, MediaFile inMedia, TimeDifference inOffset)
+       {
+               PointMediaPair pair = new PointMediaPair(inMedia);
+               // Add/subtract offset to media timestamp
+               Timestamp mediaStamp = getMediaTimestamp(inMedia).createMinusOffset(inOffset);
+               int numPoints = inTrack.getNumPoints();
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = inTrack.getPoint(i);
+                       if (point.getPhoto() == null && point.getAudio() == null)
+                       {
+                               Timestamp pointStamp = point.getTimestamp();
+                               if (pointStamp != null && pointStamp.isValid())
+                               {
+                                       long numSeconds = pointStamp.getSecondsSince(mediaStamp);
+                                       pair.addPoint(point, numSeconds);
+                               }
+                       }
+               }
+               return pair;
+       }
+
+
+       /**
+        * Finish the correlation
+        */
+       protected abstract void finishCorrelation();
+
+       /**
+        * Construct an array of the point pairs to use for correlation
+        * @return array of PointMediaPair objects
+        */
+       protected PointMediaPair[] getPointPairs()
+       {
+               MediaPreviewTableModel model = (MediaPreviewTableModel) _previewTable.getModel();
+               int numMedia = model.getRowCount();
+               PointMediaPair[] pairs = new PointMediaPair[numMedia];
+               // Loop over items in preview table model
+               for (int i=0; i<numMedia; i++)
+               {
+                       MediaPreviewTableRow row = model.getRow(i);
+                       // add all selected pairs to array (other elements remain null)
+                       if (row.getCorrelateFlag().booleanValue()) {
+                               pairs[i] = row.getPointPair();
+                       }
+               }
+               return pairs;
+       }
+}
similarity index 74%
rename from tim/prune/correlate/PhotoPreviewTableModel.java
rename to tim/prune/correlate/MediaPreviewTableModel.java
index 47d48b717d9a172eefcf2924d248e35538f12872..ea930493224b284cb54e1f5a81fe6498468ebb5f 100644 (file)
@@ -7,12 +7,14 @@ import tim.prune.I18nManager;
 import tim.prune.data.Distance;
 
 /**
- * Class to act as table model for the photo preview table
+ * Class to act as the table model for the correlation preview table
  */
-public class PhotoPreviewTableModel extends AbstractTableModel
+public class MediaPreviewTableModel extends AbstractTableModel
 {
+       /** Text for first column heading */
+       private String _firstColumnHeading = null;
        /** ArrayList containing TableRow objects */
-       private ArrayList<PhotoPreviewTableRow> _list = new ArrayList<PhotoPreviewTableRow>();
+       private ArrayList<MediaPreviewTableRow> _list = new ArrayList<MediaPreviewTableRow>();
        /** Distance units */
        private Distance.Units _distanceUnits = Distance.Units.KILOMETRES;
        /** Number formatter */
@@ -26,16 +28,21 @@ public class PhotoPreviewTableModel extends AbstractTableModel
                FORMAT_ONE_DP.setMinimumFractionDigits(1);
        }
 
+       /**
+        * Constructor
+        * @param inFirstColumnKey key for first column heading
+        */
+       public MediaPreviewTableModel(String inFirstColumnKey) {
+               _firstColumnHeading = I18nManager.getText(inFirstColumnKey);
+       }
 
        /**
         * @return the column count, always 5
         */
-       public int getColumnCount()
-       {
+       public int getColumnCount() {
                return 5;
        }
 
-
        /**
         * Get the name of the column
         * @param inColNum column number
@@ -43,9 +50,9 @@ public class PhotoPreviewTableModel extends AbstractTableModel
         */
        public String getColumnName(int inColNum)
        {
-               if (inColNum == 0) return I18nManager.getText("dialog.correlate.photoselect.photoname");
+               if (inColNum == 0) return _firstColumnHeading;
                else if (inColNum == 1) return I18nManager.getText("fieldname.timestamp");
-               else if (inColNum == 2) return I18nManager.getText("dialog.correlate.photoselect.timediff");
+               else if (inColNum == 2) return I18nManager.getText("dialog.correlate.select.timediff");
                else if (inColNum == 3) return I18nManager.getText("fieldname.distance");
                return I18nManager.getText("dialog.correlate.options.correlate");
        }
@@ -65,7 +72,7 @@ public class PhotoPreviewTableModel extends AbstractTableModel
         * @param inRowIndex row index
         * @return table row object
         */
-       public PhotoPreviewTableRow getRow(int inRowIndex)
+       public MediaPreviewTableRow getRow(int inRowIndex)
        {
                return _list.get(inRowIndex);
        }
@@ -79,10 +86,10 @@ public class PhotoPreviewTableModel extends AbstractTableModel
         */
        public Object getValueAt(int inRowIndex, int inColumnIndex)
        {
-               PhotoPreviewTableRow row = _list.get(inRowIndex);
-               if (inColumnIndex == 0) return row.getPhoto().getFile().getName();
+               MediaPreviewTableRow row = _list.get(inRowIndex);
+               if (inColumnIndex == 0) return row.getMedia().getFile().getName();
                else if (inColumnIndex == 1) {
-                       return row.getPhoto().getTimestamp().getText();
+                       return row.getMedia().getTimestamp().getText();
                }
                else if (inColumnIndex == 2) {
                        if (row.getPointPair().isValid()) {
@@ -119,10 +126,10 @@ public class PhotoPreviewTableModel extends AbstractTableModel
 
 
        /**
-        * Add a photo to the list
+        * Add a row to the list
         * @param inRow row to add
         */
-       public void addPhotoRow(PhotoPreviewTableRow inRow)
+       public void addRow(MediaPreviewTableRow inRow)
        {
                _list.add(inRow);
        }
@@ -153,12 +160,11 @@ public class PhotoPreviewTableModel extends AbstractTableModel
        /**
         * @return true if any of the correlate flags are on
         */
-       public boolean hasPhotosSelected()
+       public boolean hasAnySelected()
        {
                for (int i=0; i<getRowCount(); i++)
                {
-                       if (getRow(i).getCorrelateFlag().booleanValue())
-                       {
+                       if (getRow(i).getCorrelateFlag().booleanValue()) {
                                return true;
                        }
                }
@@ -176,10 +182,9 @@ public class PhotoPreviewTableModel extends AbstractTableModel
                // can only edit the correlate column
                if (inColumnIndex == 4)
                {
-                       PhotoPreviewTableRow row = getRow(inRowIndex);
-                       // Don't allow setting of photos which can't be correlated
-                       if (row.getPointPair().isValid())
-                       {
+                       MediaPreviewTableRow row = getRow(inRowIndex);
+                       // Don't allow setting of items which can't be correlated
+                       if (row.getPointPair().isValid()) {
                                row.setCorrelateFlag(((Boolean) inValue).booleanValue());
                        }
                }
similarity index 70%
rename from tim/prune/correlate/PhotoPreviewTableRow.java
rename to tim/prune/correlate/MediaPreviewTableRow.java
index 70955375c2022fdad279dcf1bfeedb266afbd902..cadede31b39613e5512fc6a98be0d284e8cb814d 100644 (file)
@@ -3,12 +3,11 @@ package tim.prune.correlate;
 import tim.prune.data.Distance;
 
 /**
- * Class to hold contents of a single row
- * in the photo preview table
+ * Class to hold the contents of a single row in the correlation preview table
  */
-public class PhotoPreviewTableRow extends PhotoSelectionTableRow
+public class MediaPreviewTableRow extends MediaSelectionTableRow
 {
-       private PointPair _pointPair = null;
+       private PointMediaPair _pointPair = null;
        private double _distance = 0.0;
        private int _status = 0;
        private boolean _correlate = false;
@@ -18,13 +17,13 @@ public class PhotoPreviewTableRow extends PhotoSelectionTableRow
         * Constructor
         * @param inPointPair point pair object
         */
-       public PhotoPreviewTableRow(PointPair inPointPair)
+       public MediaPreviewTableRow(PointMediaPair inPointPair)
        {
-               super(inPointPair.getPhoto(), inPointPair.getMinSeconds());
+               super(inPointPair.getMedia(), inPointPair.getMinSeconds());
                _pointPair = inPointPair;
                _distance = inPointPair.getMinRadians();
                _status = 0;
-               _correlate = (inPointPair.getPhoto().getDataPoint() == null);
+               _correlate = (inPointPair.getMedia().getDataPoint() == null);
        }
 
        /**
@@ -47,7 +46,7 @@ public class PhotoPreviewTableRow extends PhotoSelectionTableRow
        /**
         * @return point pair object
         */
-       public PointPair getPointPair()
+       public PointMediaPair getPointPair()
        {
                return _pointPair;
        }
diff --git a/tim/prune/correlate/MediaSelectionTableModel.java b/tim/prune/correlate/MediaSelectionTableModel.java
new file mode 100644 (file)
index 0000000..b06a05e
--- /dev/null
@@ -0,0 +1,109 @@
+package tim.prune.correlate;
+
+import java.util.ArrayList;
+import javax.swing.table.AbstractTableModel;
+import tim.prune.I18nManager;
+import tim.prune.data.MediaFile;
+
+
+/**
+ * Class to act as the table model for the selection table in the correlation functions.
+ * Can be used by both photo correlation and audio correlation
+ */
+public class MediaSelectionTableModel extends AbstractTableModel
+{
+       /** Text for first column heading */
+       private String _firstColumnHeading = null;
+       /** Text for last column heading */
+       private String _lastColumnHeading = null;
+       /** List of rows */
+       private ArrayList<MediaSelectionTableRow> _list = new ArrayList<MediaSelectionTableRow>();
+
+
+       /**
+        * Constructor
+        * @param inFirstColumnKey key for first column heading
+        * @param inLastColumnKey key for last column heading
+        */
+       public MediaSelectionTableModel(String inFirstColumnKey, String inLastColumnKey)
+       {
+               _firstColumnHeading = I18nManager.getText(inFirstColumnKey);
+               _lastColumnHeading = I18nManager.getText(inLastColumnKey);
+       }
+
+       /**
+        * @return the column count, always 4
+        */
+       public int getColumnCount() {
+               return 4;
+       }
+
+       /**
+        * Get the name of the column
+        * @param inColNum column number
+        * @return column name
+        */
+       public String getColumnName(int inColNum)
+       {
+               if (inColNum == 0) return _firstColumnHeading;
+               else if (inColNum == 1) return I18nManager.getText("fieldname.timestamp");
+               else if (inColNum == 2) return I18nManager.getText("dialog.correlate.select.timediff");
+               return _lastColumnHeading;
+       }
+
+
+       /**
+        * @return the row count
+        */
+       public int getRowCount()
+       {
+               return _list.size();
+       }
+
+
+       /**
+        * Get the selected row from the table
+        * @param inRowIndex row index
+        * @return table row object
+        */
+       public MediaSelectionTableRow getRow(int inRowIndex)
+       {
+               return _list.get(inRowIndex);
+       }
+
+
+       /**
+        * Get the value of the specified cell
+        * @param inRowIndex row index
+        * @param inColumnIndex column index
+        * @return value of specified cell
+        */
+       public Object getValueAt(int inRowIndex, int inColumnIndex)
+       {
+               // MAYBE: only show time of photos (not date) if dates all identical
+               MediaSelectionTableRow row = _list.get(inRowIndex);
+               if (inColumnIndex == 0) return row.getMedia().getFile().getName();
+               else if (inColumnIndex == 1) return row.getMedia().getTimestamp().getText();
+               else if (inColumnIndex == 2) return row.getTimeDiff().getDescription();
+               return (row.getTimeDiff().getIsPositive() ? I18nManager.getText("dialog.about.yes") :
+                       I18nManager.getText("dialog.about.no"));
+       }
+
+
+       /**
+        * Clear the list
+        */
+       public void reset() {
+               _list.clear();
+       }
+
+       /**
+        * Add a media object to the list
+        * @param inMedia item to add
+        * @param inTimeDiff time difference
+        */
+       public void addMedia(MediaFile inMedia, long inTimeDiff)
+       {
+               _list.add(new MediaSelectionTableRow(inMedia, inTimeDiff));
+       }
+}
diff --git a/tim/prune/correlate/MediaSelectionTableRow.java b/tim/prune/correlate/MediaSelectionTableRow.java
new file mode 100644 (file)
index 0000000..6b9660b
--- /dev/null
@@ -0,0 +1,39 @@
+package tim.prune.correlate;
+
+import tim.prune.data.MediaFile;
+import tim.prune.data.TimeDifference;
+
+/**
+ * Class to hold the contents of a single row
+ * in the selection table for correlation
+ */
+public class MediaSelectionTableRow
+{
+       private MediaFile _media = null;
+       private TimeDifference _timeDiff = null;
+
+       /**
+        * Constructor
+        * @param inMedia media item
+        * @param inNumSecs number of seconds time difference as long
+        */
+       public MediaSelectionTableRow(MediaFile inMedia, long inNumSecs)
+       {
+               _media = inMedia;
+               _timeDiff = new TimeDifference(inNumSecs);
+       }
+
+       /**
+        * @return Media object
+        */
+       public MediaFile getMedia() {
+               return _media;
+       }
+
+       /**
+        * @return time difference
+        */
+       public TimeDifference getTimeDiff() {
+               return _timeDiff;
+       }
+}
index 70420f76733b7ba58b4fcfec32d866411beebb14..2da93e6fc4ede147bdf76c9b928c433817d51afa 100644 (file)
@@ -8,13 +8,13 @@ import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
 
 /**
- * Helper class to listen for changed options on the PhotoCorrelator
+ * Helper class to listen for changed options on the Correlators
  * Tightly coupled but only to ok button and preview function
  */
 public class OptionsChangedListener implements KeyListener, ActionListener, ItemListener, Runnable
 {
        /** Correlator object for callbacks */
-       private PhotoCorrelator _correlator;
+       private Correlator _correlator;
        /** Thread counter */
        private int _threadCount = 0;
 
@@ -26,7 +26,7 @@ public class OptionsChangedListener implements KeyListener, ActionListener, Item
         * Constructor
         * @param inCorrelator correlator object for callbacks
         */
-       public OptionsChangedListener(PhotoCorrelator inCorrelator)
+       public OptionsChangedListener(Correlator inCorrelator)
        {
                _correlator = inCorrelator;
        }
index 84c179ec3d3a23576d9e6ee83cdf1133758e20af..f5f86b1ab36982565a44d9cac29d9bd7aff7ee46 100644 (file)
@@ -1,73 +1,30 @@
 package tim.prune.correlate;
 
-import java.awt.BorderLayout;
-import java.awt.CardLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.Calendar;
-import java.util.Iterator;
-import java.util.TreeSet;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
 import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
 import javax.swing.JTable;
-import javax.swing.JTextField;
 
 import tim.prune.App;
-import tim.prune.GenericFunction;
+import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
 import tim.prune.data.DataPoint;
-import tim.prune.data.Distance;
-import tim.prune.data.Field;
+import tim.prune.data.MediaList;
 import tim.prune.data.Photo;
 import tim.prune.data.PhotoList;
 import tim.prune.data.TimeDifference;
-import tim.prune.data.Timestamp;
-import tim.prune.data.Track;
-import tim.prune.data.TrackInfo;
 import tim.prune.undo.UndoCorrelatePhotos;
 
 /**
  * Class to manage the automatic correlation of photos to points
  * including the GUI stuff to control the correlation options
  */
-public class PhotoCorrelator extends GenericFunction
+public class PhotoCorrelator extends Correlator
 {
-       private JDialog _dialog;
-       private JButton _nextButton = null, _backButton = null;
-       private JButton _okButton = null;
-       private JPanel _cards = null;
-       private JTable _photoSelectionTable = null;
-       private JLabel _tipLabel = null;
-       private JTextField _offsetHourBox = null, _offsetMinBox = null, _offsetSecBox = null;
-       private JRadioButton _photoLaterOption = null, _pointLaterOption = null;
-       private JRadioButton _timeLimitRadio = null, _distLimitRadio = null;
-       private JTextField _limitMinBox = null, _limitSecBox = null;
-       private JTextField _limitDistBox = null;
-       private JComboBox _distUnitsDropdown = null;
-       private JTable _previewTable = null;
-       private boolean _firstTabAvailable = false;
-       private boolean _previewEnabled = false; // flag required to enable preview function on second panel
-
-
        /**
         * Constructor
         * @param inApp App object to report actions to
         */
-       public PhotoCorrelator(App inApp)
-       {
+       public PhotoCorrelator(App inApp) {
                super(inApp);
        }
 
@@ -77,360 +34,34 @@ public class PhotoCorrelator extends GenericFunction
                return "function.correlatephotos";
        }
 
-       /**
-        * Reset dialog and show it
-        */
-       public void begin()
-       {
-               // First create dialog if necessary
-               if (_dialog == null)
-               {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.getContentPane().add(makeDialogContents());
-                       _dialog.pack();
-               }
-               // Check whether track has timestamps, exit if not
-               if (!_app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP))
-               {
-                       JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.correlate.notimestamps"),
-                               I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
-                       return;
-               }
-               // Check for any non-correlated photos, show warning continue/cancel
-               if (!trackHasUncorrelatedPhotos())
-               {
-                       Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
-                       if (JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.correlate.nouncorrelatedphotos"),
-                                       I18nManager.getText(getNameKey()), JOptionPane.YES_NO_OPTION,
-                                       JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
-                               == JOptionPane.NO_OPTION)
-                       {
-                               return;
-                       }
-               }
-               PhotoSelectionTableModel model = makePhotoSelectionTableModel(_app.getTrackInfo());
-               _firstTabAvailable = model != null && model.getRowCount() > 0;
-               CardLayout cl = (CardLayout) _cards.getLayout();
-               if (_firstTabAvailable)
-               {
-                       cl.first(_cards);
-                       _nextButton.setEnabled(true);
-                       _backButton.setEnabled(false);
-                       _tipLabel.setVisible(false);
-                       _photoSelectionTable.setModel(model);
-                       _previewEnabled = false;
-                       for (int i=0; i<model.getColumnCount(); i++) {
-                               _photoSelectionTable.getColumnModel().getColumn(i).setPreferredWidth(i==3?50:150);
-                       }
-                       // Calculate median time difference, select corresponding row of table
-                       int preselectedIndex = model.getRowCount() < 3 ? 0 : getMedianIndex(model);
-                       _photoSelectionTable.getSelectionModel().setSelectionInterval(preselectedIndex, preselectedIndex);
-                       _nextButton.requestFocus();
-               }
-               else
-               {
-                       _tipLabel.setVisible(true);
-                       setupSecondCard(null);
-               }
-               _dialog.setVisible(true);
+       /** @return type key */
+       protected String getMediaTypeKey() {
+               return "photo";
        }
 
-
-       /**
-        * Make contents of correlate dialog
-        * @return JPanel containing gui elements
-        */
-       private JPanel makeDialogContents()
-       {
-               JPanel mainPanel = new JPanel();
-               mainPanel.setLayout(new BorderLayout());
-               // Card panel in the middle
-               _cards = new JPanel();
-               _cards.setLayout(new CardLayout());
-
-               // First panel for photo selection table
-               JPanel card1 = new JPanel();
-               card1.setLayout(new BorderLayout(10, 10));
-               card1.add(new JLabel(I18nManager.getText("dialog.correlate.photoselect.intro")), BorderLayout.NORTH);
-               _photoSelectionTable = new JTable();
-               JScrollPane photoScrollPane = new JScrollPane(_photoSelectionTable);
-               photoScrollPane.setPreferredSize(new Dimension(400, 100));
-               card1.add(photoScrollPane, BorderLayout.CENTER);
-               _cards.add(card1, "card1");
-
-               OptionsChangedListener optionsChangedListener = new OptionsChangedListener(this);
-               // Second panel for options
-               JPanel card2 = new JPanel();
-               card2.setLayout(new BorderLayout());
-               JPanel card2Top = new JPanel();
-               card2Top.setLayout(new BoxLayout(card2Top, BoxLayout.Y_AXIS));
-               _tipLabel = new JLabel(I18nManager.getText("dialog.correlate.options.tip"));
-               _tipLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
-               card2Top.add(_tipLabel);
-               JLabel introLabel = new JLabel(I18nManager.getText("dialog.correlate.options.intro"));
-               introLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
-               card2Top.add(introLabel);
-               // time offset section
-               JPanel offsetPanel = new JPanel();
-               offsetPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.offsetpanel")));
-               offsetPanel.setLayout(new BoxLayout(offsetPanel, BoxLayout.Y_AXIS));
-               JPanel offsetPanelTop = new JPanel();
-               offsetPanelTop.setLayout(new FlowLayout());
-               offsetPanelTop.setBorder(null);
-               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset") + ": "));
-               _offsetHourBox = new JTextField(3);
-               _offsetHourBox.addKeyListener(optionsChangedListener);
-               offsetPanelTop.add(_offsetHourBox);
-               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
-               _offsetMinBox = new JTextField(3);
-               _offsetMinBox.addKeyListener(optionsChangedListener);
-               offsetPanelTop.add(_offsetMinBox);
-               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
-               _offsetSecBox = new JTextField(3);
-               _offsetSecBox.addKeyListener(optionsChangedListener);
-               offsetPanelTop.add(_offsetSecBox);
-               offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
-               offsetPanel.add(offsetPanelTop);
-
-               // radio buttons for photo / point later
-               JPanel offsetPanelBot = new JPanel();
-               offsetPanelBot.setLayout(new FlowLayout());
-               offsetPanelBot.setBorder(null);
-               _photoLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options.photolater"));
-               _pointLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options.pointlater"));
-               _photoLaterOption.addItemListener(optionsChangedListener);
-               _pointLaterOption.addItemListener(optionsChangedListener);
-               ButtonGroup laterGroup = new ButtonGroup();
-               laterGroup.add(_photoLaterOption);
-               laterGroup.add(_pointLaterOption);
-               offsetPanelBot.add(_photoLaterOption);
-               offsetPanelBot.add(_pointLaterOption);
-               offsetPanel.add(offsetPanelBot);
-               offsetPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               card2Top.add(offsetPanel);
-
-               // time limits section
-               JPanel limitsPanel = new JPanel();
-               limitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.limitspanel")));
-               limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
-               JPanel timeLimitPanel = new JPanel();
-               timeLimitPanel.setLayout(new FlowLayout());
-               JRadioButton noTimeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.notimelimit"));
-               noTimeLimitRadio.addItemListener(optionsChangedListener);
-               timeLimitPanel.add(noTimeLimitRadio);
-               _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + " : ");
-               _timeLimitRadio.addItemListener(optionsChangedListener);
-               timeLimitPanel.add(_timeLimitRadio);
-               groupRadioButtons(noTimeLimitRadio, _timeLimitRadio);
-               _limitMinBox = new JTextField(3);
-               _limitMinBox.addKeyListener(optionsChangedListener);
-               timeLimitPanel.add(_limitMinBox);
-               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
-               _limitSecBox = new JTextField(3);
-               _limitSecBox.addKeyListener(optionsChangedListener);
-               timeLimitPanel.add(_limitSecBox);
-               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
-               limitsPanel.add(timeLimitPanel);
-               // distance limits
-               JPanel distLimitPanel = new JPanel();
-               distLimitPanel.setLayout(new FlowLayout());
-               JRadioButton noDistLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.nodistancelimit"));
-               noDistLimitRadio.addItemListener(optionsChangedListener);
-               distLimitPanel.add(noDistLimitRadio);
-               _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit"));
-               _distLimitRadio.addItemListener(optionsChangedListener);
-               distLimitPanel.add(_distLimitRadio);
-               groupRadioButtons(noDistLimitRadio, _distLimitRadio);
-               _limitDistBox = new JTextField(4);
-               _limitDistBox.addKeyListener(optionsChangedListener);
-               distLimitPanel.add(_limitDistBox);
-               String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
-                       I18nManager.getText("units.miles")};
-               _distUnitsDropdown = new JComboBox(distUnitsOptions);
-               _distUnitsDropdown.addItemListener(optionsChangedListener);
-               distLimitPanel.add(_distUnitsDropdown);
-               limitsPanel.add(distLimitPanel);
-               limitsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               card2Top.add(limitsPanel);
-
-               // preview button
-               JButton previewButton = new JButton(I18nManager.getText("button.preview"));
-               previewButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               createPreview(true);
-                       }
-               });
-               card2Top.add(previewButton);
-               card2.add(card2Top, BorderLayout.NORTH);
-               // preview
-               _previewTable = new JTable(new PhotoPreviewTableModel());
-               JScrollPane previewScrollPane = new JScrollPane(_previewTable);
-               previewScrollPane.setPreferredSize(new Dimension(300, 100));
-               card2.add(previewScrollPane, BorderLayout.CENTER);
-               _cards.add(card2, "card2");
-               mainPanel.add(_cards, BorderLayout.CENTER);
-
-               // Button panel at the bottom
-               JPanel buttonPanel = new JPanel();
-               _backButton = new JButton(I18nManager.getText("button.back"));
-               _backButton.addActionListener(new ActionListener()
-                       {
-                               public void actionPerformed(ActionEvent e)
-                               {
-                                       CardLayout cl = (CardLayout) _cards.getLayout();
-                                       cl.previous(_cards);
-                                       _backButton.setEnabled(false);
-                                       _nextButton.setEnabled(true);
-                                       _okButton.setEnabled(false);
-                                       _previewEnabled = false;
-                               }
-                       });
-               _backButton.setEnabled(false);
-               buttonPanel.add(_backButton);
-               _nextButton = new JButton(I18nManager.getText("button.next"));
-               _nextButton.addActionListener(new ActionListener()
-                       {
-                               public void actionPerformed(ActionEvent e)
-                               {
-                                       int rowNum = _photoSelectionTable.getSelectedRow();
-                                       if (rowNum < 0) {rowNum = 0;}
-                                       PhotoSelectionTableRow selectedRow = ((PhotoSelectionTableModel) _photoSelectionTable.getModel())
-                                               .getRow(rowNum);
-                                       setupSecondCard(selectedRow.getTimeDiff());
-                               }
-                       });
-               buttonPanel.add(_nextButton);
-               _okButton = new JButton(I18nManager.getText("button.ok"));
-               _okButton.addActionListener(new ActionListener()
-                       {
-                               public void actionPerformed(ActionEvent e)
-                               {
-                                       finishCorrelation();
-                                       _dialog.dispose();
-                               }
-                       });
-               _okButton.setEnabled(false);
-               buttonPanel.add(_okButton);
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener()
-                       {
-                               public void actionPerformed(ActionEvent e)
-                               {
-                                       _dialog.dispose();
-                               }
-                       });
-               buttonPanel.add(cancelButton);
-               mainPanel.add(buttonPanel, BorderLayout.SOUTH);
-               return mainPanel;
-       }
-
-
-       /**
-        * Construct a table model for the photo selection table
-        * @param inTrackInfo track info object
-        * @return table model
-        */
-       private static PhotoSelectionTableModel makePhotoSelectionTableModel(TrackInfo inTrackInfo)
-       {
-               PhotoSelectionTableModel model = new PhotoSelectionTableModel();
-               int numPhotos = inTrackInfo.getPhotoList().getNumPhotos();
-               for (int i=0; i<numPhotos; i++)
-               {
-                       Photo photo = inTrackInfo.getPhotoList().getPhoto(i);
-                       // For working out time differences, can't use photos which already had point information
-                       if (photo.getDataPoint() != null && photo.getDataPoint().hasTimestamp()
-                               && photo.getOriginalStatus() == Photo.Status.NOT_CONNECTED)
-                       {
-                               // Calculate time difference, add to table model
-                               long timeDiff = photo.getTimestamp().getSecondsSince(photo.getDataPoint().getTimestamp());
-                               model.addPhoto(photo, timeDiff);
-                       }
-               }
-               return model;
-       }
-
-
-       /**
-        * Group the two radio buttons together with a ButtonGroup
-        * @param inButton1 first radio button
-        * @param inButton2 second radio button
-        */
-       private static void groupRadioButtons(JRadioButton inButton1, JRadioButton inButton2)
-       {
-               ButtonGroup buttonGroup = new ButtonGroup();
-               buttonGroup.add(inButton1);
-               buttonGroup.add(inButton2);
-               inButton1.setSelected(true);
+       /** @return photo list*/
+       protected MediaList getMediaList() {
+               return _app.getTrackInfo().getPhotoList();
        }
 
-
-       /**
-        * Set up the second card using the given time difference and show it
-        * @param inTimeDiff time difference to use for photo time offsets
-        */
-       private void setupSecondCard(TimeDifference inTimeDiff)
-       {
-               _previewEnabled = false;
-               boolean hasTimeDiff = inTimeDiff != null;
-               if (!hasTimeDiff)
-               {
-                       // No time difference available, so calculate based on computer's time zone
-                       inTimeDiff = getTimezoneOffset();
-               }
-               // Use time difference to set edit boxes
-               _offsetHourBox.setText("" + inTimeDiff.getNumHours());
-               _offsetMinBox.setText("" + inTimeDiff.getNumMinutes());
-               _offsetSecBox.setText("" + inTimeDiff.getNumSeconds());
-               _photoLaterOption.setSelected(inTimeDiff.getIsPositive());
-               _pointLaterOption.setSelected(!inTimeDiff.getIsPositive());
-               createPreview(inTimeDiff, true);
-               CardLayout cl = (CardLayout) _cards.getLayout();
-               cl.last(_cards);
-               _backButton.setEnabled(hasTimeDiff);
-               _nextButton.setEnabled(false);
-               // enable ok button if any photos have been selected
-               _okButton.setEnabled(((PhotoPreviewTableModel) _previewTable.getModel()).hasPhotosSelected());
-               _previewEnabled = true;
-       }
-
-
-       /**
-        * Create a preview of the correlate action using the selected time difference
-        * @param inFromButton true if triggered from button press, false if automatic
-        */
-       public void createPreview(boolean inFromButton)
-       {
-               // Exit if still on first panel
-               if (!_previewEnabled) {return;}
-               // Create a TimeDifference based on the edit boxes
-               int numHours = getValue(_offsetHourBox.getText());
-               int numMins = getValue(_offsetMinBox.getText());
-               int numSecs = getValue(_offsetSecBox.getText());
-               boolean isPos = _photoLaterOption.isSelected();
-               createPreview(new TimeDifference(numHours, numMins, numSecs, isPos), inFromButton);
-       }
-
-
        /**
         * Create a preview of the correlate action using the selected time difference
         * @param inTimeDiff TimeDifference to use for preview
         * @param inShowWarning true to show warning if all points out of range
         */
-       private void createPreview(TimeDifference inTimeDiff, boolean inShowWarning)
+       protected void createPreview(TimeDifference inTimeDiff, boolean inShowWarning)
        {
                TimeDifference timeLimit = parseTimeLimit();
                double angDistLimit = parseDistanceLimit();
-               PhotoPreviewTableModel model = new PhotoPreviewTableModel();
+               MediaPreviewTableModel model = new MediaPreviewTableModel("dialog.correlate.select.photoname");
                PhotoList photos = _app.getTrackInfo().getPhotoList();
                // Loop through photos deciding whether to set correlate flag or not
                int numPhotos = photos.getNumPhotos();
                for (int i=0; i<numPhotos; i++)
                {
                        Photo photo = photos.getPhoto(i);
-                       PointPair pair = getPointPairForPhoto(_app.getTrackInfo().getTrack(), photo, inTimeDiff);
-                       PhotoPreviewTableRow row = new PhotoPreviewTableRow(pair);
+                       PointMediaPair pair = getPointPairForMedia(_app.getTrackInfo().getTrack(), photo, inTimeDiff);
+                       MediaPreviewTableRow row = new MediaPreviewTableRow(pair);
                        // Don't try to correlate photos which don't have points either side
                        boolean correlatePhoto = pair.isValid();
                        // Don't select photos which already have a point
@@ -453,7 +84,7 @@ public class PhotoCorrelator extends GenericFunction
                                correlatePhoto = false;
                        }
                        row.setCorrelateFlag(correlatePhoto);
-                       model.addPhotoRow(row);
+                       model.addRow(row);
                }
                _previewTable.setModel(model);
                // Set distance units
@@ -465,218 +96,22 @@ public class PhotoCorrelator extends GenericFunction
                        _previewTable.getColumnModel().getColumn(i).setPreferredWidth(colWidths[i]);
                }
                // check if any photos found
-               _okButton.setEnabled(model.hasPhotosSelected());
-               if (inShowWarning && !model.hasPhotosSelected())
+               _okButton.setEnabled(model.hasAnySelected());
+               if (inShowWarning && !model.hasAnySelected())
                {
                        JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.correlate.alloutsiderange"),
                                I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
                }
        }
 
-       /**
-        * Parse the time limit values entered and validate them
-        * @return TimeDifference object describing limit
-        */
-       private TimeDifference parseTimeLimit()
-       {
-               if (!_timeLimitRadio.isSelected()) {return null;}
-               int mins = getValue(_limitMinBox.getText());
-               _limitMinBox.setText("" + mins);
-               int secs = getValue(_limitSecBox.getText());
-               _limitSecBox.setText("" + secs);
-               if (mins <= 0 && secs <= 0) {return null;}
-               return new TimeDifference(0, mins, secs, true);
-       }
-
-       /**
-        * Parse the distance limit value entered and validate
-        * @return angular distance in radians
-        */
-       private double parseDistanceLimit()
-       {
-               double value = -1.0;
-               if (_distLimitRadio.isSelected())
-               {
-                       try
-                       {
-                               value = Double.parseDouble(_limitDistBox.getText());
-                       }
-                       catch (NumberFormatException nfe) {}
-               }
-               if (value <= 0.0) {
-                       _limitDistBox.setText("0");
-                       return -1.0;
-               }
-               _limitDistBox.setText("" + value);
-               return Distance.convertDistanceToRadians(value, getSelectedDistanceUnits());
-       }
-
-
-       /**
-        * @return the selected distance units from the dropdown
-        */
-       private Distance.Units getSelectedDistanceUnits()
-       {
-               final Distance.Units[] distUnits = {Distance.Units.KILOMETRES, Distance.Units.METRES, Distance.Units.MILES};
-               return distUnits[_distUnitsDropdown.getSelectedIndex()];
-       }
-
-
-       /**
-        * Try to parse the given string
-        * @param inText String to parse
-        * @return value if parseable, 0 otherwise
-        */
-       private static int getValue(String inText)
-       {
-               int value = 0;
-               try {
-                       value = Integer.parseInt(inText);
-               }
-               catch (NumberFormatException nfe) {}
-               return value;
-       }
-
-
-       /**
-        * Get the point pair surrounding the given photo
-        * @param inTrack track object
-        * @param inPhoto photo object
-        * @param inOffset time offset to apply to photos
-        * @return point pair resulting from correlation
-        */
-       private static PointPair getPointPairForPhoto(Track inTrack, Photo inPhoto, TimeDifference inOffset)
-       {
-               PointPair pair = new PointPair(inPhoto);
-               // Add/subtract offet to photo timestamp
-               Timestamp photoStamp = inPhoto.getTimestamp().createMinusOffset(inOffset);
-               int numPoints = inTrack.getNumPoints();
-               for (int i=0; i<numPoints; i++)
-               {
-                       DataPoint point = inTrack.getPoint(i);
-                       if (point.getPhoto() == null || point.getPhoto().getCurrentStatus() != Photo.Status.TAGGED)
-                       {
-                               Timestamp pointStamp = point.getTimestamp();
-                               if (pointStamp != null && pointStamp.isValid())
-                               {
-                                       long numSeconds = pointStamp.getSecondsSince(photoStamp);
-                                       pair.addPoint(point, numSeconds);
-                               }
-                       }
-               }
-               return pair;
-       }
-
-
-       /**
-        * Construct an array of the point pairs to use for correlation
-        * @return array of PointPair objects
-        */
-       private PointPair[] getPointPairs()
-       {
-               PhotoPreviewTableModel model = (PhotoPreviewTableModel) _previewTable.getModel();
-               int numPhotos = model.getRowCount();
-               PointPair[] pairs = new PointPair[numPhotos];
-               // Loop over photos in preview table model
-               for (int i=0; i<numPhotos; i++)
-               {
-                       PhotoPreviewTableRow row = model.getRow(i);
-                       // add all selected pairs to array (other elements remain null)
-                       if (row.getCorrelateFlag().booleanValue())
-                       {
-                               pairs[i] = row.getPointPair();
-                       }
-               }
-               return pairs;
-       }
-
-       /**
-        * @return time difference of local time zone from UTC when the first photo was taken
-        */
-       private TimeDifference getTimezoneOffset()
-       {
-               Calendar cal = null;
-               // Base time difference on DST when first photo was taken
-               Photo firstPhoto = _app.getTrackInfo().getPhotoList().getPhoto(0);
-               if (firstPhoto != null && firstPhoto.getTimestamp() != null) {
-                       cal = firstPhoto.getTimestamp().getCalendar();
-               }
-               else {
-                       // No photo or no timestamp, just use current time
-                       cal = Calendar.getInstance();
-               }
-               // Both time zone offset and dst offset are based on milliseconds, so convert to seconds
-               TimeDifference timeDiff = new TimeDifference((cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000);
-               return timeDiff;
-       }
-
-
-       /**
-        * Calculate the median index to select from the table
-        * @param inModel table model
-        * @return index of entry to select from table
-        */
-       private static int getMedianIndex(PhotoSelectionTableModel inModel)
-       {
-               // make sortable list
-               TreeSet<TimeIndexPair> set = new TreeSet<TimeIndexPair>();
-               // loop through rows of table adding to list
-               int numRows = inModel.getRowCount();
-               int i;
-               for (i=0; i<numRows; i++)
-               {
-                       PhotoSelectionTableRow row = inModel.getRow(i);
-                       set.add(new TimeIndexPair(row.getTimeDiff().getTotalSeconds(), i));
-               }
-               // pull out middle entry and return index
-               TimeIndexPair pair = null;
-               Iterator<TimeIndexPair> iterator = set.iterator();
-               for (i=0; i<(numRows+1)/2; i++)
-               {
-                       pair = iterator.next();
-               }
-               return pair.getIndex();
-       }
-
-
-       /**
-        * Disable the ok button
-        */
-       public void disableOkButton()
-       {
-               if (_okButton != null) {
-                       _okButton.setEnabled(false);
-               }
-       }
-
-
-       /**
-        * Check if the track has any uncorrelated photos
-        * @return true if there are any photos which are not connected to points
-        */
-       private boolean trackHasUncorrelatedPhotos()
-       {
-               PhotoList photoList = _app.getTrackInfo().getPhotoList();
-               int numPhotos = photoList.getNumPhotos();
-               // loop over photos
-               for (int i=0; i<numPhotos; i++)
-               {
-                       Photo photo = photoList.getPhoto(i);
-                       if (photo != null && photo.getDataPoint() == null) {
-                               return true;
-                       }
-               }
-               // no uncorrelated photos found
-               return false;
-       }
 
        /**
         * Finish the correlation by modifying the track
         * and passing the Undo information back to the App
         */
-       private void finishCorrelation()
+       protected void finishCorrelation()
        {
-               PointPair[] pointPairs = getPointPairs();
+               PointMediaPair[] pointPairs = getPointPairs();
                if (pointPairs == null || pointPairs.length <= 0) {return;}
 
                // begin to construct undo information
@@ -685,7 +120,7 @@ public class PhotoCorrelator extends GenericFunction
                int arraySize = pointPairs.length;
                int i = 0, numPhotos = 0;
                int numPointsToCreate = 0;
-               PointPair pair = null;
+               PointMediaPair pair = null;
                for (i=0; i<arraySize; i++)
                {
                        pair = pointPairs[i];
@@ -698,10 +133,10 @@ public class PhotoCorrelator extends GenericFunction
                                        if (pointPhoto == null)
                                        {
                                                // photo coincides with photoless point so connect the two
-                                               pair.getPointBefore().setPhoto(pair.getPhoto());
-                                               pair.getPhoto().setDataPoint(pair.getPointBefore());
+                                               pair.getPointBefore().setPhoto((Photo) pair.getMedia());
+                                               pair.getMedia().setDataPoint(pair.getPointBefore());
                                        }
-                                       else if (pointPhoto.equals(pair.getPhoto())) {
+                                       else if (pointPhoto.equals(pair.getMedia())) {
                                                // photo is already connected, nothing to do
                                        }
                                        else {
@@ -731,7 +166,7 @@ public class PhotoCorrelator extends GenericFunction
                                {
                                        pointToAdd = null;
                                        if (pair.getMinSeconds() == 0L && pair.getPointBefore().getPhoto() != null
-                                        && !pair.getPointBefore().getPhoto().equals(pair.getPhoto()))
+                                        && !pair.getPointBefore().getPhoto().equals(pair.getMedia()))
                                        {
                                                // clone point
                                                pointToAdd = pair.getPointBefore().clonePoint();
@@ -744,8 +179,8 @@ public class PhotoCorrelator extends GenericFunction
                                        if (pointToAdd != null)
                                        {
                                                // link photo to point
-                                               pointToAdd.setPhoto(pair.getPhoto());
-                                               pair.getPhoto().setDataPoint(pointToAdd);
+                                               pointToAdd.setPhoto((Photo) pair.getMedia());
+                                               pair.getMedia().setDataPoint(pointToAdd);
                                                // set to start of segment so not joined in track
                                                pointToAdd.setSegmentStart(true);
                                                // add to point array
@@ -761,7 +196,10 @@ public class PhotoCorrelator extends GenericFunction
                // send undo information back to controlling app
                undo.setNumPhotosCorrelated(numPhotos);
                _app.completeFunction(undo, ("" + numPhotos + " "
-                        + (numPhotos==1?I18nManager.getText("confirm.correlate.single"):I18nManager.getText("confirm.correlate.multi"))));
-               // observers already informed by track update
+                        + (numPhotos==1?I18nManager.getText("confirm.correlatephotos.single"):I18nManager.getText("confirm.correlatephotos.multi"))));
+               // observers already informed by track update if new points created
+               if (numPointsToCreate == 0) {
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+               }
        }
 }
diff --git a/tim/prune/correlate/PhotoSelectionTableModel.java b/tim/prune/correlate/PhotoSelectionTableModel.java
deleted file mode 100644 (file)
index 4472e6b..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package tim.prune.correlate;
-
-import java.util.ArrayList;
-import javax.swing.table.AbstractTableModel;
-
-import tim.prune.I18nManager;
-import tim.prune.data.Photo;
-
-/**
- * Class to act as table model for the photo selection table
- */
-public class PhotoSelectionTableModel extends AbstractTableModel
-{
-       private ArrayList<PhotoSelectionTableRow> _list = new ArrayList<PhotoSelectionTableRow>();
-
-
-       /**
-        * @return the column count, always 4
-        */
-       public int getColumnCount()
-       {
-               return 4;
-       }
-
-
-       /**
-        * Get the name of the column
-        * @param inColNum column number
-        * @return column name
-        */
-       public String getColumnName(int inColNum)
-       {
-               if (inColNum == 0) return I18nManager.getText("dialog.correlate.photoselect.photoname");
-               else if (inColNum == 1) return I18nManager.getText("fieldname.timestamp");
-               else if (inColNum == 2) return I18nManager.getText("dialog.correlate.photoselect.timediff");
-               return I18nManager.getText("dialog.correlate.photoselect.photolater");
-       }
-
-
-       /**
-        * @return the row count
-        */
-       public int getRowCount()
-       {
-               return _list.size();
-       }
-
-
-       /**
-        * Get the selected row from the table
-        * @param inRowIndex row index
-        * @return table row object
-        */
-       public PhotoSelectionTableRow getRow(int inRowIndex)
-       {
-               return _list.get(inRowIndex);
-       }
-
-
-       /**
-        * Get the value of the specified cell
-        * @param inRowIndex row index
-        * @param inColumnIndex column index
-        * @return value of specified cell
-        */
-       public Object getValueAt(int inRowIndex, int inColumnIndex)
-       {
-               // MAYBE: only show time of photos (not date) if dates all identical
-               PhotoSelectionTableRow row = _list.get(inRowIndex);
-               if (inColumnIndex == 0) return row.getPhoto().getFile().getName();
-               else if (inColumnIndex == 1) return row.getPhoto().getTimestamp().getText();
-               else if (inColumnIndex == 2) return row.getTimeDiff().getDescription();
-               return (row.getTimeDiff().getIsPositive() ? I18nManager.getText("dialog.about.yes") :
-                       I18nManager.getText("dialog.about.no"));
-       }
-
-
-       /**
-        * Clear the list
-        */
-       public void reset()
-       {
-               _list.clear();
-       }
-
-       /**
-        * Add a photo to the list
-        * @param inPhoto photo to add
-        * @param inTimeDiff time difference
-        */
-       public void addPhoto(Photo inPhoto, long inTimeDiff)
-       {
-               _list.add(new PhotoSelectionTableRow(inPhoto, inTimeDiff));
-       }
-}
diff --git a/tim/prune/correlate/PhotoSelectionTableRow.java b/tim/prune/correlate/PhotoSelectionTableRow.java
deleted file mode 100644 (file)
index e818bbe..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package tim.prune.correlate;
-
-import tim.prune.data.Photo;
-import tim.prune.data.TimeDifference;
-
-/**
- * Class to hold contents of a single row
- * in the photo selection table
- */
-public class PhotoSelectionTableRow
-{
-       private Photo _photo = null;
-       private TimeDifference _timeDiff = null;
-
-       /**
-        * Constructor
-        * @param inPhoto Photo object
-        * @param inNumSecs number of seconds time difference as long
-        */
-       public PhotoSelectionTableRow(Photo inPhoto, long inNumSecs)
-       {
-               _photo = inPhoto;
-               _timeDiff = new TimeDifference(inNumSecs);
-       }
-
-       /**
-        * @return Photo object
-        */
-       public Photo getPhoto()
-       {
-               return _photo;
-       }
-
-       /**
-        * @return time difference
-        */
-       public TimeDifference getTimeDiff()
-       {
-               return _timeDiff;
-       }
-}
similarity index 63%
rename from tim/prune/correlate/PointPair.java
rename to tim/prune/correlate/PointMediaPair.java
index 9b27ff927503f372164a7ba50f803086cda55f95..1ea83078dab88188be12befdb2df8be750e397c5 100644 (file)
@@ -1,15 +1,14 @@
 package tim.prune.correlate;
 
 import tim.prune.data.DataPoint;
-import tim.prune.data.Photo;
+import tim.prune.data.MediaFile;
 
 /**
- * Class to hold a pair of points
- * used to hold the result of correlation of a photo
+ * Class to hold a pair of points used to hold the result of correlation
  */
-public class PointPair
+public class PointMediaPair
 {
-       private Photo _photo = null;
+       private MediaFile _media = null;
        private DataPoint _pointBefore = null;
        private DataPoint _pointAfter = null;
        private long _secondsBefore = 1L;
@@ -18,14 +17,12 @@ public class PointPair
 
        /**
         * Constructor
-        * @param inPhoto Photo object
+        * @param inMedia media object
         */
-       public PointPair(Photo inPhoto)
-       {
-               _photo = inPhoto;
+       public PointMediaPair(MediaFile inMedia) {
+               _media = inMedia;
        }
 
-
        /**
         * Add a point to the pair
         * @param inPoint data point
@@ -36,10 +33,10 @@ public class PointPair
                // Check if point is closest point before
                if (inSeconds <= 0)
                {
-                       // point stamp is before photo stamp
+                       // point stamp is before media stamp
                        if (inSeconds > _secondsBefore || _secondsBefore > 0L)
                        {
-                               // point stamp is nearer to photo
+                               // point stamp is nearer to media
                                _pointBefore = inPoint;
                                _secondsBefore = inSeconds;
                        }
@@ -47,10 +44,10 @@ public class PointPair
                // Check if point is closest point after
                if (inSeconds >= 0)
                {
-                       // point stamp is after photo stamp
+                       // point stamp is after media stamp
                        if (inSeconds < _secondsAfter || _secondsAfter < 0L)
                        {
-                               // point stamp is nearer to photo
+                               // point stamp is nearer to media
                                _pointAfter = inPoint;
                                _secondsAfter = inSeconds;
                        }
@@ -59,50 +56,44 @@ public class PointPair
 
 
        /**
-        * @return Photo object
+        * @return Media object
         */
-       public Photo getPhoto()
-       {
-               return _photo;
+       public MediaFile getMedia() {
+               return _media;
        }
 
        /**
-        * @return the closest point before the photo
+        * @return the closest point before the media
         */
-       public DataPoint getPointBefore()
-       {
+       public DataPoint getPointBefore() {
                return _pointBefore;
        }
 
        /**
-        * @return number of seconds between photo and subsequent point
+        * @return number of seconds between media and subsequent point
         */
-       public long getSecondsBefore()
-       {
+       public long getSecondsBefore() {
                return _secondsBefore;
        }
 
        /**
-        * @return the closest point after the photo
+        * @return the closest point after the media
         */
-       public DataPoint getPointAfter()
-       {
+       public DataPoint getPointAfter() {
                return _pointAfter;
        }
 
        /**
-        * @return number of seconds between previous point and photo
+        * @return number of seconds between previous point and media
         */
-       public long getSecondsAfter()
-       {
+       public long getSecondsAfter() {
                return _secondsAfter;
        }
 
        /**
         * @return true if both points found
         */
-       public boolean isValid()
-       {
+       public boolean isValid() {
                return getPointBefore() != null && getPointAfter() != null;
        }
 
@@ -118,13 +109,12 @@ public class PointPair
        /**
         * @return the number of seconds to the nearest point
         */
-       public long getMinSeconds()
-       {
+       public long getMinSeconds() {
                return Math.min(_secondsAfter, -_secondsBefore);
        }
 
        /**
-        * @return angle from photo to nearest point in radians
+        * @return angle from media to nearest point in radians
         */
        public double getMinRadians()
        {
diff --git a/tim/prune/data/AudioFile.java b/tim/prune/data/AudioFile.java
new file mode 100644 (file)
index 0000000..43089ab
--- /dev/null
@@ -0,0 +1,45 @@
+package tim.prune.data;
+
+import java.io.File;
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioSystem;
+
+/**
+ * Class to represent an audio file for correlation
+ */
+public class AudioFile extends MediaFile
+{
+       /** length of current audio file in seconds */
+       private int _lengthInSeconds = LENGTH_UNKNOWN;
+
+       private static final int LENGTH_UNKNOWN = -1;
+       private static final int LENGTH_NOT_AVAILABLE = -2;
+
+       /**
+        * Constructor
+        * @param inFile file object
+        */
+       public AudioFile(File inFile)
+       {
+               // Timestamp is always just taken from the file modification stamp
+               super(inFile, new Timestamp(inFile.lastModified()));
+       }
+
+       /**
+        * @return length of this audio file in seconds
+        */
+       public int getLengthInSeconds()
+       {
+               if (_lengthInSeconds == LENGTH_UNKNOWN)
+               {
+                       try {
+                               AudioFileFormat format = AudioSystem.getAudioFileFormat(getFile());
+                               _lengthInSeconds = (int) (format.getFrameLength() / format.getFormat().getFrameRate());
+                       }
+                       catch (Exception e) {
+                               _lengthInSeconds = LENGTH_NOT_AVAILABLE;
+                       }
+               }
+               return _lengthInSeconds;
+       }
+}
diff --git a/tim/prune/data/AudioList.java b/tim/prune/data/AudioList.java
new file mode 100644 (file)
index 0000000..928f2b9
--- /dev/null
@@ -0,0 +1,99 @@
+package tim.prune.data;
+
+import java.util.ArrayList;
+
+/**
+ * Class to hold a list of audio files, using the MediaList superclass
+ */
+public class AudioList extends MediaList
+{
+       /**
+        * Empty constructor
+        */
+       public AudioList() {
+               this(null);
+       }
+
+       /**
+        * Constructor
+        * @param inList ArrayList containing audio file objects
+        */
+       private AudioList(ArrayList<MediaFile> inList) {
+               super(inList);
+       }
+
+       /**
+        * @return clone of list contents
+        */
+       public AudioList cloneList()
+       {
+               if (getNumMedia() == 0) return this;
+               ArrayList<MediaFile> listCopy = new ArrayList<MediaFile>();
+               listCopy.addAll(_media);
+               return new AudioList(listCopy);
+       }
+
+       /**
+        * @return the number of audio files in the list
+        */
+       public int getNumAudios() {
+               return getNumMedia();
+       }
+
+       /**
+        * Add an audio file to the list
+        * @param inAudio object to add
+        */
+       public void addAudio(AudioFile inAudio) {
+               addMedia(inAudio);
+       }
+
+       /**
+        * Add an audio file to the list
+        * @param inAudio object to add
+        * @param inIndex index at which to add
+        */
+       public void addAudio(AudioFile inAudio, int inIndex) {
+               addMedia(inAudio, inIndex);
+       }
+
+       /**
+        * Remove the selected audio file from the list
+        * @param inIndex index number to remove
+        */
+       public void deleteAudio(int inIndex) {
+               deleteMedia(inIndex);
+       }
+
+       /**
+        * Get the index of the given audio file
+        * @param inAudio object to check
+        * @return index of this object in the list, or -1 if not found
+        */
+       public int getAudioIndex(AudioFile inAudio) {
+               return getMediaIndex(inAudio);
+       }
+
+       /**
+        * Get the Audio object at the given index
+        * @param inIndex index number, starting at 0
+        * @return specified object
+        */
+       public AudioFile getAudio(int inIndex) {
+               return (AudioFile) getMedia(inIndex);
+       }
+
+       /**
+        * @return true if list contains correlated objects
+        */
+       public boolean hasCorrelatedAudios() {
+               return hasCorrelatedMedia();
+       }
+
+       /**
+        * Remove all correlated media from the list
+        */
+       public void removeCorrelatedAudios() {
+               removeCorrelatedMedia();
+       }
+}
index bba7adc670f90078a8deb0a3280b6fa109ef8a39..e053e1ad14405add6a237b4e748884c8575fe3c4 100644 (file)
@@ -17,7 +17,10 @@ public class DataPoint
        private Coordinate _latitude = null, _longitude = null;
        private Altitude _altitude;
        private Timestamp _timestamp = null;
+       /** Attached photo */
        private Photo _photo = null;
+       /** Attached audio file */
+       private AudioFile _audio = null;
        private String _waypointName = null;
        private boolean _startOfSegment = false;
        private boolean _markedForDeletion = false;
@@ -299,20 +302,51 @@ public class DataPoint
         * Set the photo for this data point
         * @param inPhoto Photo object
         */
-       public void setPhoto(Photo inPhoto)
-       {
+       public void setPhoto(Photo inPhoto) {
                _photo = inPhoto;
+               _modifyCount++;
        }
 
-
        /**
         * @return associated Photo object
         */
-       public Photo getPhoto()
-       {
+       public Photo getPhoto() {
                return _photo;
        }
 
+       /**
+        * Set the audio file for this point
+        * @param inAudio audio object
+        */
+       public void setAudio(AudioFile inAudio) {
+               _audio = inAudio;
+               _modifyCount++;
+       }
+
+       /**
+        * @return associated audio object
+        */
+       public AudioFile getAudio() {
+               return _audio;
+       }
+
+       /**
+        * Attach the given media object according to type
+        * @param inMedia either a photo or an audio file
+        */
+       public void attachMedia(MediaFile inMedia)
+       {
+               if (inMedia != null) {
+                       if (inMedia instanceof Photo) {
+                               setPhoto((Photo) inMedia);
+                               inMedia.setDataPoint(this);
+                       }
+                       else if (inMedia instanceof AudioFile) {
+                               setAudio((AudioFile) inMedia);
+                               inMedia.setDataPoint(this);
+                       }
+               }
+       }
 
        /**
         * @return true if the point is valid
@@ -322,6 +356,13 @@ public class DataPoint
                return _latitude.isValid() && _longitude.isValid();
        }
 
+       /**
+        * @return true if the point has either a photo or audio attached
+        */
+       public boolean hasMedia() {
+               return _photo != null || _audio != null;
+       }
+
        /**
         * Interpolate a set of points between this one and the given one
         * @param inEndPoint end point of interpolation
index 5d0adc4563d25fd5465465226958eb59ebded0ca..fa4600d8888fe7f4c85dd3f0a25da7414cdc9205 100644 (file)
@@ -4,41 +4,44 @@ import tim.prune.I18nManager;
 
 /**
  * Class to represent a field of a data point
- * including its type
  */
 public class Field
 {
        private String _labelKey = null;
        private String _customLabel = null;
-       private FieldType _type = null;
        private boolean _builtin = false;
 
-       public static final Field LATITUDE = new Field("fieldname.latitude", FieldType.COORD);
-       public static final Field LONGITUDE = new Field("fieldname.longitude", FieldType.COORD);
-       public static final Field ALTITUDE = new Field("fieldname.altitude", FieldType.INT);
-       public static final Field TIMESTAMP = new Field("fieldname.timestamp", FieldType.TIME);
-       public static final Field WAYPT_NAME = new Field("fieldname.waypointname", FieldType.NONE);
-       public static final Field WAYPT_TYPE = new Field("fieldname.waypointtype", FieldType.NONE);
-       public static final Field NEW_SEGMENT = new Field("fieldname.newsegment", FieldType.BOOL);
+       public static final Field LATITUDE = new Field("fieldname.latitude", true);
+       public static final Field LONGITUDE = new Field("fieldname.longitude", true);
+       public static final Field ALTITUDE = new Field("fieldname.altitude", true);
+       public static final Field TIMESTAMP = new Field("fieldname.timestamp", true);
+       public static final Field WAYPT_NAME = new Field("fieldname.waypointname", true);
+       public static final Field WAYPT_TYPE = new Field("fieldname.waypointtype", true);
+       public static final Field NEW_SEGMENT = new Field("fieldname.newsegment", true);
 
        // TODO: Field for photo filename, ability to load (from text) and save (to text)
 
        private static final Field[] ALL_AVAILABLE_FIELDS = {
                LATITUDE, LONGITUDE, ALTITUDE, TIMESTAMP, WAYPT_NAME, WAYPT_TYPE, NEW_SEGMENT,
-               new Field("fieldname.custom", FieldType.NONE)
+               new Field(I18nManager.getText("fieldname.custom"))
        };
 
        /**
         * Private constructor
         * @param inLabelKey Key for label texts
-        * @param inType type of field
+        * @param inBuiltin true for built-in types, false for custom
         */
-       private Field(String inLabelKey, FieldType inType)
+       private Field(String inLabelKey, boolean inBuiltin)
        {
-               _labelKey = inLabelKey;
-               _customLabel = null;
-               _type = inType;
-               _builtin = true;
+               if (inBuiltin) {
+                       _labelKey = inLabelKey;
+                       _customLabel = null;
+               }
+               else {
+                       _labelKey = null;
+                       _customLabel = inLabelKey;
+               }
+               _builtin = inBuiltin;
        }
 
 
@@ -48,9 +51,7 @@ public class Field
         */
        public Field(String inLabel)
        {
-               _labelKey = null;
-               _customLabel = inLabel;
-               _type = FieldType.NONE;
+               this(inLabel, false);
        }
 
        /**
@@ -80,14 +81,6 @@ public class Field
                return _builtin;
        }
 
-       /**
-        * @return field type
-        */
-       public FieldType getType()
-       {
-               return _type;
-       }
-
        /**
         * Checks if the two fields are equal
         * @param inOther other Field object
@@ -105,7 +98,8 @@ public class Field
         */
        public static Field getField(String inFieldName)
        {
-               for (int i=0; i<ALL_AVAILABLE_FIELDS.length; i++) {
+               for (int i=0; i<ALL_AVAILABLE_FIELDS.length; i++)
+               {
                        Field field = ALL_AVAILABLE_FIELDS[i];
                        if (field.getName().equals(inFieldName)) {
                                return field;
index 1542f09eab32e593ef1522f49d518e4ac87e2dcc..26ac966a28a94eff7f45ca953b07f08d36545106 100644 (file)
@@ -162,12 +162,12 @@ public class FieldList
        public String toString()
        {
                StringBuffer buffer = new StringBuffer();
-               buffer.append("(");
+               buffer.append('(');
                for (int i=0; i<_fieldArray.length; i++)
                {
                        buffer.append(_fieldArray[i].getName()).append(',');
                }
-               buffer.append(")");
+               buffer.append(')');
                return buffer.toString();
        }
 }
diff --git a/tim/prune/data/FieldType.java b/tim/prune/data/FieldType.java
deleted file mode 100644 (file)
index a92748a..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package tim.prune.data;
-
-/**
- * Class to represent a type of field,
- * for example coordinate or integer
- */
-public class FieldType
-{
-       private char _id = 0;
-
-       public static final FieldType NONE =  new FieldType('0');
-       public static final FieldType INT =   new FieldType('1');
-       public static final FieldType BOOL =  new FieldType('2');
-       public static final FieldType COORD = new FieldType('3');
-       public static final FieldType TIME =  new FieldType('4');
-
-
-       /**
-        * Private constructor
-        * @param inId identifier
-        */
-       private FieldType(char inId)
-       {
-               _id = inId;
-       }
-
-       /**
-        * Method only needed to avoid compiler warnings
-        * @return id
-        */
-       protected char getId() {
-               return _id;
-       }
-}
diff --git a/tim/prune/data/MediaFile.java b/tim/prune/data/MediaFile.java
new file mode 100644 (file)
index 0000000..d860db8
--- /dev/null
@@ -0,0 +1,157 @@
+package tim.prune.data;
+
+import java.io.File;
+
+/**
+ * Class to represent a general media file for correlation.
+ * Subclasses are currently Photo and AudioFile
+ */
+public abstract class MediaFile
+{
+       /** File where media is stored */
+       protected File _file = null;
+       /** Timestamp, if any */
+       protected Timestamp _timestamp = null;
+       /** Associated DataPoint if correlated */
+       protected DataPoint _dataPoint = null;
+       /** Status when loaded */
+       private Status _originalStatus = Status.NOT_CONNECTED;
+       /** Current status */
+       private Status _currentStatus = Status.NOT_CONNECTED;
+
+       /** Connection status */
+       public enum Status {
+               /** Media is not connected to any point */
+               NOT_CONNECTED,
+               /** Media has been connected to a point since it was loaded */
+               TAGGED,
+               /** Media is connected to a point */
+               CONNECTED
+       };
+
+
+       /**
+        * Constructor
+        * @param inFile file object
+        * @param inStamp timestamp object
+        */
+       public MediaFile(File inFile, Timestamp inStamp)
+       {
+               _file = inFile;
+               _timestamp = inStamp;
+       }
+
+       /**
+        * @return the file object
+        */
+       public File getFile() {
+               return _file;
+       }
+
+       /**
+        * @return the timestamp object
+        */
+       public Timestamp getTimestamp() {
+               return _timestamp;
+       }
+
+       /**
+        * @param inTimestamp Timestamp object
+        */
+       public void setTimestamp(Timestamp inTimestamp) {
+               _timestamp = inTimestamp;
+       }
+
+       /**
+        * @return true if details are valid (might not have timestamp)
+        */
+       public boolean isValid() {
+               return _file != null && _file.exists() && _file.canRead();
+       }
+
+       /**
+        * @return true if file has timestamp
+        */
+       public boolean hasTimestamp() {
+                return _timestamp != null && _timestamp.isValid();
+       }
+
+       /**
+        * Check if this object refers to the same File as another
+        * @param inOther other MediaFile object
+        * @return true if the Files are the same
+        */
+       public boolean equals(MediaFile inOther)
+       {
+               return (inOther != null && inOther.getFile() != null && getFile() != null
+                       && inOther.getFile().equals(getFile()));
+       }
+
+       /**
+        * Set the data point associated with the photo
+        * @param inPoint DataPoint with coordinates etc
+        */
+       public void setDataPoint(DataPoint inPoint)
+       {
+               _dataPoint = inPoint;
+               // set status according to point
+               if (inPoint == null) {
+                       setCurrentStatus(Status.NOT_CONNECTED);
+               }
+               else {
+                       setCurrentStatus(Status.CONNECTED);
+               }
+       }
+
+       /**
+        * @return the DataPoint object
+        */
+       public DataPoint getDataPoint() {
+               return _dataPoint;
+       }
+
+       /**
+        * @param inStatus status of file when loaded
+        */
+       public void setOriginalStatus(Status inStatus)
+       {
+               _originalStatus = inStatus;
+               _currentStatus = inStatus;
+       }
+
+       /**
+        * @return status of file when it was loaded
+        */
+       public Status getOriginalStatus()
+       {
+               return _originalStatus;
+       }
+
+       /**
+        * @return current status
+        */
+       public Status getCurrentStatus()
+       {
+               return _currentStatus;
+       }
+       /**
+        * @param inStatus current status
+        */
+       public void setCurrentStatus(Status inStatus)
+       {
+               _currentStatus = inStatus;
+       }
+
+       /**
+        * @return true if file is connected to a point
+        */
+       public boolean isConnected()
+       {
+               return _currentStatus != Status.NOT_CONNECTED;
+       }
+
+       /**
+        * Reset any cached data held by the media file (eg thumbnail)
+        */
+       public void resetCachedData() {}
+}
diff --git a/tim/prune/data/MediaList.java b/tim/prune/data/MediaList.java
new file mode 100644 (file)
index 0000000..3fc87fe
--- /dev/null
@@ -0,0 +1,227 @@
+package tim.prune.data;
+
+import java.util.ArrayList;
+
+/**
+ * Class to hold a list of Media, either Photos or Audio files
+ */
+public abstract class MediaList
+{
+       /** list of media file objects */
+       protected ArrayList<MediaFile> _media = null;
+
+
+       /**
+        * Empty constructor
+        */
+       public MediaList() {
+               this(null);
+       }
+
+       /**
+        * Constructor
+        * @param inList ArrayList containing media objects
+        */
+       protected MediaList(ArrayList<MediaFile> inList)
+       {
+               _media = inList;
+               if (_media == null) {
+                       _media = new ArrayList<MediaFile>();
+               }
+       }
+
+       /**
+        * @return the number of media in the list
+        */
+       public int getNumMedia() {
+               return _media.size();
+       }
+
+       /**
+        * Add an object to the list
+        * @param inObject object to add
+        */
+       public void addMedia(MediaFile inObject)
+       {
+               if (inObject != null) {
+                       _media.add(inObject);
+               }
+       }
+
+       /**
+        * Add an object to the list at a specified index (used for undo)
+        * @param inObject object to add
+        * @param inIndex index at which to add
+        */
+       public void addMedia(MediaFile inObject, int inIndex)
+       {
+               if (inObject != null) {
+                       _media.add(inIndex, inObject);
+               }
+       }
+
+
+       /**
+        * Remove the selected media from the list
+        * @param inIndex index number to remove
+        */
+       public void deleteMedia(int inIndex)
+       {
+               // Maybe throw exception if this fails?
+               _media.remove(inIndex);
+       }
+
+
+       /**
+        * Checks if the specified object is already in the list
+        * @param inMedia media object to check
+        * @return true if it's already in the list
+        */
+       public boolean contains(MediaFile inMedia) {
+               return (getMediaIndex(inMedia) > -1);
+       }
+
+
+       /**
+        * Get the index of the given media
+        * @param inMedia object to check
+        * @return index of this object in the list, or -1 if not found
+        */
+       public int getMediaIndex(MediaFile inMedia)
+       {
+               // Check if we need to check
+               final int num = getNumMedia();
+               if (num <= 0 || inMedia == null || inMedia.getFile() == null)
+                       return -1;
+               // Loop over list
+               for (int i=0; i<num; i++)
+               {
+                       MediaFile m = _media.get(i);
+                       if (m != null && m.equals(inMedia)) {
+                               return i;
+                       }
+               }
+               // not found
+               return -1;
+       }
+
+
+       /**
+        * Get the media at the given index
+        * @param inIndex index number, starting at 0
+        * @return specified object
+        */
+       public MediaFile getMedia(int inIndex)
+       {
+               if (inIndex < 0 || inIndex >= getNumMedia()) return null;
+               return _media.get(inIndex);
+       }
+
+
+       /**
+        * Crop the list to the specified size
+        * @param inIndex previous size
+        */
+       public void cropTo(int inIndex)
+       {
+               if (inIndex <= 0)
+               {
+                       // delete whole list
+                       _media.clear();
+               }
+               else
+               {
+                       // delete to previous size
+                       while (_media.size() > inIndex) {
+                               _media.remove(_media.size()-1);
+                       }
+               }
+       }
+
+
+       /**
+        * @return array of file names
+        */
+       public String[] getNameList()
+       {
+               final int num = getNumMedia();
+               String[] names = new String[num];
+               for (int i=0; i<num; i++) {
+                       names[i] = getMedia(i).getFile().getName();
+               }
+               return names;
+       }
+
+
+       /**
+        * @return true if list contains correlated media
+        */
+       public boolean hasCorrelatedMedia()
+       {
+               for (MediaFile m : _media) {
+                       if (m.getDataPoint() != null) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * @return true if list contains uncorrelated media
+        */
+       public boolean hasUncorrelatedMedia()
+       {
+               for (MediaFile m : _media) {
+                       if (m.getDataPoint() == null) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+
+       /**
+        * Remove all correlated media from the list
+        */
+       public void removeCorrelatedMedia()
+       {
+               if (getNumMedia() > 0)
+               {
+                       // Construct new list to copy into
+                       ArrayList<MediaFile> listCopy = new ArrayList<MediaFile>();
+                       // Loop over list
+                       for (MediaFile m : _media)
+                       {
+                               // Copy media if it has no point
+                               if (m != null)
+                               {
+                                       if (m.getDataPoint() == null)
+                                               listCopy.add(m);
+                                       else
+                                               m.resetCachedData();
+                               }
+                       }
+                       // Switch reference to new list
+                       _media = listCopy;
+               }
+       }
+
+       /**
+        * @return clone of list contents
+        */
+       public abstract MediaList cloneList();
+
+       /**
+        * Restore contents from other MediaList
+        * @param inOther MediaList with cloned contents
+        */
+       public void restore(MediaList inOther)
+       {
+               _media.clear();
+               if (inOther != null && inOther.getNumMedia() > 0)
+               {
+                       // Copy contents from other list
+                       _media.addAll(inOther._media);
+               }
+       }
+}
index 7ffe35dec984511a6de8d7e522cb1aa25b4c7932..c9046743a704ca2629e8341ebef1d4497f878384 100644 (file)
@@ -8,93 +8,23 @@ import javax.swing.ImageIcon;
 /**
  * Class to represent a photo and link to DataPoint
  */
-public class Photo
+public class Photo extends MediaFile
 {
-       /** File where photo is stored */
-       private File _file = null;
-       /** Timestamp, if any */
-       private Timestamp _timestamp = null;
-       /** Associated DataPoint if correlated */
-       private DataPoint _dataPoint = null;
        /** Size of original image */
        private Dimension _size = null;
-       /** Status of photo when loaded */
-       private Status _originalStatus = Status.NOT_CONNECTED;
-       /** Current photo status */
-       private Status _currentStatus = Status.NOT_CONNECTED;
        /** rotation flag (clockwise from 0 to 3) */
        private int _rotation = 0;
        // TODO: Need to store caption for image?
        // thumbnail for image (from exif)
        private byte[] _exifThumbnail = null;
 
-       /** Photo status */
-       public enum Status {
-               /** Photo is not connected to any point */
-               NOT_CONNECTED,
-               /** Photo has been connected to a point since it was loaded */
-               TAGGED,
-               /** Photo is connected to a point */
-               CONNECTED
-       };
-
        /**
         * Constructor
         * @param inFile File object for photo
         */
        public Photo(File inFile)
        {
-               _file = inFile;
-       }
-
-
-       /**
-        * @return File object where photo resides
-        */
-       public File getFile()
-       {
-               return _file;
-       }
-
-
-       /**
-        * Set the data point associated with the photo
-        * @param inPoint DataPoint with coordinates etc
-        */
-       public void setDataPoint(DataPoint inPoint)
-       {
-               _dataPoint = inPoint;
-               // set status according to point
-               if (inPoint == null) {
-                       setCurrentStatus(Status.NOT_CONNECTED);
-               }
-               else {
-                       setCurrentStatus(Status.CONNECTED);
-               }
-       }
-
-       /**
-        * @return the DataPoint object
-        */
-       public DataPoint getDataPoint()
-       {
-               return _dataPoint;
-       }
-
-       /**
-        * @param inTimestamp Timestamp of photo
-        */
-       public void setTimestamp(Timestamp inTimestamp)
-       {
-               _timestamp = inTimestamp;
-       }
-
-       /**
-        * @return timestamp of photo
-        */
-       public Timestamp getTimestamp()
-       {
-               return _timestamp;
+               super(inFile, null);
        }
 
        /**
@@ -116,8 +46,7 @@ public class Photo
         */
        public Dimension getSize()
        {
-               if (_size == null)
-               {
+               if (_size == null) {
                        calculateSize();
                }
                return _size;
@@ -149,46 +78,6 @@ public class Photo
                return _size.height;
        }
 
-       /**
-        * @param inStatus status of photo when loaded
-        */
-       public void setOriginalStatus(Status inStatus)
-       {
-               _originalStatus = inStatus;
-               _currentStatus = inStatus;
-       }
-
-       /**
-        * @return status of photo when it was loaded
-        */
-       public Status getOriginalStatus()
-       {
-               return _originalStatus;
-       }
-
-       /**
-        * @return current status of photo
-        */
-       public Status getCurrentStatus()
-       {
-               return _currentStatus;
-       }
-       /**
-        * @param inStatus current status of photo
-        */
-       public void setCurrentStatus(Status inStatus)
-       {
-               _currentStatus = inStatus;
-       }
-
-       /**
-        * @return true if photo is connected to a point
-        */
-       public boolean isConnected()
-       {
-               return _currentStatus != Status.NOT_CONNECTED;
-       }
-
        /**
         * @return byte array of thumbnail data
         */
@@ -214,17 +103,6 @@ public class Photo
                // remove thumbnail too
        }
 
-       /**
-        * Check if a Photo object refers to the same File as another
-        * @param inOther other Photo object
-        * @return true if the Files are the same
-        */
-       public boolean equals(Photo inOther)
-       {
-               return (inOther != null && inOther.getFile() != null && getFile() != null
-                       && inOther.getFile().equals(getFile()));
-       }
-
        /**
         * @param inRotation initial rotation value (from exif)
         */
index 18ebe15d179d9f174d2d9ddc8dd1947098c9b10c..454055309285340476057efe4a51ad86a152ec74 100644 (file)
@@ -3,17 +3,14 @@ package tim.prune.data;
 import java.util.ArrayList;
 
 /**
- * Class to hold a list of Photos
+ * Class to hold a list of Photos, using the MediaList superclass
  */
-public class PhotoList
+public class PhotoList extends MediaList
 {
-       private ArrayList<Photo> _photos = null;
-
        /**
         * Empty constructor
         */
-       public PhotoList()
-       {
+       public PhotoList() {
                this(null);
        }
 
@@ -21,236 +18,82 @@ public class PhotoList
         * Constructor
         * @param inList ArrayList containing Photo objects
         */
-       private PhotoList(ArrayList<Photo> inList)
-       {
-               _photos = inList;
+       private PhotoList(ArrayList<MediaFile> inList) {
+               super(inList);
        }
 
-
        /**
-        * @return the number of photos in the list
+        * @return clone of list contents
         */
-       public int getNumPhotos()
+       public PhotoList cloneList()
        {
-               if (_photos == null) return 0;
-               return _photos.size();
+               if (getNumMedia() == 0) return this;
+               ArrayList<MediaFile> listCopy = new ArrayList<MediaFile>();
+               listCopy.addAll(_media);
+               return new PhotoList(listCopy);
        }
 
+       /**
+        * @return the number of photos in the list
+        */
+       public int getNumPhotos() {
+               return getNumMedia();
+       }
 
        /**
         * Add a Photo to the list
         * @param inPhoto Photo object to add
         */
-       public void addPhoto(Photo inPhoto)
-       {
-               if (inPhoto != null)
-               {
-                       // Make sure array is initialised
-                       if (_photos == null)
-                       {
-                               _photos = new ArrayList<Photo>();
-                       }
-                       // Add the photo
-                       _photos.add(inPhoto);
-               }
+       public void addPhoto(Photo inPhoto) {
+               addMedia(inPhoto);
        }
 
-
        /**
         * Add a Photo to the list
         * @param inPhoto Photo object to add
         * @param inIndex index at which to add photo
         */
-       public void addPhoto(Photo inPhoto, int inIndex)
-       {
-               if (inPhoto != null)
-               {
-                       // Make sure array is initialised
-                       if (_photos == null)
-                       {
-                               _photos = new ArrayList<Photo>();
-                       }
-                       // Add the photo
-                       _photos.add(inIndex, inPhoto);
-               }
+       public void addPhoto(Photo inPhoto, int inIndex) {
+               addMedia(inPhoto, inIndex);
        }
 
-
        /**
         * Remove the selected photo from the list
         * @param inIndex index number to remove
         */
-       public void deletePhoto(int inIndex)
-       {
-               // Maybe throw exception if this fails?
-               if (_photos != null)
-               {
-                       _photos.remove(inIndex);
-               }
-       }
-
-
-       /**
-        * Checks if the specified Photo is already in the list
-        * @param inPhoto Photo object to check
-        * @return true if it's already in the list
-        */
-       public boolean contains(Photo inPhoto)
-       {
-               return (getPhotoIndex(inPhoto) > -1);
+       public void deletePhoto(int inIndex) {
+               deleteMedia(inIndex);
        }
 
-
        /**
         * Get the index of the given Photo
         * @param inPhoto Photo object to check
         * @return index of this Photo in the list, or -1 if not found
         */
-       public int getPhotoIndex(Photo inPhoto)
-       {
-               // Check if we need to check
-               int numPhotos = getNumPhotos();
-               if (numPhotos <= 0 || inPhoto == null || inPhoto.getFile() == null)
-                       return -1;
-               // Loop around photos in list
-               Photo foundPhoto = null;
-               for (int i=0; i<numPhotos; i++)
-               {
-                       foundPhoto = getPhoto(i);
-                       if (foundPhoto != null && foundPhoto.equals(inPhoto))
-                       {
-                               return i;
-                       }
-               }
-               // not found
-               return -1;
+       public int getPhotoIndex(Photo inPhoto) {
+               return getMediaIndex(inPhoto);
        }
 
-
        /**
         * Get the Photo at the given index
         * @param inIndex index number, starting at 0
         * @return specified Photo object
         */
-       public Photo getPhoto(int inIndex)
-       {
-               if (inIndex < 0 || inIndex >= getNumPhotos()) return null;
-               return _photos.get(inIndex);
+       public Photo getPhoto(int inIndex) {
+               return (Photo) getMedia(inIndex);
        }
 
-
-       /**
-        * Crop the photo list to the specified size
-        * @param inIndex previous size
-        */
-       public void cropTo(int inIndex)
-       {
-               if (inIndex <= 0)
-               {
-                       // delete whole list
-                       if (_photos != null) {_photos.clear();}
-               }
-               else
-               {
-                       // delete photos to previous size
-                       while (_photos.size() > inIndex)
-                       {
-                               _photos.remove(_photos.size()-1);
-                       }
-               }
-       }
-
-
-       /**
-        * @return array of file names
-        */
-       public String[] getNameList()
-       {
-               String[] names = new String[getNumPhotos()];
-               for (int i=0; i<getNumPhotos(); i++)
-               {
-                       names[i] = getPhoto(i).getFile().getName();
-               }
-               return names;
-       }
-
-
        /**
         * @return true if photo list contains correlated photos
         */
-       public boolean hasCorrelatedPhotos()
-       {
-               int numPhotos = getNumPhotos();
-               boolean hasCorrelated = false;
-               // Loop over photos in list
-               for (int i=0; i<numPhotos && !hasCorrelated; i++)
-               {
-                       if (getPhoto(i).getDataPoint() != null)
-                               hasCorrelated = true;
-               }
-               return hasCorrelated;
+       public boolean hasCorrelatedPhotos() {
+               return hasCorrelatedMedia();
        }
 
-
        /**
         * Remove all correlated photos from the list
         */
-       public void removeCorrelatedPhotos()
-       {
-               int numPhotos = getNumPhotos();
-               if (numPhotos > 0)
-               {
-                       // Construct new list to copy into
-                       ArrayList<Photo> listCopy = new ArrayList<Photo>();
-                       // Loop over photos in list
-                       for (int i=0; i<numPhotos; i++)
-                       {
-                               // Copy photo if it has no point
-                               Photo photo = getPhoto(i);
-                               if (photo != null)
-                               {
-                                       if (photo.getDataPoint() == null)
-                                               listCopy.add(photo);
-                                       else
-                                               photo.resetCachedData();
-                               }
-                       }
-                       // Switch reference to new list
-                       _photos = listCopy;
-               }
-       }
-
-
-       /**
-        * @return clone of photo list contents
-        */
-       public PhotoList cloneList()
-       {
-               if (_photos == null) return this;
-               ArrayList<Photo> listCopy = new ArrayList<Photo>();
-               for (int i=0; i<_photos.size(); i++) {
-                       listCopy.add(_photos.get(i));
-               }
-               return new PhotoList(listCopy);
-       }
-
-
-       /**
-        * Restore contents from other PhotoList
-        * @param inOther PhotoList with cloned contents
-        */
-       public void restore(PhotoList inOther)
-       {
-               if (inOther.getNumPhotos() == 0)
-               {
-                       // List is empty
-                       _photos = null;
-               }
-               else
-               {
-                       // Clear array and copy over from other one
-                       _photos.clear();
-                       _photos.addAll(inOther._photos);
-               }
+       public void removeCorrelatedPhotos() {
+               removeCorrelatedMedia();
        }
 }
index cbfb3504b4049f1597ca4dba8337102dc7ff5385..81ab64ce7c6cc42dc7df15d788e06a90df9bb31f 100644 (file)
@@ -12,8 +12,10 @@ public class Selection
        private Track _track = null;
        private int _currentPoint = -1;
        private boolean _valid = false;
+       private int _prevNumPoints = 0;
        private int _startIndex = -1, _endIndex = -1;
        private int _currentPhotoIndex = -1;
+       private int _currentAudioIndex = -1;
        private IntegerRange _altitudeRange = null;
        private int _climb = -1, _descent = -1;
        private Altitude.Format _altitudeFormat = Altitude.Format.NO_FORMAT;
@@ -66,7 +68,13 @@ public class Selection
        {
                _altitudeFormat = Altitude.Format.NO_FORMAT;
                _numSegments = 0;
-               if (_track.getNumPoints() > 0 && hasRangeSelected())
+               final int numPoints = _track.getNumPoints();
+               // Recheck if the number of points has changed
+               if (numPoints != _prevNumPoints) {
+                       _prevNumPoints = numPoints;
+                       check();
+               }
+               if (numPoints > 0 && hasRangeSelected())
                {
                        _altitudeRange = new IntegerRange();
                        _climb = 0;
@@ -242,13 +250,14 @@ public class Selection
        }
 
        /**
-        * Clear selected point, range and photo
+        * Clear selected point, range, photo and audio
         */
        public void clearAll()
        {
                _currentPoint = -1;
                selectRange(-1, -1);
                _currentPhotoIndex = -1;
+               _currentAudioIndex = -1;
                check();
        }
 
@@ -373,13 +382,15 @@ public class Selection
 
        /**
         * Select the specified photo and point
-        * @param inPhotoIndex index of selected photo in PhotoList
         * @param inPointIndex index of selected point
+        * @param inPhotoIndex index of selected photo in PhotoList
+        * @param inAudioIndex index of selected audio item
         */
-       public void selectPhotoAndPoint(int inPhotoIndex, int inPointIndex)
+       public void selectPointPhotoAudio(int inPointIndex, int inPhotoIndex, int inAudioIndex)
        {
-               _currentPhotoIndex = inPhotoIndex;
                _currentPoint = inPointIndex;
+               _currentPhotoIndex = inPhotoIndex;
+               _currentAudioIndex = inAudioIndex;
                check();
        }
 
@@ -392,35 +403,40 @@ public class Selection
                return _currentPhotoIndex;
        }
 
+       /**
+        * @return currently selected audio index
+        */
+       public int getCurrentAudioIndex()
+       {
+               return _currentAudioIndex;
+       }
+
        /**
         * Check that the selection still makes sense
         * and fire update message to listeners
         */
        private void check()
        {
-               if (_track != null)
+               if (_track != null && _track.getNumPoints() > 0)
                {
-                       if (_track.getNumPoints() > 0)
+                       int maxIndex = _track.getNumPoints() - 1;
+                       if (_currentPoint > maxIndex)
                        {
-                               int maxIndex = _track.getNumPoints() - 1;
-                               if (_currentPoint > maxIndex)
-                               {
-                                       _currentPoint = maxIndex;
-                               }
-                               if (_endIndex > maxIndex)
-                               {
-                                       _endIndex = maxIndex;
-                               }
-                               if (_startIndex > maxIndex)
-                               {
-                                       _startIndex = maxIndex;
-                               }
+                               _currentPoint = maxIndex;
                        }
-                       else
+                       if (_endIndex > maxIndex)
                        {
-                               // track is empty, clear selections
-                               _currentPoint = _startIndex = _endIndex = -1;
+                               _endIndex = maxIndex;
                        }
+                       if (_startIndex > maxIndex)
+                       {
+                               _startIndex = maxIndex;
+                       }
+               }
+               else
+               {
+                       // track is empty, clear selections
+                       _currentPoint = _startIndex = _endIndex = -1;
                }
                UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
        }
index 8de7c4d7d76e62c5b1041d497a6139613be14f24..e7a0ab90e3af6e5625bbdc833810e2c60fb5e92b 100644 (file)
@@ -27,7 +27,6 @@ public class Timestamp
        private static Calendar CALENDAR = null;
        private static final Pattern GENERAL_TIMESTAMP_PATTERN
                = Pattern.compile("(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})");
-       private static Matcher GENERAL_TIMESTAMP_MATCHER = null;
        private static long SECS_SINCE_1970 = 0L;
        private static long SECS_SINCE_GARTRIP = 0L;
        private static long MSECS_SINCE_1970 = 0L;
@@ -96,16 +95,18 @@ public class Timestamp
                                        }
                                        catch (ParseException e) {}
                                }
-                               if (!_valid && inString.length() == 19) {
-                                       GENERAL_TIMESTAMP_MATCHER = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
-                                       if (GENERAL_TIMESTAMP_MATCHER.matches()) {
+                               if (!_valid && inString.length() == 19)
+                               {
+                                       final Matcher matcher = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
+                                       if (matcher.matches())
+                                       {
                                                try {
-                                                       _seconds = getSeconds(Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(1)),
-                                                               Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(2)),
-                                                               Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(3)),
-                                                               Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(4)),
-                                                               Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(5)),
-                                                               Integer.parseInt(GENERAL_TIMESTAMP_MATCHER.group(6)));
+                                                       _seconds = getSeconds(Integer.parseInt(matcher.group(1)),
+                                                               Integer.parseInt(matcher.group(2)),
+                                                               Integer.parseInt(matcher.group(3)),
+                                                               Integer.parseInt(matcher.group(4)),
+                                                               Integer.parseInt(matcher.group(5)),
+                                                               Integer.parseInt(matcher.group(6)));
                                                        _valid = true;
                                                }
                                                catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
@@ -249,7 +250,17 @@ public class Timestamp
         */
        public Timestamp createPlusOffset(TimeDifference inOffset)
        {
-               return new Timestamp((_seconds + inOffset.getTotalSeconds()) * 1000L);
+               return createPlusOffset(inOffset.getTotalSeconds());
+       }
+
+       /**
+        * Add the given number of seconds to this Timestamp
+        * @param inSeconds number of seconds to add
+        * @return new Timestamp object
+        */
+       public Timestamp createPlusOffset(long inSeconds)
+       {
+               return new Timestamp((_seconds + inSeconds) * 1000L);
        }
 
 
index 9b88ade102519207d2bb730a44edc9b477386157..fa1be1e5f4c27fa8b076d075b8b621e8926bc273 100644 (file)
@@ -173,7 +173,7 @@ public class Track
                {
                        DataPoint point = _dataPoints[i];
                        // Don't delete photo points
-                       if (point.getPhoto() != null || !point.getDeleteFlag())
+                       if (point.hasMedia() || !point.getDeleteFlag())
                        {
                                newPointArray[numCopied] = point;
                                numCopied++;
index 6fa8e90b2e54ba39186053fcc38a1433d5ec298c..3df964b2652643624352a72b4e0c2be93114ed14 100644 (file)
@@ -1,6 +1,5 @@
 package tim.prune.data;
 
-import java.util.Iterator;
 import java.util.Set;
 import tim.prune.UpdateMessageBroker;
 
@@ -14,6 +13,7 @@ public class TrackInfo
        private Selection _selection = null;
        private FileInfo _fileInfo = null;
        private PhotoList _photoList = null;
+       private AudioList _audioList = null;
 
 
        /**
@@ -26,14 +26,14 @@ public class TrackInfo
                _selection = new Selection(_track);
                _fileInfo = new FileInfo();
                _photoList = new PhotoList();
+               _audioList = new AudioList();
        }
 
 
        /**
         * @return the Track object
         */
-       public Track getTrack()
-       {
+       public Track getTrack() {
                return _track;
        }
 
@@ -41,8 +41,7 @@ public class TrackInfo
        /**
         * @return the Selection object
         */
-       public Selection getSelection()
-       {
+       public Selection getSelection() {
                return _selection;
        }
 
@@ -50,8 +49,7 @@ public class TrackInfo
        /**
         * @return the FileInfo object
         */
-       public FileInfo getFileInfo()
-       {
+       public FileInfo getFileInfo() {
                return _fileInfo;
        }
 
@@ -66,17 +64,22 @@ public class TrackInfo
        /**
         * @return the PhotoList object
         */
-       public PhotoList getPhotoList()
-       {
+       public PhotoList getPhotoList() {
                return _photoList;
        }
 
+       /**
+        * @return the AudioList object
+        */
+       public AudioList getAudioList() {
+               return _audioList;
+       }
+
        /**
         * Get the currently selected point, if any
         * @return DataPoint if single point selected, otherwise null
         */
-       public DataPoint getCurrentPoint()
-       {
+       public DataPoint getCurrentPoint() {
                return _track.getPoint(_selection.getCurrentPointIndex());
        }
 
@@ -84,11 +87,18 @@ public class TrackInfo
         * Get the currently selected photo, if any
         * @return Photo if selected, otherwise null
         */
-       public Photo getCurrentPhoto()
-       {
+       public Photo getCurrentPhoto() {
                return _photoList.getPhoto(_selection.getCurrentPhotoIndex());
        }
 
+       /**
+        * Get the currently selected audio file, if any
+        * @return AudioFile if selected, otherwise null
+        */
+       public AudioFile getCurrentAudio() {
+               return _audioList.getAudio(_selection.getCurrentAudioIndex());
+       }
+
 
        /**
         * Add a Set of Photos
@@ -100,25 +110,17 @@ public class TrackInfo
                // Firstly count number of points and photos to add
                int numPhotosToAdd = 0;
                int numPointsToAdd = 0;
-               Iterator<Photo> iterator = null;
                if (inSet != null && !inSet.isEmpty())
                {
-                       iterator = inSet.iterator();
-                       while (iterator.hasNext())
+                       for (Photo photo : inSet)
                        {
-                               try
+                               if (photo != null && !_photoList.contains(photo))
                                {
-                                       Photo photo = iterator.next();
-                                       if (photo != null && !_photoList.contains(photo))
-                                       {
-                                               numPhotosToAdd++;
-                                               if (photo.getDataPoint() != null)
-                                               {
-                                                       numPointsToAdd++;
-                                               }
+                                       numPhotosToAdd++;
+                                       if (photo.getDataPoint() != null) {
+                                               numPointsToAdd++;
                                        }
                                }
-                               catch (ClassCastException ce) {}
                        }
                }
                // If there are any photos to add, add them
@@ -128,27 +130,21 @@ public class TrackInfo
                        int pointNum = 0;
                        boolean hasAltitude = false;
                        // Add each Photo in turn
-                       iterator = inSet.iterator();
-                       while (iterator.hasNext())
+                       for (Photo photo : inSet)
                        {
-                               try
+                               if (photo != null && !_photoList.contains(photo))
                                {
-                                       Photo photo = iterator.next();
-                                       if (photo != null && !_photoList.contains(photo))
+                                       // Add photo
+                                       _photoList.addPhoto(photo);
+                                       // Add point if there is one
+                                       if (photo.getDataPoint() != null)
                                        {
-                                               // Add photo
-                                               _photoList.addPhoto(photo);
-                                               // Add point if there is one
-                                               if (photo.getDataPoint() != null)
-                                               {
-                                                       dataPoints[pointNum] = photo.getDataPoint();
-                                                       // Check if any points have altitudes
-                                                       hasAltitude |= (photo.getDataPoint().getAltitude() != null);
-                                                       pointNum++;
-                                               }
+                                               dataPoints[pointNum] = photo.getDataPoint();
+                                               // Check if any points have altitudes
+                                               hasAltitude |= (photo.getDataPoint().getAltitude() != null);
+                                               pointNum++;
                                        }
                                }
-                               catch (ClassCastException ce) {}
                        }
                        if (numPointsToAdd > 0)
                        {
@@ -164,6 +160,29 @@ public class TrackInfo
                return result;
        }
 
+       /**
+        * Add a Set of Audio objects
+        * @param inSet Set containing Audio objects
+        * @return number of audio objects added
+        */
+       public int addAudios(Set<AudioFile> inSet)
+       {
+               int numAudiosAdded = 0;
+               if (inSet != null && !inSet.isEmpty())
+               {
+                       for (AudioFile audio : inSet)
+                       {
+                               if (audio != null && !_audioList.contains(audio))
+                               {
+                                       // Add audio object
+                                       _audioList.addAudio(audio);
+                                       numAudiosAdded++;
+                                       // audio objects never have points when they're loaded
+                               }
+                       }
+               }
+               return numAudiosAdded;
+       }
 
        /**
         * Delete the currently selected range of points
@@ -203,7 +222,6 @@ public class TrackInfo
         */
        public boolean deleteCurrentPhoto(boolean inPointToo)
        {
-               // delete currently selected photo
                int photoIndex = _selection.getCurrentPhotoIndex();
                if (photoIndex >= 0)
                {
@@ -232,6 +250,41 @@ public class TrackInfo
                return true;
        }
 
+       /**
+        * Delete the currently selected audio item and optionally its point too
+        * @param inPointToo true to also delete associated point
+        * @return true if delete successful
+        */
+       public boolean deleteCurrentAudio(boolean inPointToo)
+       {
+               int audioIndex = _selection.getCurrentAudioIndex();
+               if (audioIndex >= 0)
+               {
+                       AudioFile audio = _audioList.getAudio(audioIndex);
+                       _audioList.deleteAudio(audioIndex);
+                       // has it got a point?
+                       if (audio.getDataPoint() != null)
+                       {
+                               if (inPointToo)
+                               {
+                                       // delete point
+                                       int pointIndex = _track.getPointIndex(audio.getDataPoint());
+                                       _track.deletePoint(pointIndex);
+                               }
+                               else
+                               {
+                                       // disconnect point from audio
+                                       audio.getDataPoint().setAudio(null);
+                                       audio.setDataPoint(null);
+                               }
+                       }
+                       // update subscribers
+                       _selection.modifyPointDeleted();
+                       UpdateMessageBroker.informSubscribers();
+               }
+               return true;
+       }
+
 
        /**
         * Delete all the points which have been marked for deletion
@@ -328,28 +381,30 @@ public class TrackInfo
        public void selectPoint(int inPointIndex)
        {
                if (_selection.getCurrentPointIndex() == inPointIndex || inPointIndex >= _track.getNumPoints()) {return;}
+               DataPoint selectedPoint = _track.getPoint(inPointIndex);
                // get the index of the current photo
                int photoIndex = _selection.getCurrentPhotoIndex();
                // Check if point has photo or not
-               boolean pointHasPhoto = false;
-               if (inPointIndex >= 0)
-               {
-                       Photo pointPhoto = _track.getPoint(inPointIndex).getPhoto();
-                       pointHasPhoto = (pointPhoto != null);
-                       if (pointHasPhoto) {
-                               photoIndex = _photoList.getPhotoIndex(pointPhoto);
-                       }
+               boolean pointHasPhoto = inPointIndex >= 0 && selectedPoint.getPhoto() != null;
+               if (pointHasPhoto) {
+                       photoIndex = _photoList.getPhotoIndex(selectedPoint.getPhoto());
                }
-               // Might need to deselect photo
-               if (!pointHasPhoto)
-               {
+               else if (photoIndex < 0 || _photoList.getPhoto(photoIndex).isConnected()) {
                        // selected point hasn't got a photo - deselect photo if necessary
-                       if (photoIndex < 0 || _photoList.getPhoto(photoIndex).isConnected()) {
-                               photoIndex = -1;
-                       }
+                       photoIndex = -1;
+               }
+               // Check if point has an audio item or not
+               int audioIndex = _selection.getCurrentAudioIndex();
+               boolean pointHasAudio = inPointIndex >= 0 && selectedPoint.getAudio() != null;
+               if (pointHasAudio) {
+                       audioIndex = _audioList.getAudioIndex(selectedPoint.getAudio());
+               }
+               else if (audioIndex < 0 || _audioList.getAudio(audioIndex).isConnected()) {
+                       // deselect current audio file
+                       audioIndex = -1;
                }
                // give to selection
-               _selection.selectPhotoAndPoint(photoIndex, inPointIndex);
+               _selection.selectPointPhotoAudio(inPointIndex, photoIndex, audioIndex);
        }
 
        /**
@@ -363,34 +418,84 @@ public class TrackInfo
                // Therefore the photo selection takes priority, deselecting point if necessary
                // Find Photo object
                Photo photo = _photoList.getPhoto(inPhotoIndex);
+               int pointIndex = _selection.getCurrentPointIndex();
+               DataPoint currPoint = getCurrentPoint();
                if (photo != null)
                {
-                       // Find point object and its index
-                       int pointIndex = _track.getPointIndex(photo.getDataPoint());
-                       // Check whether to deselect current point or not if photo not correlated
-                       if (pointIndex < 0)
-                       {
-                               int currPointIndex = _selection.getCurrentPointIndex();
-                               if (currPointIndex >= 0 && _track.getPoint(currPointIndex).getPhoto() == null)
-                               {
-                                       pointIndex = currPointIndex; // Keep currently selected point
+                       // Has the photo got a point?
+                       if (photo.isConnected()) {
+                               pointIndex = _track.getPointIndex(photo.getDataPoint());
+                       }
+                       else {
+                               // Check whether to deselect current point or not if photo not correlated
+                               if (pointIndex >= 0 && _track.getPoint(pointIndex).getPhoto() != null) {
+                                       pointIndex = -1;
                                }
                        }
-                       // give to selection object
-                       _selection.selectPhotoAndPoint(inPhotoIndex, pointIndex);
                }
                else {
-                       // no photo, just reset selection
-                       DataPoint currPoint = getCurrentPoint();
-                       if (currPoint != null && currPoint.getPhoto() == null) {
-                               _selection.selectPhotoAndPoint(-1, _selection.getCurrentPointIndex()); // keep point
+                       // no photo, but maybe need to deselect point
+                       if (currPoint != null && currPoint.getPhoto() != null) {
+                               pointIndex = -1;
+                       }
+               }
+               // Has the new point got an audio file?
+               DataPoint selectedPoint = _track.getPoint(pointIndex);
+               int audioIndex = _selection.getCurrentAudioIndex();
+               if (selectedPoint != null) {
+                       if (selectedPoint.getAudio() != null) audioIndex = _audioList.getAudioIndex(selectedPoint.getAudio());
+               }
+               else {
+                       if (selectedPoint != currPoint && currPoint.getAudio() != null) {audioIndex = -1;}
+               }
+               // give to selection object
+               _selection.selectPointPhotoAudio(pointIndex, inPhotoIndex, audioIndex);
+       }
+
+       /**
+        * Select the given audio object and its point if any
+        * @param inAudioIndex index of audio item to select
+        */
+       public void selectAudio(int inAudioIndex)
+       {
+               if (_selection.getCurrentAudioIndex() == inAudioIndex) {return;}
+               // Audio selection takes priority, deselecting point if necessary
+               AudioFile audio = _audioList.getAudio(inAudioIndex);
+               int pointIndex = _selection.getCurrentPointIndex();
+               DataPoint currPoint = getCurrentPoint();
+               if (audio != null)
+               {
+                       // Find point object and its index
+                       if (audio.isConnected()) {
+                               pointIndex = _track.getPointIndex(audio.getDataPoint());
                        }
                        else {
-                               _selection.selectPhotoAndPoint(-1, -1); // deselect point too
+                               // Check whether to deselect current point or not if audio not correlated
+                               if (pointIndex >= 0 && _track.getPoint(pointIndex).getAudio() != null) {
+                                       pointIndex = -1;
+                               }
+                       }
+               }
+               else {
+                       // check if current point has audio or not
+                       if (currPoint != null && currPoint.getAudio() != null) {
+                               pointIndex = -1;
                        }
                }
+               // Has the new point got a photo?
+               DataPoint selectedPoint = _track.getPoint(pointIndex);
+               int photoIndex = _selection.getCurrentPhotoIndex();
+               if (selectedPoint != null) {
+                       if (selectedPoint.getPhoto() != null) photoIndex = _photoList.getPhotoIndex(selectedPoint.getPhoto());
+               }
+               else {
+                       if (selectedPoint != currPoint && currPoint.getPhoto() != null) {photoIndex = -1;}
+               }
+               // give to selection object
+               _selection.selectPointPhotoAudio(pointIndex, photoIndex, inAudioIndex);
        }
 
+
        /**
         * Extend the current selection to end at the given point, eg by shift-clicking
         * @param inPointNum index of end point
index c8985071243861bd5fe8ec3893bc4720aca02bd5..d0e4d3cdc56eaa77843bd826d72ca664d5456349 100644 (file)
@@ -97,9 +97,9 @@ public class AboutScreen extends GenericFunction
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext2")).append("</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext3")).append("</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.languages")).append(" : ")
-                       .append("deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano, nederlands,<br>" +
-                               " polski, portugu\u00EAs, \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, " +
-                               "<br> \u010de\u0161tina, rom\u00E2n\u0103, afrikaans, bahasa indonesia, farsi").append("</p>");
+                       .append("\u010de\u0161tina, deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano, magyar,<br>" +
+                               " nederlands, polski, portugu\u00EAs, \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese), \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean),<br>" +
+                               " schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, rom\u00E2n\u0103, afrikaans, bahasa indonesia, farsi</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.translatedby")).append("</p>");
                JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
                descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
@@ -195,7 +195,7 @@ public class AboutScreen extends GenericFunction
                        new JLabel(" theYinYeti, Rothermographer, Sam, Rudolph, nazotoko,"),
                        1, 4);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
-                       new JLabel(" katpatuka, R\u00E9mi, Marcus, Ali, Javier, Jeroen, prot_d"),
+                       new JLabel(" katpatuka, R\u00E9mi, Marcus, Ali, Javier, Jeroen, prot_d, Gy\u00F6rgy, HooAU"),
                        1, 5);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "),
@@ -297,20 +297,27 @@ public class AboutScreen extends GenericFunction
                // First, try locally-held readme.txt if available (as it normally should be)
                // Readme file can either be in file system or packed in the same jar as code
                String errorMessage = null;
+               String readme = null;
+               InputStream in = null;
                try
                {
                        // For some reason using ../readme.txt doesn't work, so need absolute path
-                       InputStream in = AboutScreen.class.getResourceAsStream("/tim/prune/readme.txt");
+                       in = AboutScreen.class.getResourceAsStream("/tim/prune/readme.txt");
                        if (in != null) {
                                byte[] buffer = new byte[in.available()];
                                in.read(buffer);
                                in.close();
-                               return new String(buffer);
+                               readme = new String(buffer);
                        }
                }
                catch (IOException e) {
                        errorMessage =  e.getMessage();
                }
+               finally {
+                       try {in.close();} catch (Exception e) {}
+               }
+               if (readme != null) {return readme;}
+
                // Locally-held file failed, so try to find gz file installed on system (eg Debian)
                try
                {
@@ -318,7 +325,7 @@ public class AboutScreen extends GenericFunction
                        if (gzFile.exists())
                        {
                                // Copy decompressed bytes from gz file into out
-                               InputStream in = new GZIPInputStream(new FileInputStream(gzFile));
+                               in = new GZIPInputStream(new FileInputStream(gzFile));
                                ByteArrayOutputStream out = new ByteArrayOutputStream();
                                byte[] buffer = new byte[8 * 1024];
                                int count = 0;
@@ -328,12 +335,16 @@ public class AboutScreen extends GenericFunction
                                } while (count != -1);
                                out.close();
                                in.close();
-                               return out.toString();
+                               readme = out.toString();
                        }
                }
                catch (IOException e) {
                        System.err.println("Exception trying to get readme.gz : " + e.getMessage());
                }
+               finally {
+                       try {in.close();} catch (Exception e) {}
+               }
+               if (readme != null) {return readme;}
                // Only show first error message if couldn't get readme from gz either
                if (errorMessage != null) {
                        System.err.println("Exception trying to get readme: " + errorMessage);
index d7126ea39d702961423868708322a9c2fab6266c..ec13bb1132dc52d64d3059c0d71b147a58890233 100644 (file)
@@ -9,8 +9,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
-import java.net.MalformedURLException;
-import java.net.URL;
 
 import javax.swing.BorderFactory;
 import javax.swing.ButtonGroup;
@@ -216,23 +214,15 @@ public class AddMapSourceDialog
        private boolean isOsmPanelOk()
        {
                boolean ok = _oNameField.getText().trim().length() > 1;
-               URL baseUrl = null, topUrl = null;
-               try {
-                       // Try to parse base url if given
-                       String baseText = _baseUrlField.getText().trim();
-                       if (baseText.length() > 10) {
-                               baseUrl = new URL(baseText);
-                       }
-                       else if (baseText.length() > 0) {ok = false;}
-                       // Same again for top url if given
-                       String topText = _topUrlField.getText().trim();
-                       if (topText.length() > 10) {
-                               topUrl = new URL(topText);
-                       }
-                       else if (topText.length() > 0) {ok = false;}
-               } catch (MalformedURLException e) {
-                       ok = false;
-               }
+               String baseUrl = null, topUrl = null;
+               // Try to parse base url if given
+               String baseText = _baseUrlField.getText().trim();
+               baseUrl = MapSource.fixBaseUrl(baseText);
+               if (baseText.length() > 0 && baseUrl == null) {ok = false;}
+               // Same again for top url if given
+               String topText = _topUrlField.getText().trim();
+               topUrl = MapSource.fixBaseUrl(topText);
+               if (topText.length() > 0 && topUrl == null) {ok = false;}
                // looks ok if at least one url given
                return (ok && (baseUrl != null || topUrl != null));
        }
diff --git a/tim/prune/function/ConnectToPointFunction.java b/tim/prune/function/ConnectToPointFunction.java
new file mode 100644 (file)
index 0000000..fa6f30b
--- /dev/null
@@ -0,0 +1,63 @@
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.AudioFile;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Photo;
+import tim.prune.undo.UndoConnectMedia;
+import tim.prune.undo.UndoOperation;
+
+/**
+ * Function to connect either a photo or an audio file to the current point
+ */
+public class ConnectToPointFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public ConnectToPointFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.connecttopoint";
+       }
+
+       /**
+        * Perform function
+        */
+       public void begin()
+       {
+               Photo photo = _app.getTrackInfo().getCurrentPhoto();
+               DataPoint point = _app.getTrackInfo().getCurrentPoint();
+               AudioFile audio = _app.getTrackInfo().getCurrentAudio();
+               boolean connectPhoto = (point != null && photo != null && point.getPhoto() == null);
+               boolean connectAudio = (point != null && audio != null && point.getAudio() == null);
+
+               if (connectPhoto && connectAudio) {
+                       // TODO: Let user choose whether to connect photo/audio or both
+               }
+               // Make undo object
+               UndoOperation undo = new UndoConnectMedia(point, connectPhoto?photo.getFile().getName():null,
+                       connectAudio?audio.getFile().getName():null);
+               // Connect the media
+               if (connectPhoto) {
+                       photo.setDataPoint(point);
+                       point.setPhoto(photo);
+               }
+               if (connectAudio) {
+                       audio.setDataPoint(point);
+                       point.setAudio(audio);
+               }
+               UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+               _app.completeFunction(undo, I18nManager.getText("confirm.media.connect"));
+       }
+}
diff --git a/tim/prune/function/DisconnectAudioFunction.java b/tim/prune/function/DisconnectAudioFunction.java
new file mode 100644 (file)
index 0000000..abcd9c4
--- /dev/null
@@ -0,0 +1,48 @@
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.AudioFile;
+import tim.prune.data.DataPoint;
+import tim.prune.undo.UndoDisconnectMedia;
+import tim.prune.undo.UndoOperation;
+
+/**
+ * Function to disconnect the current audio object from the current point (like DisconnectPhotoFunction)
+ */
+public class DisconnectAudioFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public DisconnectAudioFunction(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.disconnectfrompoint";
+       }
+
+       /**
+        * Perform the operation
+        */
+       public void begin()
+       {
+               AudioFile audio = _app.getTrackInfo().getCurrentAudio();
+               if (audio != null && audio.getDataPoint() != null)
+               {
+                       DataPoint point = audio.getDataPoint();
+                       UndoOperation undo = new UndoDisconnectMedia(point, false, true, audio.getFile().getName());
+                       // disconnect
+                       audio.setDataPoint(null);
+                       point.setAudio(null);
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+                       _app.completeFunction(undo, I18nManager.getText("confirm.audio.disconnect"));
+               }
+       }
+}
diff --git a/tim/prune/function/DisconnectPhotoFunction.java b/tim/prune/function/DisconnectPhotoFunction.java
new file mode 100644 (file)
index 0000000..6223433
--- /dev/null
@@ -0,0 +1,47 @@
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Photo;
+import tim.prune.undo.UndoDisconnectMedia;
+
+/**
+ * Function to disconnect the current photo from the current point
+ */
+public class DisconnectPhotoFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public DisconnectPhotoFunction(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.disconnectfrompoint";
+       }
+
+       /**
+        * Perform the operation
+        */
+       public void begin()
+       {
+               Photo photo = _app.getTrackInfo().getCurrentPhoto();
+               if (photo != null && photo.getDataPoint() != null)
+               {
+                       DataPoint point = photo.getDataPoint();
+                       UndoDisconnectMedia undo = new UndoDisconnectMedia(point, true, false, photo.getFile().getName());
+                       // disconnect
+                       photo.setDataPoint(null);
+                       point.setPhoto(null);
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+                       _app.completeFunction(undo, I18nManager.getText("confirm.photo.disconnect"));
+               }
+       }
+}
diff --git a/tim/prune/function/DownloadOsmFunction.java b/tim/prune/function/DownloadOsmFunction.java
new file mode 100644 (file)
index 0000000..bef09ad
--- /dev/null
@@ -0,0 +1,277 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.NumberFormat;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.DoubleRange;
+
+/**
+ * Class to allow the download of OSM data (using the XAPI)
+ * for the area covered by the data
+ */
+public class DownloadOsmFunction extends GenericFunction implements Runnable
+{
+       private JDialog _dialog = null;
+       private JLabel[] _latLonLabels = null;
+       private JProgressBar _progressBar = null;
+       private JButton _okButton = null;
+       private JFileChooser _fileChooser = null;
+       private File _selectedFile = null;
+       private boolean _cancelled = false;
+       /** Number formatter */
+       private final NumberFormat FORMAT_TWO_DP = NumberFormat.getNumberInstance();
+
+
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        */
+       public DownloadOsmFunction(App inApp)
+       {
+               super(inApp);
+               FORMAT_TWO_DP.setMaximumFractionDigits(2);
+               FORMAT_TWO_DP.setMinimumFractionDigits(2);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.downloadosm";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+                       _fileChooser = new JFileChooser();
+                       _fileChooser.setSelectedFile(new File("data.osm"));
+               }
+               initDialog();
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(0, 10));
+               dialogPanel.add(new JLabel(I18nManager.getText("dialog.downloadosm.desc")), BorderLayout.NORTH);
+               // grid of labels to show lat/long extent
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(3, 3));
+               _latLonLabels = new JLabel[4];
+               for (int i=0; i<4; i++) {
+                       _latLonLabels[i] = new JLabel("0");
+               }
+               int lNum = 0;
+               for (int i=0; i<4; i++) {
+                       gridPanel.add(new JLabel(" "));
+                       gridPanel.add(_latLonLabels[lNum++]);
+               }
+               // layout main panel
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+               mainPanel.add(gridPanel);
+               _progressBar = new JProgressBar();
+               _progressBar.setIndeterminate(true);
+               mainPanel.add(_progressBar);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               finish();
+                       }
+               };
+               _okButton.addActionListener(okListener);
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _cancelled = true;
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       /**
+        * Initialise the values of the labels in the dialog
+        */
+       private void initDialog()
+       {
+               // Get range of data
+               String[] lats = expandRange(_app.getTrackInfo().getTrack().getLatRange());
+               String[] lons = expandRange(_app.getTrackInfo().getTrack().getLonRange());
+               _latLonLabels[0].setText(lats[1]); // max lat
+               _latLonLabels[1].setText(lons[0]); // min lon
+               _latLonLabels[2].setText(lons[1]); // max lon
+               _latLonLabels[3].setText(lats[0]); // min lat
+               _okButton.setEnabled(true);
+               _progressBar.setVisible(false);
+               _cancelled = false;
+       }
+
+       /**
+        * Expand the given range to reasonable limits
+        * @param inRange range of lat/long values
+        * @return expanded range as pair of Strings
+        */
+       private String[] expandRange(DoubleRange inRange)
+       {
+               double mid = (inRange.getMaximum() + inRange.getMinimum()) / 2.0;
+               double range = inRange.getRange();
+               double max = 0.0, min = 0.0;
+               // Expand range to at least 0.02 degree
+               if (range < 0.02)
+               {
+                       min = mid - 0.01;
+                       max = mid + 0.01;
+               }
+               else {
+                       // expand by 10% in both directions
+                       min = mid - range * 0.55;
+                       max = mid + range * 0.55;
+               }
+               // Round min down to 0.01 degree
+               int minCents = (int) (100 * min);
+               // Round max upwards likewise
+               int maxCents = (int) (100 * max + 1);
+               final String[] answer = new String[] {FORMAT_TWO_DP.format(minCents/100.0),
+                       FORMAT_TWO_DP.format(maxCents/100.0)};
+               return answer;
+       }
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               if (!_okButton.isEnabled()) return;
+               _selectedFile = selectOsmFile();
+               if (_selectedFile != null)
+               {
+                       // Show progress bar
+                       _okButton.setEnabled(false);
+                       _progressBar.setVisible(true);
+                       new Thread(this).start();
+               }
+               else
+                       _dialog.dispose();
+       }
+
+       /**
+        * Select a file to save the OSM data to
+        * @return selected file or null if cancelled
+        */
+       private File selectOsmFile()
+       {
+               File saveFile = null;
+               boolean chooseAgain = false;
+               do
+               {
+                       chooseAgain = false;
+                       if (_fileChooser.showSaveDialog(_dialog) == JFileChooser.APPROVE_OPTION)
+                       {
+                               // OK pressed and file chosen
+                               File file = _fileChooser.getSelectedFile();
+                               // Check if file exists and if necessary prompt for overwrite
+                               Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
+                               if (!file.exists() || JOptionPane.showOptionDialog(_dialog,
+                                               I18nManager.getText("dialog.save.overwrite.text"),
+                                               I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
+                                               JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+                                       == JOptionPane.YES_OPTION)
+                               {
+                                       // new file or overwrite confirmed
+                                       saveFile = file;
+                               }
+                               else
+                               {
+                                       // file exists and overwrite cancelled - select again
+                                       chooseAgain = true;
+                               }
+                       }
+               } while (chooseAgain);
+               return saveFile;
+       }
+
+
+       /**
+        * Do the actual download - launched to run in another thread
+        */
+       public void run()
+       {
+               final String url = "http://www.informationfreeway.org/api/0.6/map?bbox=" +
+                       _latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
+                       _latLonLabels[2].getText() + "," + _latLonLabels[0].getText();
+
+               byte[] buffer = new byte[1024];
+               InputStream inStream = null;
+               FileOutputStream outStream = null;
+               int numBytesRead = 0;
+               try
+               {
+                       inStream = new URL(url).openStream();
+                       outStream = new FileOutputStream(_selectedFile);
+                       // Loop and copy bytes to file
+                       while ((numBytesRead = inStream.read(buffer)) > -1 && !_cancelled)
+                       {
+                               outStream.write(buffer, 0, numBytesRead);
+                       }
+               }
+               catch (MalformedURLException mue) {}
+               catch (IOException ioe) {
+                       // TODO: throw exception or show dialog
+                       System.out.println("Exception: " + ioe.getClass().getName());
+               }
+               // clean up streams
+               finally {
+                       try {inStream.close();} catch (Exception e) {}
+                       try {outStream.close();} catch (Exception e) {}
+               }
+               // close dialog
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/function/GetWikipediaFunction.java b/tim/prune/function/GetWikipediaFunction.java
new file mode 100644 (file)
index 0000000..acef09c
--- /dev/null
@@ -0,0 +1,133 @@
+package tim.prune.function;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Field;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
+import tim.prune.function.gpsies.GenericDownloaderFunction;
+import tim.prune.function.gpsies.GpsiesTrack;
+
+/**
+ * Function to load nearby point information from Wikipedia
+ * according to the currently viewed area
+ */
+public class GetWikipediaFunction extends GenericDownloaderFunction
+{
+       /** Maximum number of results to get */
+       private static final int MAX_RESULTS = 20;
+       /** Maximum distance from point in km */
+       private static final int MAX_DISTANCE = 15;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public GetWikipediaFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.getwikipedia";
+       }
+
+       /**
+        * @param inColNum index of column, 0 or 1
+        * @return key for this column
+        */
+       protected String getColumnKey(int inColNum)
+       {
+               if (inColNum == 0) return "dialog.wikipedia.column.name";
+               return "dialog.wikipedia.column.distance";
+       }
+
+
+       /**
+        * Run method to call geonames in separate thread
+        */
+       public void run()
+       {
+               _statusLabel.setText(I18nManager.getText("confirm.running"));
+               // Get coordinates from current point (if any) or from centre of screen
+               double lat = 0.0, lon = 0.0;
+               DataPoint point = _app.getTrackInfo().getCurrentPoint();
+               if (point == null)
+               {
+                       double[] coords = _app.getViewport().getBounds();
+                       lat = (coords[0] + coords[2]) / 2.0;
+                       lon = (coords[1] + coords[3]) / 2.0;
+               }
+               else {
+                       lat = point.getLatitude().getDouble();
+                       lon = point.getLongitude().getDouble();
+               }
+
+               String descMessage = "";
+               InputStream inStream = null;
+
+               // Example http://ws.geonames.org/findNearbyWikipedia?lat=47&lng=9
+               String urlString = "http://ws.geonames.org/findNearbyWikipedia?lat=" +
+                       lat + "&lng=" + lon + "&maxRows=" + MAX_RESULTS
+                       + "&radius=" + MAX_DISTANCE + "&lang=" + I18nManager.getText("wikipedia.lang");
+               // Parse the returned XML with a special handler
+               GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+               try
+               {
+                       URL url = new URL(urlString);
+                       SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+                       inStream = url.openStream();
+                       saxParser.parse(inStream, xmlHandler);
+               }
+               catch (Exception e) {
+                       descMessage = e.getClass().getName() + " - " + e.getMessage();
+               }
+               // Close stream and ignore errors
+               try {
+                       inStream.close();
+               } catch (Exception e) {}
+               // Add track list to model
+               ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
+               _trackListModel.addTracks(trackList);
+
+               // Set status label according to error or "none found", leave blank if ok
+               if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
+                       descMessage = I18nManager.getText("dialog.gpsies.nonefound");
+               }
+               _statusLabel.setText(descMessage);
+       }
+
+       /**
+        * Load the selected track or point
+        */
+       protected void loadSelected()
+       {
+               // Find the row selected in the table and get the corresponding track
+               int rowNum = _trackTable.getSelectedRow();
+               if (rowNum >= 0 && rowNum < _trackListModel.getRowCount())
+               {
+                       String coords = _trackListModel.getTrack(rowNum).getDownloadLink();
+                       String[] latlon = coords.split(",");
+                       if (latlon.length == 2)
+                       {
+                               DataPoint point = new DataPoint(new Latitude(latlon[0]), new Longitude(latlon[1]), null);
+                               point.setFieldValue(Field.WAYPT_NAME, _trackListModel.getTrack(rowNum).getTrackName(), false);
+                               _app.createPoint(point);
+                       }
+               }
+               // Close the dialog
+               _cancelled = true;
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/function/GetWikipediaXmlHandler.java b/tim/prune/function/GetWikipediaXmlHandler.java
new file mode 100644 (file)
index 0000000..3cca682
--- /dev/null
@@ -0,0 +1,93 @@
+package tim.prune.function;
+
+import java.util.ArrayList;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import tim.prune.function.gpsies.GpsiesTrack;
+
+/**
+ * XML handler for dealing with XML returned from gpsies.com
+ */
+public class GetWikipediaXmlHandler extends DefaultHandler
+{
+       private String _value = null;
+       private ArrayList<GpsiesTrack> _trackList = null;
+       private GpsiesTrack _track = null;
+       private String _lat = null, _lon = null;
+
+
+       /**
+        * React to the start of an XML tag
+        */
+       public void startElement(String inUri, String inLocalName, String inTagName,
+               Attributes inAttributes) throws SAXException
+       {
+               if (inTagName.equals("geonames")) {
+                       _trackList = new ArrayList<GpsiesTrack>();
+               }
+               else if (inTagName.equals("entry")) {
+                       _track = new GpsiesTrack();
+                       _lat = null;
+                       _lon = null;
+               }
+               else _value = null;
+               super.startElement(inUri, inLocalName, inTagName, inAttributes);
+       }
+
+       /**
+        * React to the end of an XML tag
+        */
+       public void endElement(String inUri, String inLocalName, String inTagName)
+       throws SAXException
+       {
+               if (inTagName.equals("entry")) {
+                       // end of the entry
+                       _track.setDownloadLink(_lat + "," + _lon);
+                       _trackList.add(_track);
+               }
+               else if (inTagName.equals("title")) {
+                       _track.setTrackName(_value);
+               }
+               else if (inTagName.equals("summary")) {
+                       _track.setDescription(_value);
+               }
+               else if (inTagName.equals("lat")) {
+                       _lat = _value;
+               }
+               else if (inTagName.equals("lng")) {
+                       _lon = _value;
+               }
+               else if (inTagName.equals("distance")) {
+                       try {
+                               _track.setLength(Double.parseDouble(_value) * 1000.0); // convert from km to m
+                       }
+                       catch (NumberFormatException nfe) {}
+               }
+               else if (inTagName.equals("wikipediaUrl")) {
+                       _track.setWebUrl(_value);
+               }
+               super.endElement(inUri, inLocalName, inTagName);
+       }
+
+       /**
+        * React to characters received inside tags
+        */
+       public void characters(char[] inCh, int inStart, int inLength)
+       throws SAXException
+       {
+               String value = new String(inCh, inStart, inLength);
+               _value = (_value==null?value:_value+value);
+               super.characters(inCh, inStart, inLength);
+       }
+
+       /**
+        * @return the list of tracks
+        */
+       public ArrayList<GpsiesTrack> getTrackList()
+       {
+               return _trackList;
+       }
+}
diff --git a/tim/prune/function/PhotoPopupFunction.java b/tim/prune/function/PhotoPopupFunction.java
new file mode 100644 (file)
index 0000000..6b8b3e0
--- /dev/null
@@ -0,0 +1,113 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.Photo;
+import tim.prune.gui.PhotoThumbnail;
+
+/**
+ * Class to show a popup window for a photo
+ */
+public class PhotoPopupFunction extends GenericFunction
+{
+       /** popup window */
+       private JFrame _frame = null; // would be a JDialog but that doesn't allow max button
+       /** label for filename */
+       private JLabel _label = null;
+       /** Photo thumbnail */
+       private PhotoThumbnail _photoThumb = null;
+
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public PhotoPopupFunction(App inApp)
+       {
+               super(inApp);
+       }
+
+       /**
+        * Get the name key
+        */
+       public String getNameKey() {
+               return "function.photopopup";
+       }
+
+       /**
+        * Show the screen
+        */
+       public void begin()
+       {
+               if (_frame == null)
+               {
+                       _frame = new JFrame(I18nManager.getText(getNameKey()));
+                       _frame.setIconImage(_parentFrame.getIconImage());
+                       _frame.getContentPane().add(makeContents());
+                       _frame.pack();
+                       _frame.setLocationRelativeTo(_parentFrame);
+               }
+               initFrame();
+               _frame.setVisible(true);
+       }
+
+       /**
+        * Initialise the frame to show the current photo
+        */
+       private void initFrame()
+       {
+               _frame.setVisible(false);
+               Photo photo = _app.getTrackInfo().getCurrentPhoto();
+               _frame.setTitle(photo.getFile().getName());
+               _label.setText("'" + photo.getFile().getName() + "' ("
+                       + photo.getWidth() + " x " + photo.getHeight() + ")");
+               _photoThumb.setPhoto(photo);
+       }
+
+       /**
+        * @return the contents of the window as a Component
+        */
+       private Component makeContents()
+       {
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BorderLayout());
+               _label = new JLabel("Photo popup");
+               mainPanel.add(_label, BorderLayout.NORTH);
+               _photoThumb = new PhotoThumbnail(false); // specify not in details panel
+               _photoThumb.setPreferredSize(new Dimension(300, 300));
+               mainPanel.add(_photoThumb, BorderLayout.CENTER);
+               // Close button at bottom
+               JPanel okPanel = new JPanel();
+               okPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton okButton = new JButton(I18nManager.getText("button.ok"));
+               okButton.addActionListener(new ActionListener()
+               {
+                       public void actionPerformed(ActionEvent e) {
+                               _frame.dispose();
+                       }
+               });
+               okButton.addKeyListener(new KeyListener() {
+                       public void keyPressed(KeyEvent e) {
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {_frame.dispose();}
+                       }
+                       public void keyTyped(KeyEvent e) {}
+                       public void keyReleased(KeyEvent e) {}
+               });
+               okPanel.add(okButton);
+               mainPanel.add(okPanel, BorderLayout.SOUTH);
+               return mainPanel;
+       }
+}
diff --git a/tim/prune/function/PlayAudioFunction.java b/tim/prune/function/PlayAudioFunction.java
new file mode 100644 (file)
index 0000000..7e7a34a
--- /dev/null
@@ -0,0 +1,153 @@
+package tim.prune.function;
+
+import java.io.File;
+import java.io.IOException;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+
+/**
+ * Class to play the current audio file
+ */
+public class PlayAudioFunction extends GenericFunction implements Runnable
+{
+       /** Audio clip used for playing within java */
+       private Clip _clip = null;
+
+
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public PlayAudioFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.playaudio";
+       }
+
+       /**
+        * Perform function
+        */
+       public void begin()
+       {
+               // Launch new thread if clip isn't currently playing
+               if (_clip == null) {
+                       new Thread(this).start();
+               }
+       }
+
+       /**
+        * Play the audio in a new thread
+        */
+       public void run()
+       {
+               File audioFile = _app.getTrackInfo().getCurrentAudio().getFile();
+               boolean played = false;
+               if (audioFile.exists() && audioFile.isFile() && audioFile.canRead())
+               {
+                       // First choice is to play using java
+                       played = playClip(audioFile);
+                       // Second choice is to try the Desktop library from java 6, if available
+                       if (!played) {
+                               try {
+                                       Class<?> d = Class.forName("java.awt.Desktop");
+                                       d.getDeclaredMethod("open", new Class[] {File.class}).invoke(
+                                               d.getDeclaredMethod("getDesktop").invoke(null), new Object[] {audioFile});
+                                       //above code mimics: Desktop.getDesktop().open(audioFile);
+                                       played = true;
+                               }
+                               catch (Exception ignore) {
+                                       played = false;
+                               }
+                       }
+                       // If the Desktop call failed, need to try backup methods
+                       if (!played)
+                       {
+                               // If system looks like a Mac, try open command
+                               String osName = System.getProperty("os.name").toLowerCase();
+                               boolean isMacOsx = osName.indexOf("mac os") >= 0 || osName.indexOf("darwin") >= 0;
+                               if (isMacOsx) {
+                                       String[] command = new String[] {"open", audioFile.getAbsolutePath()};
+                                       try {
+                                               Runtime.getRuntime().exec(command);
+                                               played = true;
+                                       }
+                                       catch (IOException ioe) {}
+                               }
+                       }
+               }
+               if (!played)
+               {
+                       // If still not worked, show error message
+                       _app.showErrorMessage(getNameKey(), "error.playaudiofailed");
+               }
+       }
+
+       /**
+        * Try to play the sound file using built-in java libraries
+        * @return true if play was successful
+        */
+       private boolean playClip(File inFile)
+       {
+               boolean success = false;
+               AudioInputStream audioInputStream = null;
+               _clip = null;
+               try
+               {
+                       audioInputStream = AudioSystem.getAudioInputStream(inFile);
+                       _clip = AudioSystem.getClip();
+                       _clip.open(audioInputStream);
+                       // play the clip
+                       _clip.start();
+                       _clip.drain();
+                       success = true;
+               } catch (Exception e) {
+               } finally {
+                       // close the stream to clean up
+                       try {
+                               _clip.close();
+                               audioInputStream.close();
+                       } catch (Exception e) {}
+                       _clip = null;
+               }
+               return success;
+       }
+
+       /**
+        * Try to stop a currently playing clip
+        */
+       public void stopClip()
+       {
+               if (_clip != null && _clip.isActive()) {
+                       try {
+                               _clip.stop();
+                               _clip.flush();
+                       }
+                       catch (Exception e) {}
+               }
+       }
+
+       /**
+        * @return percentage of clip currently played, or -1 if not playing
+        */
+       public int getPercentage()
+       {
+               int percent = -1;
+               if (_clip != null && _clip.isActive())
+               {
+                       long clipLen = _clip.getMicrosecondLength();
+                       if (clipLen > 0) {
+                               percent = (int) (_clip.getMicrosecondPosition() * 100.0 / clipLen);
+                       }
+               }
+               return percent;
+       }
+}
diff --git a/tim/prune/function/RemoveAudioFunction.java b/tim/prune/function/RemoveAudioFunction.java
new file mode 100644 (file)
index 0000000..fa859be
--- /dev/null
@@ -0,0 +1,68 @@
+package tim.prune.function;
+
+import javax.swing.JOptionPane;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.AudioFile;
+import tim.prune.undo.UndoDeleteAudio;
+
+/**
+ * Function to remove the currently selected audio file
+ */
+public class RemoveAudioFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public RemoveAudioFunction(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.removeaudio";
+       }
+
+       /**
+        * Perform the function
+        */
+       public void begin()
+       {
+               // Delete the current audio, and optionally its point too, keeping undo information
+               AudioFile currentAudio = _app.getTrackInfo().getCurrentAudio();
+               if (currentAudio != null)
+               {
+                       // Audio is selected, see if it has a point or not
+                       boolean deleted = false;
+                       UndoDeleteAudio undoAction = null;
+                       if (currentAudio.getDataPoint() == null)
+                       {
+                               // no point attached, so just delete
+                               undoAction = new UndoDeleteAudio(currentAudio, _app.getTrackInfo().getSelection().getCurrentAudioIndex(),
+                                       null, -1);
+                               deleted = _app.getTrackInfo().deleteCurrentAudio(false);
+                       }
+                       else
+                       {
+                               // point is attached, so need to confirm point deletion
+                               undoAction = new UndoDeleteAudio(currentAudio, _app.getTrackInfo().getSelection().getCurrentAudioIndex(),
+                                       currentAudio.getDataPoint(), _app.getTrackInfo().getTrack().getPointIndex(currentAudio.getDataPoint()));
+                               int response = JOptionPane.showConfirmDialog(_app.getFrame(),
+                                       I18nManager.getText("dialog.deleteaudio.deletepoint"),
+                                       I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION);
+                               boolean deletePointToo = (response == JOptionPane.YES_OPTION);
+                               // Cancel delete if cancel pressed or dialog closed
+                               if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION) {
+                                       deleted = _app.getTrackInfo().deleteCurrentAudio(deletePointToo);
+                               }
+                       }
+                       // Add undo information to stack if necessary
+                       if (deleted) {
+                               _app.completeFunction(undoAction, currentAudio.getFile().getName() + " " + I18nManager.getText("confirm.media.removed"));
+                       }
+               }
+       }
+}
diff --git a/tim/prune/function/RemovePhotoFunction.java b/tim/prune/function/RemovePhotoFunction.java
new file mode 100644 (file)
index 0000000..b09ae10
--- /dev/null
@@ -0,0 +1,69 @@
+package tim.prune.function;
+
+import javax.swing.JOptionPane;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.Photo;
+import tim.prune.undo.UndoDeletePhoto;
+
+/**
+ * Function to remove the currently selected photo
+ */
+public class RemovePhotoFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public RemovePhotoFunction(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.removephoto";
+       }
+
+       /**
+        * Perform the function
+        */
+       public void begin()
+       {
+               // Delete the current photo, and optionally its point too, keeping undo information
+               Photo currentPhoto = _app.getTrackInfo().getCurrentPhoto();
+               if (currentPhoto != null)
+               {
+                       // Photo is selected, see if it has a point or not
+                       boolean photoDeleted = false;
+                       UndoDeletePhoto undoAction = null;
+                       if (currentPhoto.getDataPoint() == null)
+                       {
+                               // no point attached, so just delete photo
+                               undoAction = new UndoDeletePhoto(currentPhoto, _app.getTrackInfo().getSelection().getCurrentPhotoIndex(),
+                                       null, -1);
+                               photoDeleted = _app.getTrackInfo().deleteCurrentPhoto(false);
+                       }
+                       else
+                       {
+                               // point is attached, so need to confirm point deletion
+                               undoAction = new UndoDeletePhoto(currentPhoto, _app.getTrackInfo().getSelection().getCurrentPhotoIndex(),
+                                       currentPhoto.getDataPoint(), _app.getTrackInfo().getTrack().getPointIndex(currentPhoto.getDataPoint()));
+                               int response = JOptionPane.showConfirmDialog(_app.getFrame(),
+                                       I18nManager.getText("dialog.deletephoto.deletepoint"),
+                                       I18nManager.getText("dialog.deletephoto.title"),
+                                       JOptionPane.YES_NO_CANCEL_OPTION);
+                               boolean deletePointToo = (response == JOptionPane.YES_OPTION);
+                               // Cancel delete if cancel pressed or dialog closed
+                               if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION) {
+                                       photoDeleted = _app.getTrackInfo().deleteCurrentPhoto(deletePointToo);
+                               }
+                       }
+                       // Add undo information to stack if necessary
+                       if (photoDeleted) {
+                               _app.completeFunction(undoAction, currentPhoto.getFile().getName() + " " + I18nManager.getText("confirm.media.removed"));
+                       }
+               }
+       }
+}
index 125b4b7496b0558c487cf87c2f63a83fbd2fae70..6c0e4d089cedcfc43e9b17ef7f5c0f328efcd6fb 100644 (file)
@@ -135,14 +135,19 @@ public class SaveConfig extends GenericFunction
                if (response == JFileChooser.APPROVE_OPTION)
                {
                        File saveFile = chooser.getSelectedFile();
+                       FileOutputStream outStream = null;
                        try
                        {
-                               Config.getAllConfig().store(new FileOutputStream(saveFile), "Prune config file");
+                               outStream = new FileOutputStream(saveFile);
+                               Config.getAllConfig().store(outStream, "Prune config file");
                        }
                        catch (IOException ioe) {
                                _app.showErrorMessageNoLookup(getNameKey(),
                                        I18nManager.getText("error.save.failed") + " : " + ioe.getMessage());
                        }
+                       finally {
+                               try {outStream.close();} catch (Exception e) {}
+                       }
                }
                _dialog.dispose();
                _dialog = null;
diff --git a/tim/prune/function/SearchWikipediaNames.java b/tim/prune/function/SearchWikipediaNames.java
new file mode 100644 (file)
index 0000000..4377df8
--- /dev/null
@@ -0,0 +1,144 @@
+package tim.prune.function;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+import javax.swing.JOptionPane;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Field;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
+import tim.prune.function.gpsies.GenericDownloaderFunction;
+import tim.prune.function.gpsies.GpsiesTrack;
+
+/**
+ * Function to search Wikipedia for place names
+ */
+public class SearchWikipediaNames extends GenericDownloaderFunction
+{
+       /** search term */
+       private String _searchTerm = null;
+       /** Maximum number of results to get */
+       private static final int MAX_RESULTS = 20;
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public SearchWikipediaNames(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.searchwikipedianames";
+       }
+
+       /**
+        * @param inColNum index of column, 0 or 1
+        * @return key for this column
+        */
+       protected String getColumnKey(int inColNum)
+       {
+               if (inColNum == 0) return "dialog.wikipedia.column.name";
+               return null;
+       }
+
+       /**
+        * Before dialog is shown, need to get search term
+        */
+       public void begin()
+       {
+               Object search = JOptionPane.showInputDialog(_app.getFrame(),
+                       I18nManager.getText("dialog.searchwikipedianames.search"),
+                       I18nManager.getText(getNameKey()),
+                       JOptionPane.QUESTION_MESSAGE, null, null, "");
+               if (search != null)
+               {
+                       _searchTerm = search.toString();
+                       if (!_searchTerm.equals("")) {
+                               super.begin();
+                       }
+               }
+       }
+
+       /**
+        * Run method to call geonames in separate thread
+        */
+       public void run()
+       {
+               _statusLabel.setText(I18nManager.getText("confirm.running"));
+
+               String descMessage = "";
+               InputStream inStream = null;
+
+               // language (only de and en available)
+               String lang = I18nManager.getText("wikipedia.lang");
+               if (lang.equals("de") || lang.equals("als")) {
+                       lang = "de";
+               }
+               else {
+                       lang = "en";
+               }
+               // Example http://ws.geonames.org/wikipediaSearch?q=london&maxRows=10
+               String urlString = "http://ws.geonames.org/wikipediaSearch?title=" + _searchTerm + "&maxRows=" + MAX_RESULTS
+                       + "&lang=" + lang;
+               // Parse the returned XML with a special handler
+               GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+               try
+               {
+                       URL url = new URL(urlString);
+                       SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+                       inStream = url.openStream();
+                       saxParser.parse(inStream, xmlHandler);
+               }
+               catch (Exception e) {
+                       descMessage = e.getClass().getName() + " - " + e.getMessage();
+               }
+               // Close stream and ignore errors
+               try {
+                       inStream.close();
+               } catch (Exception e) {}
+               // Add track list to model
+               ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
+               // TODO: Do a better job of sorting replies by relevance - use three different lists
+               _trackListModel.addTracks(trackList);
+
+               // Set status label according to error or "none found", leave blank if ok
+               if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
+                       descMessage = I18nManager.getText("dialog.gpsies.nonefound");
+               }
+               _statusLabel.setText(descMessage);
+       }
+
+       /**
+        * Load the selected track or point
+        */
+       protected void loadSelected()
+       {
+               // Find the row selected in the table and get the corresponding track
+               int rowNum = _trackTable.getSelectedRow();
+               if (rowNum >= 0 && rowNum < _trackListModel.getRowCount())
+               {
+                       String coords = _trackListModel.getTrack(rowNum).getDownloadLink();
+                       String[] latlon = coords.split(",");
+                       if (latlon.length == 2)
+                       {
+                               DataPoint point = new DataPoint(new Latitude(latlon[0]), new Longitude(latlon[1]), null);
+                               point.setFieldValue(Field.WAYPT_NAME, _trackListModel.getTrack(rowNum).getTrackName(), false);
+                               _app.createPoint(point);
+                       }
+               }
+               // Close the dialog
+               _cancelled = true;
+               _dialog.dispose();
+       }
+}
index 90bbc8fb4f598f15e491a4df5b76e3b68b057b82..f2ef5d3bb3073a5617fe3f2e38ca01a39dec658f 100644 (file)
@@ -41,14 +41,15 @@ public class SetLanguage extends GenericFunction
        private int _startIndex = 0;
 
        /** Names of languages for display in dropdown (not translated) */
-       private static final String[] LANGUAGE_NAMES = {"\u010de\u0161tina", "deutsch", "english", "espa\u00F1ol",
-               "fran\u00E7ais", "italiano", "nederlands", "polski", "portugu\u00EAs", "\u4e2d\u6587 (chinese)",
-               "\u65E5\u672C\u8A9E (japanese)", "schwiizerd\u00FC\u00FCtsch", "t\u00FCrk\u00E7e", "rom\u00E2n\u0103",
-               "afrikaans", "bahasa indonesia", "farsi"
+       private static final String[] LANGUAGE_NAMES = {"\u010de\u0161tina", "deutsch", "english",
+               "espa\u00F1ol", "fran\u00E7ais", "italiano", "magyar", "nederlands", "polski",
+               "portugu\u00EAs", "\u4e2d\u6587 (chinese)", "\u65E5\u672C\u8A9E (japanese)",
+               "\uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean)", "schwiizerd\u00FC\u00FCtsch", "t\u00FCrk\u00E7e",
+               "rom\u00E2n\u0103", "afrikaans", "bahasa indonesia", "farsi"
        };
        /** Associated language codes (must be in same order as names!) */
-       private static final String[] LANGUAGE_CODES = {"cz", "de", "en", "es", "fr", "it", "nl", "pl", "pt", "zh",
-               "ja", "de_ch", "tr", "ro", "af", "in", "fa"
+       private static final String[] LANGUAGE_CODES = {"cz", "de", "en", "es", "fr", "it", "hu",
+               "nl", "pl", "pt", "zh", "ja", "ko", "de_ch", "tr", "ro", "af", "in", "fa"
        };
 
 
diff --git a/tim/prune/function/SetLineWidth.java b/tim/prune/function/SetLineWidth.java
new file mode 100644 (file)
index 0000000..5ac64ae
--- /dev/null
@@ -0,0 +1,56 @@
+package tim.prune.function;
+
+import javax.swing.JOptionPane;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+
+public class SetLineWidth extends GenericFunction
+{
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public SetLineWidth(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.setlinewidth";
+       }
+
+
+       /**
+        * Run function
+        */
+       public void begin()
+       {
+               int currLineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
+               if (currLineWidth < 1 || currLineWidth > 4) {
+                       currLineWidth = 2;
+               }
+               Object lineWidthStr = JOptionPane.showInputDialog(_app.getFrame(),
+                       I18nManager.getText("dialog.setlinewidth.text"),
+                       I18nManager.getText(getNameKey()),
+                       JOptionPane.QUESTION_MESSAGE, null, null, "" + currLineWidth);
+               if (lineWidthStr != null)
+               {
+                       int lineWidth = 2;
+                       try {
+                               lineWidth = Integer.parseInt(lineWidthStr.toString());
+                               if (lineWidth >= 1 && lineWidth <= 4 && lineWidth != currLineWidth)
+                               {
+                                       Config.setConfigInt(Config.KEY_LINE_WIDTH, lineWidth);
+                                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+                               }
+                       }
+                       catch (NumberFormatException nfe) {};
+               }
+       }
+}
diff --git a/tim/prune/function/StopAudioFunction.java b/tim/prune/function/StopAudioFunction.java
new file mode 100644 (file)
index 0000000..1d58dda
--- /dev/null
@@ -0,0 +1,35 @@
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.FunctionLibrary;
+import tim.prune.GenericFunction;
+
+/**
+ * Class to stop playing the current audio file
+ */
+public class StopAudioFunction extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public StopAudioFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.stopaudio";
+       }
+
+       /**
+        * Perform function
+        */
+       public void begin()
+       {
+               PlayAudioFunction playFn = (PlayAudioFunction) FunctionLibrary.FUNCTION_PLAY_AUDIO;
+               playFn.stopClip();
+       }
+}
index 69c36355ca8d5b586a5995c5612b743a7efdd5c8..b48823840d7cfdca29075291bb4c91b83579e39b 100644 (file)
@@ -58,7 +58,7 @@ public class ClosePointsAlgorithm extends SingleParameterAlgorithm
                                if (!currPoint.isWaypoint())
                                {
                                        // Don't delete any photo points or start/end of segments
-                                       if (currPoint.getPhoto() == null
+                                       if (!currPoint.hasMedia()
                                                && !_trackDetails.isSegmentStart(i) && !_trackDetails.isSegmentEnd(i))
                                        {
                                                // Check current point against prevPoint
index 35ff61da1ea3b73cd7618315d73af34647096a32..b347305c7968c511aa9e4072e291d56000a100cd 100644 (file)
@@ -171,7 +171,7 @@ public class CompressTrackFunction extends GenericFunction
                for (int i=0; i<deleteFlags.length; i++)
                {
                        DataPoint point = _track.getPoint(i);
-                       point.setMarkedForDeletion(deleteFlags[i] && point.getPhoto() == null);
+                       point.setMarkedForDeletion(deleteFlags[i] && !point.hasMedia());
                }
 
                // Close dialog and inform listeners
index fa673c20df65e37797b520d9af4d0daa911b81af..ce3f5977abfe59dd11c50689130ef62bcdab44ce 100644 (file)
@@ -41,8 +41,8 @@ public class DuplicatePointAlgorithm extends CompressionAlgorithm
                        if (!inFlags[i])
                        {
                                DataPoint currPoint = _track.getPoint(i);
-                               // Don't delete any photo points
-                               if (currPoint.getPhoto() == null)
+                               // Don't delete any photo points or audio points
+                               if (!currPoint.hasMedia())
                                {
                                        // loop over last few points before this one
                                        for (int j=i-NUM_POINTS_TO_BACKTRACK; j<i; j++)
index 7a61d1c74fe00249c157369da87b7633887cfa03..07d4279b468b2a100c2f2d0c96e565b04a40b74e 100644 (file)
@@ -45,7 +45,7 @@ public class SingletonAlgorithm extends SingleParameterAlgorithm
                        {
                                // Don't delete any waypoints or photo points
                                // Only interested in start and end of segments
-                               if (!currPoint.isWaypoint() && currPoint.getPhoto() == null
+                               if (!currPoint.isWaypoint() && !currPoint.hasMedia()
                                        && _trackDetails.isSegmentStart(i) && _trackDetails.isSegmentEnd(i))
                                {
                                        // Measure distance from previous track point
index d7b5b9f18b02a99d8a51b1b6b86d5040a79be4b8..66a4a486e58b44e3df6e24d9d9a9992cb6104394 100644 (file)
@@ -43,7 +43,7 @@ public class WackyPointAlgorithm extends SingleParameterAlgorithm
                        if (!inFlags[i])
                        {
                                // Don't delete any waypoints or photo points, or start/end of segments
-                               if (!currPoint.isWaypoint() && currPoint.getPhoto() == null
+                               if (!currPoint.isWaypoint() && !currPoint.hasMedia()
                                        && !_trackDetails.isSegmentStart(i) && !_trackDetails.isSegmentEnd(i))
                                {
                                        // Measure distance from previous track point
index b41ab8bc5d012469607ff552b864443707b38dfd..dada3f0bd510c4e6163460d69b474fbd1679c6dd 100644 (file)
@@ -112,7 +112,12 @@ public class DistanceFunction extends GenericFunction
                // second table for distances
                _distModel = new DistanceTableModel();
                JTable distTable = new JTable(_distModel);
-               distTable.setAutoCreateRowSorter(true);
+               // Use reflection to call distTable.setAutoCreateRowSorter(true) which is new with Java 1.6
+               try {
+                       Class<?> d = Class.forName("javax.swing.JTable");
+                       d.getDeclaredMethod("setAutoCreateRowSorter", new Class[]{Boolean.TYPE}).invoke(distTable, Boolean.TRUE);
+               }
+               catch (Exception e) {}
                scrollPane = new JScrollPane(distTable);
                scrollPane.setPreferredSize(new Dimension(200, 250));
                mainPanel.add(scrollPane);
diff --git a/tim/prune/function/gpsies/GenericDownloaderFunction.java b/tim/prune/function/gpsies/GenericDownloaderFunction.java
new file mode 100644 (file)
index 0000000..743becf
--- /dev/null
@@ -0,0 +1,226 @@
+package tim.prune.function.gpsies;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.function.browser.BrowserLauncher;
+
+/**
+ * Function to load track information from any source,
+ * subclassed for special cases gpsies or wikipedia
+ */
+public abstract class GenericDownloaderFunction extends GenericFunction implements Runnable
+{
+       /** Dialog object */
+       protected JDialog _dialog = null;
+       /** list model */
+       protected TrackListModel _trackListModel = null;
+       /** track table */
+       protected JTable _trackTable = null;
+       /** Cancelled flag */
+       protected boolean _cancelled = false;
+       /** Status label */
+       protected JLabel _statusLabel = null;
+       /** Description box */
+       private JTextArea _descriptionBox = null;
+       /** Load button */
+       private JButton _loadButton = null;
+       /** Show button */
+       private JButton _showButton = null;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public GenericDownloaderFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Initialise dialog, show empty list
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       // add closing listener
+                       _dialog.addWindowListener(new WindowAdapter() {
+                               public void windowClosing(WindowEvent e) {
+                                       _cancelled = true;
+                               }
+                       });
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               // Clear list
+               _trackListModel.clear();
+               _loadButton.setEnabled(false);
+               _showButton.setEnabled(false);
+               _cancelled = false;
+               _descriptionBox.setText("");
+               // Start new thread to load list asynchronously
+               new Thread(this).start();
+
+               // Show dialog
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+
+               // Status label
+               _statusLabel = new JLabel(I18nManager.getText("confirm.running"));
+               dialogPanel.add(_statusLabel, BorderLayout.NORTH);
+               // Main panel with track list
+               _trackListModel = new TrackListModel(getColumnKey(0), getColumnKey(1));
+               _trackTable = new JTable(_trackListModel);
+               _trackTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+                       public void valueChanged(ListSelectionEvent e) {
+                               if (!e.getValueIsAdjusting())
+                               {
+                                       if (_trackTable.getSelectedRow() >= 0
+                                        && _trackTable.getSelectedRow() < _trackListModel.getRowCount())
+                                       {
+                                               _loadButton.setEnabled(true);
+                                               _showButton.setEnabled(true);
+                                               setDescription(_trackListModel.getTrack(_trackTable.getSelectedRow()).getDescription());
+                                               _descriptionBox.setCaretPosition(0);
+                                       }
+                                       else {
+                                               _descriptionBox.setText("");
+                                       }
+                               }
+                       }
+               });
+               _trackTable.getColumnModel().getColumn(0).setPreferredWidth(300);
+               if (_trackListModel.getColumnCount() > 1) {
+                       _trackTable.getColumnModel().getColumn(1).setPreferredWidth(70);
+               }
+               JScrollPane tablePane = new JScrollPane(_trackTable);
+               tablePane.setPreferredSize(new Dimension(450, 200));
+               // Panel to hold description label and box
+               JPanel descPanel = new JPanel();
+               descPanel.setLayout(new BorderLayout());
+               JLabel descLabel = new JLabel(I18nManager.getText("dialog.gpsies.description") + " :");
+               descPanel.add(descLabel, BorderLayout.NORTH);
+               _descriptionBox = new JTextArea(5, 20);
+               _descriptionBox.setEditable(false);
+               _descriptionBox.setLineWrap(true);
+               _descriptionBox.setWrapStyleWord(true);
+               JScrollPane descPane = new JScrollPane(_descriptionBox);
+               descPane.setPreferredSize(new Dimension(400, 80));
+               descPanel.add(descPane, BorderLayout.CENTER);
+               // Use split pane to split table from description
+               JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tablePane, descPanel);
+               splitPane.setResizeWeight(1.0);
+               dialogPanel.add(splitPane, BorderLayout.CENTER);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _loadButton = new JButton(I18nManager.getText("button.load"));
+               _loadButton.setEnabled(false);
+               _loadButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               loadSelected();
+                       }
+               });
+               buttonPanel.add(_loadButton);
+               _showButton = new JButton(I18nManager.getText("button.showwebpage"));
+               _showButton.setEnabled(false);
+               _showButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               showSelectedWebpage();
+                       }
+               });
+               buttonPanel.add(_showButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _cancelled = true;
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       /**
+        * @param inColNum index of column, 0 or 1
+        * @return key for this column
+        */
+       protected abstract String getColumnKey(int inColNum);
+
+       /**
+        * Set the description in the box
+        * @param inDesc description to set, or null for no description
+        */
+       private void setDescription(String inDesc)
+       {
+               String text = inDesc;
+               if (inDesc == null || inDesc.length() < 2) {
+                       text = I18nManager.getText("dialog.gpsies.nodescription");
+               }
+               _descriptionBox.setText(text);
+       }
+
+
+       /**
+        * Load the selected track or point
+        */
+       protected abstract void loadSelected();
+
+
+       /**
+        * Show the webpage for the selected item
+        */
+       private void showSelectedWebpage()
+       {
+               // Find the row selected in the table and show the corresponding url
+               int rowNum = _trackTable.getSelectedRow();
+               if (rowNum >= 0 && rowNum < _trackListModel.getRowCount())
+               {
+                       String url = _trackListModel.getTrack(rowNum).getWebUrl();
+                       BrowserLauncher.launchBrowser(url);
+               }
+               // Don't close the dialog
+       }
+}
index d1d83f34b053d03b7bfca5dc6755c815e9f416c7..804c4ce12e8d897369c47037b5b85c0951fff186 100644 (file)
@@ -1,36 +1,15 @@
 package tim.prune.function.gpsies;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
 
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSplitPane;
-import javax.swing.JTable;
-import javax.swing.JTextArea;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 import tim.prune.App;
-import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
-import tim.prune.function.browser.BrowserLauncher;
 import tim.prune.load.xml.XmlFileLoader;
 import tim.prune.load.xml.ZipFileLoader;
 
@@ -38,24 +17,8 @@ import tim.prune.load.xml.ZipFileLoader;
  * Function to load track information from Gpsies.com
  * according to the currently viewed area
  */
-public class GetGpsiesFunction extends GenericFunction implements Runnable
+public class GetGpsiesFunction extends GenericDownloaderFunction
 {
-       /** Dialog object */
-       private JDialog _dialog = null;
-       /** list model */
-       private TrackListModel _trackListModel = null;
-       /** track table */
-       private JTable _trackTable = null;
-       /** Cancelled flag */
-       private boolean _cancelled = false;
-       /** Status label */
-       private JLabel _statusLabel = null;
-       /** Description box */
-       private JTextArea _descriptionBox = null;
-       /** Load button */
-       private JButton _loadButton = null;
-       /** Show button */
-       private JButton _showButton = null;
        /** Number of results per page */
        private static final int RESULTS_PER_PAGE = 20;
        /** Maximum number of results to get */
@@ -66,8 +29,7 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
         * Constructor
         * @param inApp App object
         */
-       public GetGpsiesFunction(App inApp)
-       {
+       public GetGpsiesFunction(App inApp) {
                super(inApp);
        }
 
@@ -79,140 +41,15 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
        }
 
        /**
-        * Begin the function
-        */
-       public void begin()
-       {
-               // Initialise dialog, show empty list
-               if (_dialog == null)
-               {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-                       // add closing listener
-                       _dialog.addWindowListener(new WindowAdapter() {
-                               public void windowClosing(WindowEvent e) {
-                                       _cancelled = true;
-                               }
-                       });
-                       _dialog.getContentPane().add(makeDialogComponents());
-                       _dialog.pack();
-               }
-               // Clear list
-               _trackListModel.clear();
-               _loadButton.setEnabled(false);
-               _showButton.setEnabled(false);
-               _cancelled = false;
-               _descriptionBox.setText("");
-               // Start new thread to load list asynchronously
-               new Thread(this).start();
-
-               // Show dialog
-               _dialog.setVisible(true);
-       }
-
-
-       /**
-        * Create dialog components
-        * @return Panel containing all gui elements in dialog
+        * @param inColNum index of column, 0 or 1
+        * @return key for this column
         */
-       private Component makeDialogComponents()
+       protected String getColumnKey(int inColNum)
        {
-               JPanel dialogPanel = new JPanel();
-               dialogPanel.setLayout(new BorderLayout());
-
-               // Status label
-               _statusLabel = new JLabel(I18nManager.getText("confirm.running"));
-               dialogPanel.add(_statusLabel, BorderLayout.NORTH);
-               // Main panel with track list
-               _trackListModel = new TrackListModel();
-               _trackTable = new JTable(_trackListModel);
-               _trackTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
-                       public void valueChanged(ListSelectionEvent e) {
-                               if (!e.getValueIsAdjusting())
-                               {
-                                       if (_trackTable.getSelectedRow() >= 0
-                                        && _trackTable.getSelectedRow() < _trackListModel.getRowCount())
-                                       {
-                                               _loadButton.setEnabled(true);
-                                               _showButton.setEnabled(true);
-                                               setDescription(_trackListModel.getTrack(_trackTable.getSelectedRow()).getDescription());
-                                               _descriptionBox.setCaretPosition(0);
-                                       }
-                                       else {
-                                               _descriptionBox.setText("");
-                                       }
-                               }
-                       }
-               });
-               _trackTable.getColumnModel().getColumn(0).setPreferredWidth(300);
-               _trackTable.getColumnModel().getColumn(1).setPreferredWidth(70);
-               JScrollPane tablePane = new JScrollPane(_trackTable);
-               tablePane.setPreferredSize(new Dimension(450, 200));
-               // Panel to hold description label and box
-               JPanel descPanel = new JPanel();
-               descPanel.setLayout(new BorderLayout());
-               JLabel descLabel = new JLabel(I18nManager.getText("dialog.gpsies.description") + " :");
-               descPanel.add(descLabel, BorderLayout.NORTH);
-               _descriptionBox = new JTextArea(5, 20);
-               _descriptionBox.setEditable(false);
-               _descriptionBox.setLineWrap(true);
-               _descriptionBox.setWrapStyleWord(true);
-               JScrollPane descPane = new JScrollPane(_descriptionBox);
-               descPane.setPreferredSize(new Dimension(400, 80));
-               descPanel.add(descPane, BorderLayout.CENTER);
-               // Use split pane to split table from description
-               JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tablePane, descPanel);
-               splitPane.setResizeWeight(1.0);
-               dialogPanel.add(splitPane, BorderLayout.CENTER);
-
-               // button panel at bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               _loadButton = new JButton(I18nManager.getText("button.load"));
-               _loadButton.setEnabled(false);
-               _loadButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               loadSelectedTrack();
-                       }
-               });
-               buttonPanel.add(_loadButton);
-               _showButton = new JButton(I18nManager.getText("button.showwebpage"));
-               _showButton.setEnabled(false);
-               _showButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               showSelectedTrack();
-                       }
-               });
-               buttonPanel.add(_showButton);
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _cancelled = true;
-                               _dialog.dispose();
-                       }
-               });
-               buttonPanel.add(cancelButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
-               return dialogPanel;
+               if (inColNum == 0) return "dialog.gpsies.column.name";
+               return "dialog.gpsies.column.length";
        }
 
-       /**
-        * Set the description in the box
-        * @param inDesc description to set, or null for no description
-        */
-       private void setDescription(String inDesc)
-       {
-               String text = inDesc;
-               if (inDesc == null || inDesc.length() < 2) {
-                       text = I18nManager.getText("dialog.gpsies.nodescription");
-               }
-               _descriptionBox.setText(text);
-       }
 
        /**
         * Run method to call gpsies.com in separate thread
@@ -235,7 +72,6 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
                        String urlString = "http://www.gpsies.com/api.do?BBOX=" +
                                coords[1] + "," + coords[0] + "," + coords[3] + "," + coords[2] +
                                "&limit=" + RESULTS_PER_PAGE + "&resultPage=" + currPage;
-                       // System.out.println(urlString);
                        // Parse the returned XML with a special handler
                        GpsiesXmlHandler xmlHandler = new GpsiesXmlHandler();
                        try
@@ -260,7 +96,7 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
                        currPage++;
                }
                while (trackList != null && trackList.size() == RESULTS_PER_PAGE
-                && _trackListModel.getRowCount() < MAX_RESULTS && !_cancelled);
+                       && _trackListModel.getRowCount() < MAX_RESULTS && !_cancelled);
                // Set status label according to error or "none found", leave blank if ok
                if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
                        descMessage = I18nManager.getText("dialog.gpsies.nonefound");
@@ -268,11 +104,10 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
                _statusLabel.setText(descMessage);
        }
 
-
        /**
-        * Load the selected track
+        * Load the selected track or point
         */
-       private void loadSelectedTrack()
+       protected void loadSelected()
        {
                // Find the row selected in the table and get the corresponding track
                int rowNum = _trackTable.getSelectedRow();
@@ -293,22 +128,4 @@ public class GetGpsiesFunction extends GenericFunction implements Runnable
                _cancelled = true;
                _dialog.dispose();
        }
-
-
-       /**
-        * Show the webpage for the selected track
-        */
-       private void showSelectedTrack()
-       {
-               // Find the row selected in the table and show the corresponding url
-               int rowNum = _trackTable.getSelectedRow();
-               if (rowNum >= 0 && rowNum < _trackListModel.getRowCount())
-               {
-                       String id = _trackListModel.getTrack(rowNum).getFileId();
-                       BrowserLauncher.launchBrowser("http://gpsies.com/map.do?fileId=" + id);
-               }
-               // Close the dialog
-               _cancelled = true;
-               _dialog.dispose();
-       }
 }
index 74411951d89a211322415c2b3ee54db9296e8f16..da1339ee113883c19788d9df6bfbeb51242bb4f7 100644 (file)
@@ -9,8 +9,8 @@ public class GpsiesTrack
        private String _trackName = null;
        /** Description */
        private String _description = null;
-       /** File id for more details */
-       private String _fileId = null;
+       /** Web page for more details */
+       private String _webUrl = null;
        /** Track length in metres */
        private double _trackLength = 0.0;
        /** Download link */
@@ -50,19 +50,19 @@ public class GpsiesTrack
        }
 
        /**
-        * @param inId id of track
+        * @param inUrl web page url
         */
-       public void setFileId(String inId)
+       public void setWebUrl(String inUrl)
        {
-               _fileId = inId;
+               _webUrl = inUrl;
        }
 
        /**
-        * @return file id
+        * @return web url
         */
-       public String getFileId()
+       public String getWebUrl()
        {
-               return _fileId;
+               return _webUrl;
        }
 
        /**
index e63916029450a4ae0f5c3159cc65e26405e127a5..3d801be5bc845c5e449c486626ef0837d89ba4d6 100644 (file)
@@ -11,13 +11,6 @@ import org.xml.sax.helpers.DefaultHandler;
  */
 public class GpsiesXmlHandler extends DefaultHandler
 {
-       private boolean _inTracks = false;
-       private boolean _inTrack = false;
-       private boolean _inTrackName = false;
-       private boolean _inDescription = false;
-       private boolean _inFileId = false;
-       private boolean _inTrackLength = false;
-       private boolean _inLink = false;
        private String _value = null;
        private ArrayList<GpsiesTrack> _trackList = null;
        private GpsiesTrack _track = null;
@@ -30,18 +23,12 @@ public class GpsiesXmlHandler extends DefaultHandler
                Attributes inAttributes) throws SAXException
        {
                if (inTagName.equals("tracks")) {
-                       _inTracks = true;
                        _trackList = new ArrayList<GpsiesTrack>();
                }
-               else if (_inTracks && inTagName.equals("track")) {
-                       _inTrack = true;
+               else if (inTagName.equals("track")) {
                        _track = new GpsiesTrack();
                }
-               else if (_inTrack && inTagName.equals("title")) {_inTrackName = true;}
-               else if (_inTrack && inTagName.equals("description")) {_inDescription = true;}
-               else if (_inTrack && inTagName.equals("fileId")) {_inFileId = true;}
-               else if (_inTrack && inTagName.equals("trackLengthM")) {_inTrackLength = true;}
-               else if (_inTrack && inTagName.equals("downloadLink")) {_inLink = true;}
+               _value = null;
                super.startElement(inUri, inLocalName, inTagName, inAttributes);
        }
 
@@ -51,33 +38,26 @@ public class GpsiesXmlHandler extends DefaultHandler
        public void endElement(String inUri, String inLocalName, String inTagName)
        throws SAXException
        {
-               if (inTagName.equals("tracks")) {_inTracks = false;}
-               else if (_inTrack && inTagName.equals("track")) {
+               if (inTagName.equals("track")) {
                        _trackList.add(_track);
-                       _inTrack = false;
                }
-               else if (_inTrackName && inTagName.equals("title")) {
+               else if (inTagName.equals("title")) {
                        _track.setTrackName(_value);
-                       _inTrackName = false;
                }
-               else if (_inDescription && inTagName.equals("description")) {
+               else if (inTagName.equals("description")) {
                        _track.setDescription(_value);
-                       _inDescription = false;
                }
-               else if (_inFileId && inTagName.equals("fileId")) {
-                       _track.setFileId(_value);
-                       _inFileId = false;
+               else if (inTagName.equals("fileId")) {
+                       _track.setWebUrl("http://gpsies.com/map.do?fileId=" + _value);
                }
-               else if (_inTrackLength && inTagName.equals("trackLengthM")) {
+               else if (inTagName.equals("trackLengthM")) {
                        try {
                                _track.setLength(Double.parseDouble(_value));
                        }
                        catch (NumberFormatException nfe) {}
-                       _inTrackLength = false;
                }
-               else if (_inLink && inTagName.equals("downloadLink")) {
+               else if (inTagName.equals("downloadLink")) {
                        _track.setDownloadLink(_value);
-                       _inLink = false;
                }
                super.endElement(inUri, inLocalName, inTagName);
        }
@@ -88,9 +68,8 @@ public class GpsiesXmlHandler extends DefaultHandler
        public void characters(char[] inCh, int inStart, int inLength)
        throws SAXException
        {
-               _value = new String(inCh, inStart, inLength);
-               // System.out.println("Value: '" + value + "'");
-               // TODO: Note, this doesn't cope well with split characters for really long descriptions etc
+               String value = new String(inCh, inStart, inLength);
+               _value = (_value==null?value:_value+value);
                super.characters(inCh, inStart, inLength);
        }
 
index 3bdeb162eb73372f32916dfcf05da4e82ff17f26..3a7326f647409b131d70964ad48279ac086ab275 100644 (file)
@@ -17,17 +17,26 @@ public class TrackListModel extends AbstractTableModel
        /** List of tracks */
        private ArrayList<GpsiesTrack> _trackList = null;
        /** Column heading for track name */
-       private static final String _nameColLabel = I18nManager.getText("dialog.gpsies.column.name");
+       private String _nameColLabel = null;
        /** Column heading for length */
-       private static final String _lengthColLabel = I18nManager.getText("dialog.gpsies.column.length");
+       private String _lengthColLabel = null;
+       /** Number of columns */
+       private int _numColumns = 2;
        /** Formatter for distances */
        private NumberFormat _distanceFormatter = NumberFormat.getInstance();
 
        /**
         * Constructor
+        * @param inColumn1Key key for first column
+        * @param inColumn2Key key for second column
         */
-       public TrackListModel()
+       public TrackListModel(String inColumn1Key, String inColumn2Key)
        {
+               _nameColLabel = I18nManager.getText(inColumn1Key);
+               if (inColumn2Key != null) {
+                       _lengthColLabel = I18nManager.getText(inColumn2Key);
+               }
+               _numColumns = (_lengthColLabel != null?2:1);
                _distanceFormatter.setMaximumFractionDigits(1);
        }
 
@@ -36,7 +45,7 @@ public class TrackListModel extends AbstractTableModel
         */
        public int getColumnCount()
        {
-               return 2;
+               return _numColumns;
        }
 
        /**
index 6bd762f56c637dbda7f8cb1eb54ba21049fa7a39..49bd000c79d99e688dfef33c0256210a218a1dd9 100644 (file)
@@ -250,6 +250,7 @@ public class UploadGpsiesFunction extends GenericFunction
         */
        private void startUpload()
        {
+               BufferedReader reader = null;
                try
                {
                        FormPoster poster = new FormPoster(new URL(GPSIES_URL));
@@ -277,12 +278,12 @@ public class UploadGpsiesFunction extends GenericFunction
                        _writer = new OutputStreamWriter(oStream);
                        new Thread(new Runnable() {
                                public void run() {
-                                       boolean[] saveFlags = {true, true, true, false, true}; // export everything
+                                       boolean[] saveFlags = {true, true, true, true, false, true}; // export everything
                                        try {
                                                GpxExporter.exportData(_writer, _app.getTrackInfo(), _nameField.getText(), null, saveFlags, false);
-                                               _writer.close();
-                                       } catch (IOException e) {
-                                               e.printStackTrace();
+                                       } catch (IOException e) {}
+                                       finally {
+                                               try {_writer.close();} catch (IOException e) {}
                                        }
                                }
                        }).start();
@@ -290,7 +291,7 @@ public class UploadGpsiesFunction extends GenericFunction
 
                        BufferedInputStream answer = new BufferedInputStream(poster.post());
                        int response = poster.getResponseCode();
-                       BufferedReader reader = new BufferedReader(new InputStreamReader(answer));
+                       reader = new BufferedReader(new InputStreamReader(answer));
                        String line = reader.readLine();
                        // Try to extract gpsies page url from the returned message
                        String pageUrl = null;
@@ -321,6 +322,9 @@ public class UploadGpsiesFunction extends GenericFunction
                        _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getText("error.gpsies.uploadfailed") + ": "
                                + ioe.getClass().getName() + " : " + ioe.getMessage());
                }
+               finally {
+                       try {if (reader != null) reader.close();} catch (IOException e) {}
+               }
                _dialog.dispose();
        }
 }
diff --git a/tim/prune/gui/AudioListener.java b/tim/prune/gui/AudioListener.java
new file mode 100644 (file)
index 0000000..fe1e933
--- /dev/null
@@ -0,0 +1,52 @@
+package tim.prune.gui;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JProgressBar;
+
+import tim.prune.FunctionLibrary;
+import tim.prune.function.PlayAudioFunction;
+
+/**
+ * Class to update the supplied progress bar on the basis of
+ * the currently playing audio file (if any)
+ */
+public class AudioListener implements Runnable, ActionListener
+{
+       /** progress bar */
+       private JProgressBar _progressBar = null;
+
+       /**
+        * Constructor
+        * @param inBar progress bar object to update
+        */
+       public AudioListener(JProgressBar inBar) {
+               _progressBar = inBar;
+       }
+
+       /**
+        * React to button press
+        */
+       public void actionPerformed(ActionEvent inEvent) {
+               new Thread(this).start();
+       }
+
+       /**
+        * Loop and update progress bar
+        */
+       public void run()
+       {
+               int progress = 0;
+               while (progress >= 0)
+               {
+                       try {
+                               Thread.sleep(400);
+                       }
+                       catch (InterruptedException e) {}
+                       progress = ((PlayAudioFunction) FunctionLibrary.FUNCTION_PLAY_AUDIO).getPercentage();
+                       _progressBar.setVisible(progress >= 0);
+                       _progressBar.setValue(progress);
+               }
+       }
+}
index 9cfd7cee703d9039bc4d520ab2888b0de89100ed..dee14a9cd72c3d8016c54b99f165c0c62175e959 100644 (file)
@@ -3,6 +3,7 @@ package tim.prune.gui;
 import java.awt.BorderLayout;
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.FlowLayout;
 import java.awt.Font;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
@@ -16,6 +17,7 @@ import javax.swing.JButton;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import javax.swing.JProgressBar;
 import javax.swing.border.EtchedBorder;
 
 import tim.prune.DataSubscriber;
@@ -25,6 +27,7 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.Altitude;
+import tim.prune.data.AudioFile;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Distance;
@@ -55,12 +58,22 @@ public class DetailsDisplay extends GenericDisplay
        private JLabel _aveSpeedLabel = null;
 
        // Photo details
+       private JPanel _photoDetailsPanel = null;
        private JLabel _photoLabel = null;
        private PhotoThumbnail _photoThumbnail = null;
        private JLabel _photoTimestampLabel = null;
        private JLabel _photoConnectedLabel = null;
        private JPanel _rotationButtons = null;
 
+       // Audio details
+       private JPanel _audioDetailsPanel = null;
+       private JLabel _audioLabel = null;
+       private JLabel _audioConnectedLabel = null;
+       private JLabel _audioTimestampLabel = null;
+       private JLabel _audioLengthLabel = null;
+       private JProgressBar _audioProgress = null;
+       private JPanel _playAudioPanel = null;
+
        // Units
        private JComboBox _coordFormatDropdown = null;
        private JComboBox _distUnitsDropdown = null;
@@ -81,6 +94,7 @@ public class DetailsDisplay extends GenericDisplay
        private static final String LABEL_RANGE_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": ";
        private static final String LABEL_RANGE_CLIMB = I18nManager.getText("details.range.climb") + ": ";
        private static final String LABEL_RANGE_DESCENT = ", " + I18nManager.getText("details.range.descent") + ": ";
+       private static final String LABEL_AUDIO_FILE = I18nManager.getText("details.audio.file") + ": ";
        private static String LABEL_POINT_ALTITUDE_UNITS = null;
        private static Altitude.Format LABEL_POINT_ALTITUDE_FORMAT = Altitude.Format.NO_FORMAT;
 
@@ -97,18 +111,11 @@ public class DetailsDisplay extends GenericDisplay
                JPanel mainPanel = new JPanel();
                mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
                mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+               Font biggerFont = new JLabel().getFont();
+               biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
 
                // Point details panel
-               JPanel pointDetailsPanel = new JPanel();
-               pointDetailsPanel.setLayout(new BoxLayout(pointDetailsPanel, BoxLayout.Y_AXIS));
-               pointDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
-                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
-               );
-               JLabel pointDetailsLabel = new JLabel(I18nManager.getText("details.pointdetails"));
-               Font biggerFont = pointDetailsLabel.getFont();
-               biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
-               pointDetailsLabel.setFont(biggerFont);
-               pointDetailsPanel.add(pointDetailsLabel);
+               JPanel pointDetailsPanel = makeDetailsPanel("details.pointdetails", biggerFont);
                _indexLabel = new JLabel(I18nManager.getText("details.nopointselection"));
                pointDetailsPanel.add(_indexLabel);
                _latLabel = new JLabel("");
@@ -118,6 +125,7 @@ public class DetailsDisplay extends GenericDisplay
                _altLabel = new JLabel("");
                pointDetailsPanel.add(_altLabel);
                _timeLabel = new JLabel("");
+               _timeLabel.setMinimumSize(new Dimension(120, 10));
                pointDetailsPanel.add(_timeLabel);
                _speedLabel = new JLabel("");
                pointDetailsPanel.add(_speedLabel);
@@ -128,14 +136,7 @@ public class DetailsDisplay extends GenericDisplay
                pointDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
 
                // range details panel
-               JPanel rangeDetailsPanel = new JPanel();
-               rangeDetailsPanel.setLayout(new BoxLayout(rangeDetailsPanel, BoxLayout.Y_AXIS));
-               rangeDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
-                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
-               );
-               JLabel rangeDetailsLabel = new JLabel(I18nManager.getText("details.rangedetails"));
-               rangeDetailsLabel.setFont(biggerFont);
-               rangeDetailsPanel.add(rangeDetailsLabel);
+               JPanel rangeDetailsPanel = makeDetailsPanel("details.rangedetails", biggerFont);
                _rangeLabel = new JLabel(I18nManager.getText("details.norangeselection"));
                rangeDetailsPanel.add(_rangeLabel);
                _distanceLabel = new JLabel("");
@@ -151,40 +152,68 @@ public class DetailsDisplay extends GenericDisplay
                rangeDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
 
                // photo details panel
-               JPanel photoDetailsPanel = new JPanel();
-               photoDetailsPanel.setLayout(new BoxLayout(photoDetailsPanel, BoxLayout.Y_AXIS));
-               photoDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
-                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
-               );
-               JLabel photoDetailsLabel = new JLabel(I18nManager.getText("details.photodetails"));
-               photoDetailsLabel.setFont(biggerFont);
-               photoDetailsPanel.add(photoDetailsLabel);
+               _photoDetailsPanel = makeDetailsPanel("details.photodetails", biggerFont);
                _photoLabel = new JLabel(I18nManager.getText("details.nophoto"));
-               photoDetailsPanel.add(_photoLabel);
+               _photoDetailsPanel.add(_photoLabel);
                _photoTimestampLabel = new JLabel("");
-               photoDetailsPanel.add(_photoTimestampLabel);
+               _photoTimestampLabel.setMinimumSize(new Dimension(120, 10));
+               _photoDetailsPanel.add(_photoTimestampLabel);
                _photoConnectedLabel = new JLabel("");
-               photoDetailsPanel.add(_photoConnectedLabel);
+               _photoDetailsPanel.add(_photoConnectedLabel);
                _photoThumbnail = new PhotoThumbnail();
                _photoThumbnail.setVisible(false);
                _photoThumbnail.setPreferredSize(new Dimension(100, 100));
-               photoDetailsPanel.add(_photoThumbnail);
+               _photoDetailsPanel.add(_photoThumbnail);
                // Rotate buttons
                JButton rotLeft = makeRotateButton(IconManager.ROTATE_LEFT, FunctionLibrary.FUNCTION_ROTATE_PHOTO_LEFT);
                JButton rotRight = makeRotateButton(IconManager.ROTATE_RIGHT, FunctionLibrary.FUNCTION_ROTATE_PHOTO_RIGHT);
+               JButton popup = makeRotateButton(IconManager.SHOW_DETAILS, FunctionLibrary.FUNCTION_PHOTO_POPUP);
                _rotationButtons = new JPanel();
                _rotationButtons.add(rotLeft);
                _rotationButtons.add(rotRight);
+               _rotationButtons.add(Box.createHorizontalStrut(10));
+               _rotationButtons.add(popup);
                _rotationButtons.setAlignmentX(Component.LEFT_ALIGNMENT);
                _rotationButtons.setVisible(false);
-               photoDetailsPanel.add(_rotationButtons);
+               _photoDetailsPanel.add(_rotationButtons);
+               _photoDetailsPanel.setVisible(false);
+
+               // audio details panel
+               _audioDetailsPanel = makeDetailsPanel("details.audiodetails", biggerFont);
+               _audioLabel = new JLabel(I18nManager.getText("details.noaudio"));
+               _audioDetailsPanel.add(_audioLabel);
+               _audioTimestampLabel = new JLabel("");
+               _audioTimestampLabel.setMinimumSize(new Dimension(120, 10));
+               _audioDetailsPanel.add(_audioTimestampLabel);
+               _audioLengthLabel = new JLabel("");
+               _audioDetailsPanel.add(_audioLengthLabel);
+               _audioConnectedLabel = new JLabel("");
+               _audioDetailsPanel.add(_audioConnectedLabel);
+               _audioProgress = new JProgressBar(0, 100);
+               _audioProgress.setString(I18nManager.getText("details.audio.playing"));
+               _audioProgress.setStringPainted(true);
+               _audioProgress.setVisible(false);
+               _audioDetailsPanel.add(_audioProgress);
+               _playAudioPanel = new JPanel();
+               _playAudioPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton playAudio = makeRotateButton(IconManager.PLAY_AUDIO, FunctionLibrary.FUNCTION_PLAY_AUDIO);
+               playAudio.addActionListener(new AudioListener(_audioProgress));
+               _playAudioPanel.add(playAudio);
+               JButton stopAudio = makeRotateButton(IconManager.STOP_AUDIO, FunctionLibrary.FUNCTION_STOP_AUDIO);
+               _playAudioPanel.add(stopAudio);
+               _playAudioPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               _playAudioPanel.setVisible(false);
+               _audioDetailsPanel.add(_playAudioPanel);
+               _audioDetailsPanel.setVisible(false);
 
                // add the details panels to the main panel
                mainPanel.add(pointDetailsPanel);
                mainPanel.add(Box.createVerticalStrut(5));
                mainPanel.add(rangeDetailsPanel);
                mainPanel.add(Box.createVerticalStrut(5));
-               mainPanel.add(photoDetailsPanel);
+               mainPanel.add(_photoDetailsPanel);
+               mainPanel.add(Box.createVerticalStrut(5));
+               mainPanel.add(_audioDetailsPanel);
                mainPanel.add(Box.createVerticalStrut(5));
                // add the main panel at the top
                add(mainPanel, BorderLayout.NORTH);
@@ -234,6 +263,7 @@ public class DetailsDisplay extends GenericDisplay
                // Update current point data, if any
                DataPoint currentPoint = _trackInfo.getCurrentPoint();
                Selection selection = _trackInfo.getSelection();
+               if ((inUpdateType | DATA_ADDED_OR_REMOVED) > 0) selection.markInvalid();
                int currentPointIndex = selection.getCurrentPointIndex();
                _speedLabel.setText("");
                Distance.Units distUnits = _distUnitsDropdown.getSelectedIndex()==0?Distance.Units.KILOMETRES:Distance.Units.MILES;
@@ -343,6 +373,7 @@ public class DetailsDisplay extends GenericDisplay
                        }
                }
                // show photo details and thumbnail
+               _photoDetailsPanel.setVisible(_trackInfo.getPhotoList().getNumPhotos() > 0);
                Photo currentPhoto = _trackInfo.getPhotoList().getPhoto(_trackInfo.getSelection().getCurrentPhotoIndex());
                if ((currentPoint == null || currentPoint.getPhoto() == null) && currentPhoto == null)
                {
@@ -358,7 +389,7 @@ public class DetailsDisplay extends GenericDisplay
                        if (currentPhoto == null) {currentPhoto = currentPoint.getPhoto();}
                        _photoLabel.setText(I18nManager.getText("details.photofile") + ": " + currentPhoto.getFile().getName());
                        _photoTimestampLabel.setText(LABEL_POINT_TIMESTAMP + currentPhoto.getTimestamp().getText());
-                       _photoConnectedLabel.setText(I18nManager.getText("details.photo.connected") + ": "
+                       _photoConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
                                + (currentPhoto.getCurrentStatus() == Photo.Status.NOT_CONNECTED ?
                                        I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes")));
                        _photoThumbnail.setVisible(true);
@@ -367,6 +398,27 @@ public class DetailsDisplay extends GenericDisplay
                        if ((inUpdateType & DataSubscriber.PHOTOS_MODIFIED) > 0) {_photoThumbnail.refresh();}
                }
                _photoThumbnail.repaint();
+
+               // audio details
+               _audioDetailsPanel.setVisible(_trackInfo.getAudioList().getNumAudios() > 0);
+               AudioFile currentAudio = _trackInfo.getAudioList().getAudio(_trackInfo.getSelection().getCurrentAudioIndex());
+               if (currentAudio == null) {
+                       _audioLabel.setText(I18nManager.getText("details.noaudio"));
+                       _audioTimestampLabel.setText("");
+                       _audioLengthLabel.setText("");
+                       _audioConnectedLabel.setText("");
+               }
+               else
+               {
+                       _audioLabel.setText(LABEL_AUDIO_FILE + currentAudio.getFile().getName());
+                       _audioTimestampLabel.setText(LABEL_POINT_TIMESTAMP + currentAudio.getTimestamp().getText());
+                       int audioLength = currentAudio.getLengthInSeconds();
+                       _audioLengthLabel.setText(audioLength < 0?"":LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(audioLength));
+                       _audioConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
+                               + (currentAudio.getCurrentStatus() == Photo.Status.NOT_CONNECTED ?
+                                       I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes")));
+               }
+               _playAudioPanel.setVisible(currentAudio != null);
        }
 
 
@@ -455,6 +507,25 @@ public class DetailsDisplay extends GenericDisplay
                return inCoord;
        }
 
+       /**
+        * Make a details subpanel
+        * @param inNameKey key to use for top label
+        * @param inFont font for top label
+        * @return panel with correct layout, label
+        */
+       private static JPanel makeDetailsPanel(String inNameKey, Font inFont)
+       {
+               JPanel detailsPanel = new JPanel();
+               detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
+               detailsPanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
+               );
+               JLabel detailsLabel = new JLabel(I18nManager.getText(inNameKey));
+               detailsLabel.setFont(inFont);
+               detailsPanel.add(detailsLabel);
+               return detailsPanel;
+       }
+
        /**
         * Create a little button for rotating the current photo
         * @param inIcon icon to use (from IconManager)
diff --git a/tim/prune/gui/DialogCloser.java b/tim/prune/gui/DialogCloser.java
new file mode 100644 (file)
index 0000000..1085545
--- /dev/null
@@ -0,0 +1,33 @@
+package tim.prune.gui;
+
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JDialog;
+
+/**
+ * Convenience class to close a dialog when the escape key is pressed
+ */
+public class DialogCloser extends KeyAdapter
+{
+       /** dialog to close */
+       private JDialog _dialog = null;
+
+       /**
+        * Constructor
+        * @param inDialog dialog to close
+        */
+       public DialogCloser(JDialog inDialog) {
+               _dialog = inDialog;
+       }
+
+       /**
+        * React to the release of the escape key
+        */
+       public void keyReleased(KeyEvent e)
+       {
+               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                       _dialog.dispose();
+               }
+       }
+}
index a42d759a8dc8f123d090cfa105ba94ba7902a9bf..858e97a2dd9daeeed49706443b8d2b3eda5d9190 100644 (file)
@@ -60,6 +60,12 @@ public abstract class IconManager
        public static final String ROTATE_LEFT = "rotate_left_icon.png";
        /** Icon for rotating photos rightwards */
        public static final String ROTATE_RIGHT = "rotate_right_icon.png";
+       /** Icon for showing photo popup */
+       public static final String SHOW_DETAILS = "show_details_icon.gif";
+       /** Icon for playing audio file */
+       public static final String PLAY_AUDIO = "play_audio.gif";
+       /** Icon for stopping the current audio file */
+       public static final String STOP_AUDIO = "stop_audio.gif";
 
        /**
         * Get the specified image
diff --git a/tim/prune/gui/MediaListModel.java b/tim/prune/gui/MediaListModel.java
new file mode 100644 (file)
index 0000000..2d8eb6e
--- /dev/null
@@ -0,0 +1,47 @@
+package tim.prune.gui;
+
+import javax.swing.AbstractListModel;
+
+import tim.prune.data.MediaFile;
+import tim.prune.data.MediaList;
+
+/**
+ * Class to act as list model for the photo list and audio list
+ */
+public class MediaListModel extends AbstractListModel
+{
+       /** media list */
+       MediaList _media = null;
+
+       /**
+        * Constructor giving MediaList object
+        * @param inList MediaList
+        */
+       public MediaListModel(MediaList inList) {
+               _media = inList;
+       }
+
+       /**
+        * @see javax.swing.ListModel#getSize()
+        */
+       public int getSize() {
+               return _media.getNumMedia();
+       }
+
+       /**
+        * @see javax.swing.ListModel#getElementAt(int)
+        */
+       public Object getElementAt(int inIndex)
+       {
+               MediaFile m = _media.getMedia(inIndex);
+               // * means modified since loading
+               return (m.getCurrentStatus() == m.getOriginalStatus()?"":"* ") + m.getFile().getName();
+       }
+
+       /**
+        * Fire event to notify that contents have changed
+        */
+       public void fireChanged() {
+               this.fireContentsChanged(this, 0, getSize()-1);
+       }
+}
index 588b49e2e81ca3e5e318e2ef65d7decd93e8f9e7..47044f2b568637eec671ab38355226663eda18b8 100644 (file)
@@ -20,8 +20,8 @@ import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
+import tim.prune.data.AudioFile;
 import tim.prune.data.Photo;
-import tim.prune.data.PhotoList;
 import tim.prune.data.Selection;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
@@ -37,7 +37,6 @@ public class MenuManager implements DataSubscriber
        private App _app = null;
        private Track _track = null;
        private Selection _selection = null;
-       private PhotoList _photos = null;
 
        // Menu items which need enabling/disabling
        private JMenuItem _sendGpsItem = null;
@@ -77,18 +76,26 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _getGpsiesItem = null;
        private JMenuItem _uploadGpsiesItem = null;
        private JMenuItem _lookupSrtmItem = null;
+       private JMenuItem _lookupWikipediaItem = null;
+       private JMenuItem _downloadOsmItem = null;
        private JMenuItem _distanceItem = null;
        private JMenuItem _fullRangeDetailsItem = null;
        private JMenuItem _saveExifItem = null;
+       private JMenuItem _photoPopupItem = null;
        private JMenuItem _selectNoPhotoItem = null;
        private JMenuItem _connectPhotoItem = null;
-       private JMenuItem _deletePhotoItem = null;
+       private JMenuItem _removePhotoItem = null;
        private JMenuItem _disconnectPhotoItem = null;
        private JMenuItem _correlatePhotosItem = null;
        private JMenuItem _rearrangePhotosItem = null;
        private JMenuItem _rotatePhotoLeft = null;
        private JMenuItem _rotatePhotoRight = null;
        private JMenuItem _ignoreExifThumb = null;
+       private JMenuItem _connectAudioItem = null;
+       private JMenuItem _disconnectAudioItem = null;
+       private JMenuItem _removeAudioItem = null;
+       private JMenuItem _correlateAudiosItem = null;
+       private JMenuItem _selectNoAudioItem = null;
        private JCheckBoxMenuItem _onlineCheckbox = null;
 
        // ActionListeners for reuse by menu and toolbar
@@ -101,7 +108,6 @@ public class MenuManager implements DataSubscriber
        private ActionListener _deleteRangeAction = null;
        private ActionListener _selectStartAction = null;
        private ActionListener _selectEndAction = null;
-       private ActionListener _connectPhotoAction = null;
 
        // Toolbar buttons which need enabling/disabling
        private JButton _saveButton = null;
@@ -111,7 +117,7 @@ public class MenuManager implements DataSubscriber
        private JButton _deleteRangeButton = null;
        private JButton _selectStartButton = null;
        private JButton _selectEndButton = null;
-       private JButton _connectPhotoButton = null;
+       private JButton _connectButton = null;
 
        /** Array of key events */
        private static final int[] KEY_EVENTS = {
@@ -132,7 +138,6 @@ public class MenuManager implements DataSubscriber
                _app = inApp;
                _track = inTrackInfo.getTrack();
                _selection = inTrackInfo.getSelection();
-               _photos = inTrackInfo.getPhotoList();
        }
 
 
@@ -149,8 +154,7 @@ public class MenuManager implements DataSubscriber
                JMenuItem openMenuItem = new JMenuItem(I18nManager.getText("function.open"));
                setShortcut(openMenuItem, "shortcut.menu.file.open");
                _openFileAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.openFile();
                        }
                };
@@ -159,29 +163,29 @@ public class MenuManager implements DataSubscriber
                // Add photos
                JMenuItem addPhotosMenuItem = new JMenuItem(I18nManager.getText("menu.file.addphotos"));
                _addPhotoAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.addPhotos();
                        }
                };
                addPhotosMenuItem.addActionListener(_addPhotoAction);
                fileMenu.add(addPhotosMenuItem);
+               // Add audio files
+               JMenuItem addAudioMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_LOAD_AUDIO);
+               fileMenu.add(addAudioMenuItem);
                fileMenu.addSeparator();
                // Load from GPS
                JMenuItem loadFromGpsMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_GPSLOAD);
                setShortcut(loadFromGpsMenuItem, "shortcut.menu.file.load");
                fileMenu.add(loadFromGpsMenuItem);
                // Send to GPS
-               _sendGpsItem = makeMenuItem(FunctionLibrary.FUNCTION_GPSSAVE);
-               _sendGpsItem.setEnabled(false);
+               _sendGpsItem = makeMenuItem(FunctionLibrary.FUNCTION_GPSSAVE, false);
                fileMenu.add(_sendGpsItem);
                fileMenu.addSeparator();
                // Save
                _saveItem = new JMenuItem(I18nManager.getText("menu.file.save"), KeyEvent.VK_S);
                setShortcut(_saveItem, "shortcut.menu.file.save");
                _saveAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.saveFile();
                        }
                };
@@ -189,26 +193,21 @@ public class MenuManager implements DataSubscriber
                _saveItem.setEnabled(false);
                fileMenu.add(_saveItem);
                // Export - Kml
-               _exportKmlItem = makeMenuItem(FunctionLibrary.FUNCTION_KMLEXPORT);
-               _exportKmlItem.setEnabled(false);
+               _exportKmlItem = makeMenuItem(FunctionLibrary.FUNCTION_KMLEXPORT, false);
                fileMenu.add(_exportKmlItem);
                // Gpx
-               _exportGpxItem = makeMenuItem(FunctionLibrary.FUNCTION_GPXEXPORT);
-               _exportGpxItem.setEnabled(false);
+               _exportGpxItem = makeMenuItem(FunctionLibrary.FUNCTION_GPXEXPORT, false);
                fileMenu.add(_exportGpxItem);
                // Pov
-               _exportPovItem = makeMenuItem(FunctionLibrary.FUNCTION_POVEXPORT);
-               _exportPovItem.setEnabled(false);
+               _exportPovItem = makeMenuItem(FunctionLibrary.FUNCTION_POVEXPORT, false);
                fileMenu.add(_exportPovItem);
                // Svg
-               _exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT);
-               _exportSvgItem.setEnabled(false);
+               _exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT, false);
                fileMenu.add(_exportSvgItem);
                fileMenu.addSeparator();
                JMenuItem exitMenuItem = new JMenuItem(I18nManager.getText("menu.file.exit"));
                exitMenuItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.exit();
                        }
                });
@@ -220,8 +219,7 @@ public class MenuManager implements DataSubscriber
                _undoItem = new JMenuItem(I18nManager.getText("menu.track.undo"));
                setShortcut(_undoItem, "shortcut.menu.track.undo");
                _undoAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.beginUndo();
                        }
                };
@@ -230,22 +228,19 @@ public class MenuManager implements DataSubscriber
                trackMenu.add(_undoItem);
                _clearUndoItem = new JMenuItem(I18nManager.getText("menu.track.clearundo"));
                _clearUndoItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.clearUndo();
                        }
                });
                _clearUndoItem.setEnabled(false);
                trackMenu.add(_clearUndoItem);
                trackMenu.addSeparator();
-               _compressItem = makeMenuItem(FunctionLibrary.FUNCTION_COMPRESS);
+               _compressItem = makeMenuItem(FunctionLibrary.FUNCTION_COMPRESS, false);
                setShortcut(_compressItem, "shortcut.menu.edit.compress");
-               _compressItem.setEnabled(false);
                trackMenu.add(_compressItem);
                _deleteMarkedPointsItem = new JMenuItem(I18nManager.getText("menu.track.deletemarked"));
                _deleteMarkedPointsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.finishCompressTrack();
                        }
                });
@@ -257,8 +252,7 @@ public class MenuManager implements DataSubscriber
                _rearrangeMenu.setEnabled(false);
                JMenuItem  rearrangeStartItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.start"));
                rearrangeStartItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_START);
                        }
                });
@@ -266,8 +260,7 @@ public class MenuManager implements DataSubscriber
                _rearrangeMenu.add(rearrangeStartItem);
                JMenuItem rearrangeEndItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.end"));
                rearrangeEndItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_END);
                        }
                });
@@ -275,8 +268,7 @@ public class MenuManager implements DataSubscriber
                _rearrangeMenu.add(rearrangeEndItem);
                JMenuItem rearrangeNearestItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.nearest"));
                rearrangeNearestItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_NEAREST);
                        }
                });
@@ -284,16 +276,19 @@ public class MenuManager implements DataSubscriber
                _rearrangeMenu.add(rearrangeNearestItem);
                trackMenu.add(_rearrangeMenu);
                // Get gpsies tracks
-               _getGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_GPSIES);
-               _getGpsiesItem.setEnabled(false);
+               _getGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_GPSIES, false);
                trackMenu.add(_getGpsiesItem);
                // Upload to gpsies
-               _uploadGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_UPLOAD_GPSIES);
-               _uploadGpsiesItem.setEnabled(false);
+               _uploadGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_UPLOAD_GPSIES, false);
                trackMenu.add(_uploadGpsiesItem);
-               _lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM);
-               _lookupSrtmItem.setEnabled(false);
+               _lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM, false);
                trackMenu.add(_lookupSrtmItem);
+               _lookupWikipediaItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_WIKIPEDIA, false);
+               trackMenu.add(_lookupWikipediaItem);
+               JMenuItem searchWikipediaNamesItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_WIKIPEDIA);
+               trackMenu.add(searchWikipediaNamesItem);
+               _downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
+               trackMenu.add(_downloadOsmItem);
                menubar.add(trackMenu);
 
                // Range menu
@@ -303,8 +298,7 @@ public class MenuManager implements DataSubscriber
                setShortcut(_selectAllItem, "shortcut.menu.range.all");
                _selectAllItem.setEnabled(false);
                _selectAllItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _selection.selectRange(0, _track.getNumPoints()-1);
                        }
                });
@@ -312,8 +306,7 @@ public class MenuManager implements DataSubscriber
                _selectNoneItem = new JMenuItem(I18nManager.getText("menu.range.none"));
                _selectNoneItem.setEnabled(false);
                _selectNoneItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.selectNone();
                        }
                });
@@ -322,8 +315,7 @@ public class MenuManager implements DataSubscriber
                _selectStartItem = new JMenuItem(I18nManager.getText("menu.range.start"));
                _selectStartItem.setEnabled(false);
                _selectStartAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _selection.selectRangeStart();
                        }
                };
@@ -332,8 +324,7 @@ public class MenuManager implements DataSubscriber
                _selectEndItem = new JMenuItem(I18nManager.getText("menu.range.end"));
                _selectEndItem.setEnabled(false);
                _selectEndAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _selection.selectRangeEnd();
                        }
                };
@@ -342,8 +333,7 @@ public class MenuManager implements DataSubscriber
                rangeMenu.addSeparator();
                _deleteRangeItem = new JMenuItem(I18nManager.getText("menu.range.deleterange"));
                _deleteRangeAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.deleteSelectedRange();
                        }
                };
@@ -352,36 +342,30 @@ public class MenuManager implements DataSubscriber
                rangeMenu.add(_deleteRangeItem);
                _reverseItem = new JMenuItem(I18nManager.getText("menu.range.reverse"));
                _reverseItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.reverseRange();
                        }
                });
                _reverseItem.setEnabled(false);
                rangeMenu.add(_reverseItem);
-               _addTimeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_TIME_OFFSET);
-               _addTimeOffsetItem.setEnabled(false);
+               _addTimeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_TIME_OFFSET, false);
                rangeMenu.add(_addTimeOffsetItem);
-               _addAltitudeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_ALTITUDE_OFFSET);
-               _addAltitudeOffsetItem.setEnabled(false);
+               _addAltitudeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_ALTITUDE_OFFSET, false);
                rangeMenu.add(_addAltitudeOffsetItem);
                _mergeSegmentsItem = new JMenuItem(I18nManager.getText("menu.range.mergetracksegments"));
                _mergeSegmentsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.mergeTrackSegments();
                        }
                });
                _mergeSegmentsItem.setEnabled(false);
                rangeMenu.add(_mergeSegmentsItem);
-               _deleteFieldValuesItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_FIELD_VALUES);
-               _deleteFieldValuesItem.setEnabled(false);
+               _deleteFieldValuesItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_FIELD_VALUES, false);
                rangeMenu.add(_deleteFieldValuesItem);
                rangeMenu.addSeparator();
                _interpolateItem = new JMenuItem(I18nManager.getText("menu.range.interpolate"));
                _interpolateItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.interpolateSelection();
                        }
                });
@@ -389,8 +373,7 @@ public class MenuManager implements DataSubscriber
                rangeMenu.add(_interpolateItem);
                _averageItem = new JMenuItem(I18nManager.getText("menu.range.average"));
                _averageItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.averageSelection();
                        }
                });
@@ -405,8 +388,7 @@ public class MenuManager implements DataSubscriber
                });
                _cutAndMoveItem.setEnabled(false);
                rangeMenu.add(_cutAndMoveItem);
-               _convertNamesToTimesItem = makeMenuItem(FunctionLibrary.FUNCTION_CONVERT_NAMES_TO_TIMES);
-               _convertNamesToTimesItem.setEnabled(false);
+               _convertNamesToTimesItem = makeMenuItem(FunctionLibrary.FUNCTION_CONVERT_NAMES_TO_TIMES, false);
                rangeMenu.add(_convertNamesToTimesItem);
                menubar.add(rangeMenu);
 
@@ -415,21 +397,18 @@ public class MenuManager implements DataSubscriber
                setAltKey(pointMenu, "altkey.menu.point");
                _editPointItem = new JMenuItem(I18nManager.getText("menu.point.editpoint"));
                _editPointAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.editCurrentPoint();
                        }
                };
                _editPointItem.addActionListener(_editPointAction);
                _editPointItem.setEnabled(false);
                pointMenu.add(_editPointItem);
-               _editWaypointNameItem = makeMenuItem(FunctionLibrary.FUNCTION_EDIT_WAYPOINT_NAME);
-               _editWaypointNameItem.setEnabled(false);
+               _editWaypointNameItem = makeMenuItem(FunctionLibrary.FUNCTION_EDIT_WAYPOINT_NAME, false);
                pointMenu.add(_editWaypointNameItem);
                _deletePointItem = new JMenuItem(I18nManager.getText("menu.point.deletepoint"));
                _deletePointAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.deleteCurrentPoint();
                        }
                };
@@ -439,12 +418,10 @@ public class MenuManager implements DataSubscriber
                pointMenu.add(_deletePointItem);
                pointMenu.addSeparator();
                // find a waypoint
-               _findWaypointItem = makeMenuItem(FunctionLibrary.FUNCTION_FIND_WAYPOINT);
-               _findWaypointItem.setEnabled(false);
+               _findWaypointItem = makeMenuItem(FunctionLibrary.FUNCTION_FIND_WAYPOINT, false);
                pointMenu.add(_findWaypointItem);
                // duplicate current point
-               _duplicatePointItem = makeMenuItem(FunctionLibrary.FUNCTION_DUPLICATE_POINT);
-               _duplicatePointItem.setEnabled(false);
+               _duplicatePointItem = makeMenuItem(FunctionLibrary.FUNCTION_DUPLICATE_POINT, false);
                pointMenu.add(_duplicatePointItem);
                // paste coordinates function
                JMenuItem pasteCoordsItem = makeMenuItem(FunctionLibrary.FUNCTION_PASTE_COORDINATES);
@@ -474,64 +451,55 @@ public class MenuManager implements DataSubscriber
                });
                viewMenu.add(sidebarsCheckbox);
                // 3d
-               _show3dItem = makeMenuItem(FunctionLibrary.FUNCTION_3D);
-               _show3dItem.setEnabled(false);
+               _show3dItem = makeMenuItem(FunctionLibrary.FUNCTION_3D, false);
                viewMenu.add(_show3dItem);
                // browser submenu
                _browserMapMenu = new JMenu(I18nManager.getText("menu.view.browser"));
                _browserMapMenu.setEnabled(false);
                JMenuItem googleMapsItem = new JMenuItem(I18nManager.getText("menu.view.browser.google"));
                googleMapsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.showExternalMap(UrlGenerator.MAP_SOURCE_GOOGLE);
                        }
                });
                _browserMapMenu.add(googleMapsItem);
                JMenuItem openMapsItem = new JMenuItem(I18nManager.getText("menu.view.browser.openstreetmap"));
                openMapsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.showExternalMap(UrlGenerator.MAP_SOURCE_OSM);
                        }
                });
                _browserMapMenu.add(openMapsItem);
                JMenuItem mapquestMapsItem = new JMenuItem(I18nManager.getText("menu.view.browser.mapquest"));
                mapquestMapsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.showExternalMap(UrlGenerator.MAP_SOURCE_MAPQUEST);
                        }
                });
                _browserMapMenu.add(mapquestMapsItem);
                JMenuItem yahooMapsItem = new JMenuItem(I18nManager.getText("menu.view.browser.yahoo"));
                yahooMapsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.showExternalMap(UrlGenerator.MAP_SOURCE_YAHOO);
                        }
                });
                _browserMapMenu.add(yahooMapsItem);
                JMenuItem bingMapsItem = new JMenuItem(I18nManager.getText("menu.view.browser.bing"));
                bingMapsItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.showExternalMap(UrlGenerator.MAP_SOURCE_BING);
                        }
                });
                _browserMapMenu.add(bingMapsItem);
                viewMenu.add(_browserMapMenu);
                // Charts
-               _chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS);
-               _chartItem.setEnabled(false);
+               _chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS, false);
                viewMenu.add(_chartItem);
                // Distances
-               _distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES);
-               _distanceItem.setEnabled(false);
+               _distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES, false);
                viewMenu.add(_distanceItem);
                // full range details
-               _fullRangeDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_RANGE_DETAILS);
-               _fullRangeDetailsItem.setEnabled(false);
+               _fullRangeDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_RANGE_DETAILS, false);
                viewMenu.add(_fullRangeDetailsItem);
                menubar.add(viewMenu);
 
@@ -543,73 +511,77 @@ public class MenuManager implements DataSubscriber
                photoMenu.add(addPhotosMenuItem);
                _saveExifItem = new JMenuItem(I18nManager.getText("menu.photo.saveexif"));
                _saveExifItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _app.saveExif();
                        }
                });
                _saveExifItem.setEnabled(false);
                photoMenu.add(_saveExifItem);
-               _connectPhotoItem = new JMenuItem(I18nManager.getText("menu.photo.connect"));
-               _connectPhotoAction = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _app.connectPhotoToPoint();
+               // Deselect current photo
+               _selectNoPhotoItem = new JMenuItem(I18nManager.getText("menu.range.none"));
+               _selectNoPhotoItem.setEnabled(false);
+               _selectNoPhotoItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _app.getTrackInfo().selectPhoto(-1);
                        }
-               };
-               _connectPhotoItem.addActionListener(_connectPhotoAction);
-               _connectPhotoItem.setEnabled(false);
+               });
+               photoMenu.add(_selectNoPhotoItem);
                photoMenu.addSeparator();
+               _connectPhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_CONNECT_TO_POINT, false);
                photoMenu.add(_connectPhotoItem);
                // disconnect photo
-               _disconnectPhotoItem = new JMenuItem(I18nManager.getText("menu.photo.disconnect"));
-               _disconnectPhotoItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _app.disconnectPhotoFromPoint();
-                       }
-               });
-               _disconnectPhotoItem.setEnabled(false);
+               _disconnectPhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_DISCONNECT_PHOTO, false);
                photoMenu.add(_disconnectPhotoItem);
-               _deletePhotoItem = new JMenuItem(I18nManager.getText("menu.photo.delete"));
-               _deletePhotoItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _app.deleteCurrentPhoto();
-                       }
-               });
-               _deletePhotoItem.setEnabled(false);
-               photoMenu.add(_deletePhotoItem);
+               _removePhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_REMOVE_PHOTO, false);
+               photoMenu.add(_removePhotoItem);
                // Rotate current photo
-               _rotatePhotoLeft = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_LEFT);
-               _rotatePhotoLeft.setEnabled(false);
+               _rotatePhotoLeft = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_LEFT, false);
                photoMenu.add(_rotatePhotoLeft);
-               _rotatePhotoRight = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_RIGHT);
-               _rotatePhotoRight.setEnabled(false);
+               _rotatePhotoRight = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_RIGHT, false);
                photoMenu.add(_rotatePhotoRight);
-               _ignoreExifThumb = makeMenuItem(FunctionLibrary.FUNCTION_IGNORE_EXIF_THUMB);
-               _ignoreExifThumb.setEnabled(false);
+               // Show photo popup
+               _photoPopupItem = makeMenuItem(FunctionLibrary.FUNCTION_PHOTO_POPUP, false);
+               photoMenu.add(_photoPopupItem);
+               _ignoreExifThumb = makeMenuItem(FunctionLibrary.FUNCTION_IGNORE_EXIF_THUMB, false);
                photoMenu.add(_ignoreExifThumb);
-               _selectNoPhotoItem = new JMenuItem(I18nManager.getText("menu.range.none"));
-               _selectNoPhotoItem.setEnabled(false);
-               _selectNoPhotoItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _app.getTrackInfo().selectPhoto(-1);
-                       }
-               });
-               photoMenu.add(_selectNoPhotoItem);
                photoMenu.addSeparator();
                // correlate all photos
-               _correlatePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_CORRELATE_PHOTOS);
-               _correlatePhotosItem.setEnabled(false);
+               _correlatePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_CORRELATE_PHOTOS, false);
                photoMenu.add(_correlatePhotosItem);
                // rearrange photo points
-               _rearrangePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_REARRANGE_PHOTOS);
-               _rearrangePhotosItem.setEnabled(false);
+               _rearrangePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_REARRANGE_PHOTOS, false);
                photoMenu.add(_rearrangePhotosItem);
                menubar.add(photoMenu);
 
+               // Audio menu
+               JMenu audioMenu = new JMenu(I18nManager.getText("menu.audio"));
+               setAltKey(audioMenu, "altkey.menu.audio");
+               addAudioMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_LOAD_AUDIO);
+               audioMenu.add(addAudioMenuItem);
+               _selectNoAudioItem = new JMenuItem(I18nManager.getText("menu.range.none"));
+               _selectNoAudioItem.setEnabled(false);
+               _selectNoAudioItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _app.getTrackInfo().selectAudio(-1);
+                       }
+               });
+               audioMenu.add(_selectNoAudioItem);
+               audioMenu.addSeparator();
+               // connect audio
+               _connectAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_CONNECT_TO_POINT, false);
+               audioMenu.add(_connectAudioItem);
+               // Disconnect current audio file
+               _disconnectAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_DISCONNECT_AUDIO, false);
+               audioMenu.add(_disconnectAudioItem);
+               // Remove current audio file
+               _removeAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_REMOVE_AUDIO, false);
+               audioMenu.add(_removeAudioItem);
+               audioMenu.addSeparator();
+               // Correlate audio files
+               _correlateAudiosItem = makeMenuItem(FunctionLibrary.FUNCTION_CORRELATE_AUDIOS, false);
+               audioMenu.add(_correlateAudiosItem);
+               menubar.add(audioMenu);
+
                // Settings menu
                JMenu settingsMenu = new JMenu(I18nManager.getText("menu.settings"));
                setAltKey(settingsMenu, "altkey.menu.settings");
@@ -634,6 +606,8 @@ public class MenuManager implements DataSubscriber
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
                // Set colours
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_COLOURS));
+               // Set line width used for drawing
+               settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LINE_WIDTH));
                // Set language
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LANGUAGE));
                settingsMenu.addSeparator();
@@ -655,6 +629,19 @@ public class MenuManager implements DataSubscriber
                return menubar;
        }
 
+       /**
+        * Convenience method for making a menu item using a function
+        * @param inFunction function
+        * @param inEnabled flag to specify initial enabled state
+        * @return menu item using localized name of function
+        */
+       private static JMenuItem makeMenuItem(GenericFunction inFunction, boolean inEnabled)
+       {
+               JMenuItem item = makeMenuItem(inFunction);
+               item.setEnabled(inEnabled);
+               return item;
+       }
+
        /**
         * Convenience method for making a menu item using a function
         * @param inFunction function
@@ -767,11 +754,16 @@ public class MenuManager implements DataSubscriber
                _selectEndButton.addActionListener(_selectEndAction);
                _selectEndButton.setEnabled(false);
                toolbar.add(_selectEndButton);
-               _connectPhotoButton = new JButton(IconManager.getImageIcon(IconManager.CONNECT_PHOTO));
-               _connectPhotoButton.setToolTipText(I18nManager.getText("menu.photo.connect"));
-               _connectPhotoButton.addActionListener(_connectPhotoAction);
-               _connectPhotoButton.setEnabled(false);
-               toolbar.add(_connectPhotoButton);
+               // Connect to point
+               _connectButton = new JButton(IconManager.getImageIcon(IconManager.CONNECT_PHOTO));
+               _connectButton.setToolTipText(I18nManager.getText(FunctionLibrary.FUNCTION_CONNECT_TO_POINT.getNameKey()));
+               _connectButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               FunctionLibrary.FUNCTION_CONNECT_TO_POINT.begin();
+                       }
+               });
+               _connectButton.setEnabled(false);
+               toolbar.add(_connectButton);
                // finish off
                toolbar.setFloatable(false);
                return toolbar;
@@ -818,6 +810,8 @@ public class MenuManager implements DataSubscriber
                _getGpsiesItem.setEnabled(hasData);
                _uploadGpsiesItem.setEnabled(hasData && _track.hasTrackPoints());
                _lookupSrtmItem.setEnabled(hasData);
+               _lookupWikipediaItem.setEnabled(hasData);
+               _downloadOsmItem.setEnabled(hasData);
                _findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
                // is undo available?
                boolean hasUndo = !_app.getUndoStack().isEmpty();
@@ -837,24 +831,33 @@ public class MenuManager implements DataSubscriber
                _selectEndButton.setEnabled(hasPoint);
                _duplicatePointItem.setEnabled(hasPoint);
                // are there any photos?
-               boolean anyPhotos = _photos != null && _photos.getNumPhotos() > 0;
+               boolean anyPhotos = _app.getTrackInfo().getPhotoList().getNumPhotos() > 0;
                _saveExifItem.setEnabled(anyPhotos);
-               // is there a current photo?
-               boolean hasPhoto = anyPhotos && _selection.getCurrentPhotoIndex() >= 0;
-               // connect is available if photo and point selected, and photo has no point
-               Photo currentPhoto = _photos.getPhoto(_selection.getCurrentPhotoIndex());
-               boolean connectAvailable = hasPhoto && hasPoint && currentPhoto != null
-                       && currentPhoto.getDataPoint() == null;
-               _connectPhotoItem.setEnabled(connectAvailable);
-               _connectPhotoButton.setEnabled(connectAvailable);
-               _disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto != null && currentPhoto.getDataPoint() != null);
+               // is there a current photo, audio?
+               Photo currentPhoto = _app.getTrackInfo().getCurrentPhoto();
+               boolean hasPhoto = currentPhoto != null;
+               AudioFile currentAudio = _app.getTrackInfo().getCurrentAudio();
+               boolean hasAudio = currentAudio != null;
+               // connect is available if (photo/audio) and point selected, and media has no point
+               boolean connectAvailable = (hasPhoto && hasPoint && currentPhoto.getDataPoint() == null)
+                       || (hasAudio && hasPoint && currentAudio.getDataPoint() == null);
+               _connectPhotoItem.setEnabled(hasPhoto && hasPoint && currentPhoto.getDataPoint() == null);
+               _connectButton.setEnabled(connectAvailable);
+               _disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto.getDataPoint() != null);
                _correlatePhotosItem.setEnabled(anyPhotos && hasData);
                _rearrangePhotosItem.setEnabled(anyPhotos && hasData && _track.getNumPoints() > 1);
-               _deletePhotoItem.setEnabled(hasPhoto);
+               _removePhotoItem.setEnabled(hasPhoto);
                _rotatePhotoLeft.setEnabled(hasPhoto);
                _rotatePhotoRight.setEnabled(hasPhoto);
-               _ignoreExifThumb.setEnabled(hasPhoto && currentPhoto != null && currentPhoto.getExifThumbnail() != null);
+               _photoPopupItem.setEnabled(hasPhoto);
+               _ignoreExifThumb.setEnabled(hasPhoto && currentPhoto.getExifThumbnail() != null);
                _selectNoPhotoItem.setEnabled(hasPhoto);
+               boolean anyAudios = _app.getTrackInfo().getAudioList().getNumAudios() > 0;
+               _selectNoAudioItem.setEnabled(hasAudio);
+               _removeAudioItem.setEnabled(hasAudio);
+               _connectAudioItem.setEnabled(hasAudio && hasPoint && currentAudio.getDataPoint() == null);
+               _disconnectAudioItem.setEnabled(hasAudio && _app.getTrackInfo().getCurrentAudio().getDataPoint() != null);
+               _correlateAudiosItem.setEnabled(anyAudios && hasData);
                // is there a current range?
                boolean hasRange = (hasData && _selection.hasRangeSelected());
                _deleteRangeItem.setEnabled(hasRange);
diff --git a/tim/prune/gui/PhotoListModel.java b/tim/prune/gui/PhotoListModel.java
deleted file mode 100644 (file)
index 27ed0e6..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-package tim.prune.gui;
-
-import javax.swing.AbstractListModel;
-
-import tim.prune.data.Photo;
-import tim.prune.data.PhotoList;
-
-/**
- * Class to act as list model for the photo list
- */
-public class PhotoListModel extends AbstractListModel
-{
-       PhotoList _photos = null;
-
-       /**
-        * Constructor giving PhotoList object
-        * @param inList PhotoList
-        */
-       public PhotoListModel(PhotoList inList)
-       {
-               _photos = inList;
-       }
-
-       /**
-        * @see javax.swing.ListModel#getSize()
-        */
-       public int getSize()
-       {
-               return _photos.getNumPhotos();
-       }
-
-       /**
-        * @see javax.swing.ListModel#getElementAt(int)
-        */
-       public Object getElementAt(int inIndex)
-       {
-               return _photos.getPhoto(inIndex).getFile().getName();
-       }
-
-       /**
-        * Get the Photo at the given index
-        * @param inIndex index number, starting at 0
-        * @return Photo object
-        */
-       public Photo getPhoto(int inIndex)
-       {
-               return _photos.getPhoto(inIndex);
-       }
-
-       /**
-        * Fire event to notify that contents have changed
-        */
-       public void fireChanged()
-       {
-               this.fireContentsChanged(this, 0, getSize()-1);
-       }
-}
index cc7e776e224aa4bb93a3bb3831faac5c42c457c3..686d90a1ac1eff972a0b20380e40e2cf03c05ea9 100644 (file)
@@ -4,7 +4,6 @@ import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Image;
-import java.awt.image.BufferedImage;
 import javax.swing.ImageIcon;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
@@ -18,9 +17,10 @@ import tim.prune.data.Photo;
 public class PhotoThumbnail extends JPanel implements Runnable
 {
        private Photo _photo = null;
-       private BufferedImage _thumbnail = null;
+       private Image _thumbnail = null;
        private boolean _loadingImage = false;
        private boolean _loadFailed = false;
+       private boolean _inPanel = false;
        /** String to show before photo is loaded */
        private static final String LOADING_STRING = I18nManager.getText("details.photo.loading") + " ...";
 
@@ -30,9 +30,18 @@ public class PhotoThumbnail extends JPanel implements Runnable
         */
        public PhotoThumbnail()
        {
-               setOpaque(true);
+               this(true);
        }
 
+       /**
+        * Constructor
+        * @param inPanel true if thumbnail is inside panel
+        */
+       public PhotoThumbnail(boolean inPanel)
+       {
+               setOpaque(true);
+               _inPanel = inPanel;
+       }
 
        /**
         * Set the Photo
@@ -41,7 +50,8 @@ public class PhotoThumbnail extends JPanel implements Runnable
        public void setPhoto(Photo inPhoto)
        {
                // Check whether the photo has changed
-               if (_photo != inPhoto) {
+               if (_photo != inPhoto)
+               {
                        _photo = inPhoto;
                        _thumbnail = null;
                        _loadFailed = false;
@@ -52,7 +62,8 @@ public class PhotoThumbnail extends JPanel implements Runnable
        /**
         * Force a refresh / reload
         */
-       public void refresh() {
+       public void refresh()
+       {
                _thumbnail = null;
                _loadFailed = false;
        }
@@ -82,14 +93,16 @@ public class PhotoThumbnail extends JPanel implements Runnable
                        {
                                // Copy scaled, smoothed (and rotated) image into scaled
                                int usableWidth = getParent().getWidth()-10;
-                               Image scaled = ImageUtils.rotateImage(_thumbnail, usableWidth, usableWidth, _photo.getRotationDegrees());
+                               int usableHeight = (_inPanel?usableWidth:getHeight()-10);
+                               Image scaled = ImageUtils.rotateImage(_thumbnail, usableWidth, usableHeight, _photo.getRotationDegrees());
                                int scaleWidth = scaled.getWidth(null);
                                int scaleHeight = scaled.getHeight(null);
                                // Draw scaled / rotated image to component
                                int horizOffset = (getWidth() - scaleWidth) / 2;
                                int vertOffset = (getHeight() - scaleHeight) / 2;
                                inG.drawImage(scaled, horizOffset, vertOffset, scaleWidth, scaleHeight, null);
-                               if (getHeight() < getWidth() || getHeight() > usableWidth)
+                               // Special resize behaviour when locked inside details panel
+                               if (_inPanel && (getHeight() < getWidth() || getHeight() > usableWidth))
                                {
                                        Dimension newsize = new Dimension(usableWidth, usableWidth);
                                        setPreferredSize(newsize);
@@ -114,30 +127,37 @@ public class PhotoThumbnail extends JPanel implements Runnable
         */
        public void run()
        {
-               // Use exif thumbnail?
-               if (_photo.getExifThumbnail() != null) {
-                       Image image = new ImageIcon(_photo.getExifThumbnail()).getImage();
-                       _thumbnail = ImageUtils.createScaledImage(image, image.getWidth(null), image.getHeight(null));
-                       image = null;
-               }
-               else
+               if (_inPanel)
                {
-                       // no exif thumbnail available, going to have to read whole thing
-                       int picWidth = _photo.getWidth();
-                       int picHeight = _photo.getHeight();
-                       if (picWidth > -1 && picHeight > -1)
-                       {
-                               // Just set a "reasonable" thumbnail size for now
-                               final int DEFAULT_THUMB_SIZE = 400;
-                               // calculate maximum thumbnail size
-                               Dimension thumbSize = ImageUtils.getThumbnailSize(picWidth, picHeight, DEFAULT_THUMB_SIZE, DEFAULT_THUMB_SIZE);
-                               // Make icon to load image into
-                               Image image = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage();
-                               // save scaled, smoothed thumbnail for reuse
-                               _thumbnail = ImageUtils.createScaledImage(image, thumbSize.width, thumbSize.height);
+                       // use either exif thumbnail or photo scaled down to sensible size
+                       if (_photo.getExifThumbnail() != null) {
+                               // Use exif thumbnail
+                               Image image = new ImageIcon(_photo.getExifThumbnail()).getImage();
+                               _thumbnail = ImageUtils.createScaledImage(image, image.getWidth(null), image.getHeight(null));
                                image = null;
                        }
-                       else _loadFailed = true;
+                       else
+                       {
+                               // no exif thumbnail available, going to have to read whole thing
+                               int picWidth = _photo.getWidth();
+                               int picHeight = _photo.getHeight();
+                               if (picWidth > -1 && picHeight > -1)
+                               {
+                                       // Just set a "reasonable" thumbnail size for now
+                                       final int DEFAULT_THUMB_SIZE = 400;
+                                       // calculate maximum thumbnail size
+                                       Dimension thumbSize = ImageUtils.getThumbnailSize(picWidth, picHeight, DEFAULT_THUMB_SIZE, DEFAULT_THUMB_SIZE);
+                                       // Make icon to load image into
+                                       Image image = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage();
+                                       // save scaled, smoothed thumbnail for reuse
+                                       _thumbnail = ImageUtils.createScaledImage(image, thumbSize.width, thumbSize.height);
+                                       image = null;
+                               }
+                               else _loadFailed = true;
+                       }
+               }
+               else {
+                       _thumbnail = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage();
                }
                _loadingImage = false;
                repaint();
index ed244d0bdbff57816492f5d23d7a6407e4151ce4..f0772bf2695a3aed6ab7a803623dbe180c2fbe3a 100644 (file)
@@ -38,12 +38,21 @@ public class SelectorDisplay extends GenericDisplay
        private JScrollBar _scroller = null;
        private boolean _ignoreScrollEvents = false;
 
-       // Photos
-       private JList _photoList = null;
-       private PhotoListModel _photoListModel = null;
+       // Panel containing lists
+       private JPanel _listsPanel = null;
+       private int _visiblePanels = 1;
        // Waypoints
+       private JPanel _waypointListPanel = null;
        private JList _waypointList = null;
        private WaypointListModel _waypointListModel = null;
+       // Photos
+       private JPanel _photoListPanel = null;
+       private JList _photoList = null;
+       private MediaListModel _photoListModel = null;
+       // Audio files
+       private JPanel _audioListPanel = null;
+       private JList _audioList = null;
+       private MediaListModel _audioListModel = null;
 
        // scrollbar interval
        private static final int SCROLLBAR_INTERVAL = 50;
@@ -78,43 +87,39 @@ public class SelectorDisplay extends GenericDisplay
                _trackpointsLabel = new JLabel(I18nManager.getText("details.notrack"));
                trackDetailsPanel.add(_trackpointsLabel);
                _filenameLabel = new JLabel("");
+               _filenameLabel.setMinimumSize(new Dimension(120, 10));
                trackDetailsPanel.add(_filenameLabel);
 
                // Scroll bar
                _scroller = new JScrollBar(JScrollBar.HORIZONTAL, 0, SCROLLBAR_INTERVAL, 0, 100);
                _scroller.addAdjustmentListener(new AdjustmentListener() {
-                       public void adjustmentValueChanged(AdjustmentEvent e)
-                       {
+                       public void adjustmentValueChanged(AdjustmentEvent e) {
                                selectPoint(e.getValue());
                        }
                });
                _scroller.setEnabled(false);
 
                // Add panel for waypoints / photos
-               JPanel listsPanel = new JPanel();
-               listsPanel.setLayout(new GridLayout(0, 1));
-               listsPanel.setBorder(BorderFactory.createCompoundBorder(
+               _listsPanel = new JPanel();
+               _listsPanel.setLayout(new GridLayout(0, 1));
+               _listsPanel.setBorder(BorderFactory.createCompoundBorder(
                        BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
                );
                _waypointListModel = new WaypointListModel(_trackInfo.getTrack());
                _waypointList = new JList(_waypointListModel);
                _waypointList.setVisibleRowCount(NUM_LIST_ENTRIES);
-               _waypointList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                _waypointList.addListSelectionListener(new ListSelectionListener() {
                        public void valueChanged(ListSelectionEvent e)
                        {
                                if (!e.getValueIsAdjusting()) selectWaypoint(_waypointList.getSelectedIndex());
-                       }});
-               JPanel waypointListPanel = new JPanel();
-               waypointListPanel.setLayout(new BorderLayout());
-               waypointListPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.waypoints")), BorderLayout.NORTH);
-               waypointListPanel.add(new JScrollPane(_waypointList), BorderLayout.CENTER);
-               listsPanel.add(waypointListPanel);
+                       }
+               });
+               _waypointListPanel = makeListPanel("details.lists.waypoints", _waypointList);
+               _listsPanel.add(_waypointListPanel);
                // photo list
-               _photoListModel = new PhotoListModel(_trackInfo.getPhotoList());
+               _photoListModel = new MediaListModel(_trackInfo.getPhotoList());
                _photoList = new JList(_photoListModel);
                _photoList.setVisibleRowCount(NUM_LIST_ENTRIES);
-               _photoList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                _photoList.addListSelectionListener(new ListSelectionListener() {
                        public void valueChanged(ListSelectionEvent e)
                        {
@@ -122,12 +127,22 @@ public class SelectorDisplay extends GenericDisplay
                                        selectPhoto(_photoList.getSelectedIndex());
                                }
                        }});
-               JPanel photoListPanel = new JPanel();
-               photoListPanel.setLayout(new BorderLayout());
-               photoListPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.photos")), BorderLayout.NORTH);
-               photoListPanel.add(new JScrollPane(_photoList), BorderLayout.CENTER);
-               listsPanel.add(photoListPanel);
-               listsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               _photoListPanel = makeListPanel("details.lists.photos", _photoList);
+               // don't add photo list (because there aren't any photos yet)
+
+               // List for audio files
+               _audioListModel = new MediaListModel(_trackInfo.getAudioList());
+               _audioList = new JList(_audioListModel);
+               _audioList.addListSelectionListener(new ListSelectionListener() {
+                       public void valueChanged(ListSelectionEvent e)
+                       {
+                               if (!e.getValueIsAdjusting()) {
+                                       selectAudio(_audioList.getSelectedIndex());
+                               }
+                       }});
+               _audioListPanel = makeListPanel("details.lists.audio", _audioList);
+               // don't add audio list either
+               _listsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
 
                // add the controls to the main panel
                mainPanel.add(trackDetailsPanel);
@@ -138,7 +153,7 @@ public class SelectorDisplay extends GenericDisplay
                // add the main panel at the top
                add(mainPanel, BorderLayout.NORTH);
                // and lists in the centre
-               add(listsPanel, BorderLayout.CENTER);
+               add(_listsPanel, BorderLayout.CENTER);
                // set preferred width to be small
                setPreferredSize(new Dimension(100, 100));
        }
@@ -150,8 +165,7 @@ public class SelectorDisplay extends GenericDisplay
         */
        private void selectPoint(int inValue)
        {
-               if (_track != null && !_ignoreScrollEvents)
-               {
+               if (_track != null && !_ignoreScrollEvents) {
                        _trackInfo.selectPoint(inValue);
                }
        }
@@ -166,6 +180,14 @@ public class SelectorDisplay extends GenericDisplay
                _trackInfo.selectPhoto(inPhotoIndex);
        }
 
+       /**
+        * Select the specified audio file
+        * @param inIndex index of selected audio file
+        */
+       private void selectAudio(int inIndex)
+       {
+               _trackInfo.selectAudio(inIndex);
+       }
 
        /**
         * Select the specified waypoint
@@ -173,8 +195,7 @@ public class SelectorDisplay extends GenericDisplay
         */
        private void selectWaypoint(int inWaypointIndex)
        {
-               if (inWaypointIndex >= 0)
-               {
+               if (inWaypointIndex >= 0) {
                        _trackInfo.selectPoint(_waypointListModel.getWaypoint(inWaypointIndex));
                }
        }
@@ -203,8 +224,7 @@ public class SelectorDisplay extends GenericDisplay
                        }
                        else if (numFiles > 1)
                        {
-                               _filenameLabel.setText(I18nManager.getText("details.track.numfiles") + ": "
-                                       + numFiles);
+                               _filenameLabel.setText(I18nManager.getText("details.track.numfiles") + ": " + numFiles);
                        }
                        else _filenameLabel.setText("");
                }
@@ -237,6 +257,7 @@ public class SelectorDisplay extends GenericDisplay
                        (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.PHOTOS_MODIFIED)) > 0)
                {
                        _photoListModel.fireChanged();
+                       _audioListModel.fireChanged();
                }
                // Deselect selected waypoint if selected point has since changed
                if (_waypointList.getSelectedIndex() >= 0)
@@ -249,6 +270,9 @@ public class SelectorDisplay extends GenericDisplay
                                _waypointList.clearSelection();
                        }
                }
+               // Hide photo list if no photos loaded, same for audio
+               redrawLists(_photoListModel.getSize() > 0, _audioListModel.getSize() > 0);
+
                // Make sure correct photo is selected
                if (_photoListModel.getSize() > 0)
                {
@@ -265,5 +289,62 @@ public class SelectorDisplay extends GenericDisplay
                                }
                        }
                }
+               // Same for audio files
+               if (_audioListModel.getSize() > 0)
+               {
+                       int audioIndex = _trackInfo.getSelection().getCurrentAudioIndex();
+                       int listSelection = _audioList.getSelectedIndex();
+                       // Change listbox selection if indexes not equal
+                       if (listSelection != audioIndex)
+                       {
+                               if (audioIndex < 0) {
+                                       _audioList.clearSelection();
+                               }
+                               else {
+                                       _audioList.setSelectedIndex(audioIndex);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Make one of the three list panels
+        * @param inNameKey key for heading text
+        * @param inList list object
+        * @return panel object
+        */
+       private static JPanel makeListPanel(String inNameKey, JList inList)
+       {
+               JPanel panel = new JPanel();
+               panel.setLayout(new BorderLayout());
+               panel.add(new JLabel(I18nManager.getText(inNameKey)), BorderLayout.NORTH);
+               inList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               panel.add(new JScrollPane(inList), BorderLayout.CENTER);
+               return panel;
+       }
+
+       /**
+        * Redraw the list panels in the display according to which ones should be shown
+        * @param inShowPhotos true to show photo list
+        * @param inShowAudio true to show audio list
+        */
+       private void redrawLists(boolean inShowPhotos, boolean inShowAudio)
+       {
+               // exit if same as last time
+               int panels = 1 + (inShowPhotos?2:0) + (inShowAudio?4:0);
+               if (panels == _visiblePanels) return;
+               _visiblePanels = panels;
+               // remove all panels and re-add them
+               _listsPanel.removeAll();
+               _listsPanel.setLayout(new GridLayout(0, 1));
+               _listsPanel.add(_waypointListPanel);
+               if (inShowPhotos) {
+                       _listsPanel.add(_photoListPanel);
+               }
+               if (inShowAudio) {
+                       _listsPanel.add(_audioListPanel);
+               }
+               _listsPanel.invalidate();
+               _listsPanel.getParent().validate();
        }
 }
diff --git a/tim/prune/gui/images/play_audio.gif b/tim/prune/gui/images/play_audio.gif
new file mode 100644 (file)
index 0000000..8d4d6e5
Binary files /dev/null and b/tim/prune/gui/images/play_audio.gif differ
diff --git a/tim/prune/gui/images/show_details_icon.gif b/tim/prune/gui/images/show_details_icon.gif
new file mode 100644 (file)
index 0000000..97674f2
Binary files /dev/null and b/tim/prune/gui/images/show_details_icon.gif differ
diff --git a/tim/prune/gui/images/stop_audio.gif b/tim/prune/gui/images/stop_audio.gif
new file mode 100644 (file)
index 0000000..e0af8f5
Binary files /dev/null and b/tim/prune/gui/images/stop_audio.gif differ
index dfc4f927aed2aee0baef04623c37831e824a5156..a250e236256a07d54acc7350c626ea39f1dfc4fa 100644 (file)
@@ -120,7 +120,8 @@ public class DiskTileCacher implements Runnable
                        }
                        catch (Exception e) {return;}
                }
-               try {
+               try
+               {
                        // Open streams from URL and to file
                        out = new FileOutputStream(tempFile);
                        in = _url.openStream();
@@ -131,16 +132,18 @@ public class DiskTileCacher implements Runnable
                        }
                        finished = true;
                } catch (IOException e) {}
-               finally {
-                       try {
-                               in.close();
-                               out.close();
-                               if (!finished) {tempFile.delete();}
+               finally
+               {
+                       // clean up files
+                       try {in.close();} catch (Exception e) {} // ignore
+                       try {out.close();} catch (Exception e) {} // ignore
+                       if (!finished) {
+                               tempFile.delete();
                        }
-                       catch (Exception e) {} // ignore
                }
                // Move temp file to desired file location
-               if (!tempFile.renameTo(_file)) {
+               if (!tempFile.renameTo(_file))
+               {
                        // File couldn't be moved - delete both to be sure
                        tempFile.delete();
                        _file.delete();
index 97be7b5024e47124a92b940292052aa09277ec42..2308facb099668175044103480110e81b605574f 100644 (file)
@@ -103,8 +103,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        private int _dragFromX = -1;
        /** y coordinate of drag from point */
        private int _dragFromY = -1;
-       /** Flag set to true for right-click dragging */
-       private boolean _zoomDragging = false;
        /** x coordinate of drag to point */
        private int _dragToX = -1;
        /** y coordinate of drag to point */
@@ -115,6 +113,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        private int _popupMenuY = -1;
        /** Flag to prevent showing too often the error message about loading maps */
        private boolean _shownOsmErrorAlready = false;
+       /** Current drawing mode */
+       private int _drawMode = MODE_DEFAULT;
 
        /** Constant for click sensitivity when selecting nearest point */
        private static final int CLICK_SENSITIVITY = 10;
@@ -126,6 +126,11 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        // Colours
        private static final Color COLOR_MESSAGES   = Color.GRAY;
 
+       // Drawing modes
+       private static final int MODE_DEFAULT = 0;
+       private static final int MODE_ZOOM_RECT = 1;
+       private static final int MODE_DRAW_POINTS_START = 2;
+       private static final int MODE_DRAW_POINTS_CONT = 3;
 
        /**
         * Constructor
@@ -273,7 +278,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        {
                                zoomIn();
                        }});
-               zoomInItem.setEnabled(true);
                _popup.add(zoomInItem);
                JMenuItem zoomOutItem = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
                zoomOutItem.addActionListener(new ActionListener() {
@@ -281,7 +285,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        {
                                zoomOut();
                        }});
-               zoomOutItem.setEnabled(true);
                _popup.add(zoomOutItem);
                JMenuItem zoomFullItem = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
                zoomFullItem.addActionListener(new ActionListener() {
@@ -291,7 +294,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                _recalculate = true;
                                repaint();
                        }});
-               zoomFullItem.setEnabled(true);
                _popup.add(zoomFullItem);
                _popup.addSeparator();
                // Set background
@@ -308,13 +310,18 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                newPointItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               double lat = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight()));
-                               double lon = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth()));
-                               _app.createPoint(new DataPoint(new Latitude(lat, Coordinate.FORMAT_NONE),
-                                       new Longitude(lon, Coordinate.FORMAT_NONE), null));
+                               _app.createPoint(createPointFromClick(_popupMenuX, _popupMenuY));
                        }});
-               newPointItem.setEnabled(true);
                _popup.add(newPointItem);
+               // draw point series
+               JMenuItem drawPointsItem = new JMenuItem(I18nManager.getText("menu.map.drawpoints"));
+               drawPointsItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _drawMode = MODE_DRAW_POINTS_START;
+                       }
+               });
+               _popup.add(drawPointsItem);
        }
 
 
@@ -386,7 +393,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
                        }
                        // Draw the zoom rectangle if necessary
-                       if (_zoomDragging)
+                       if (_drawMode == MODE_ZOOM_RECT)
                        {
                                inG.setColor(Color.RED);
                                inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
@@ -394,6 +401,15 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
                                inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
                        }
+                       else if (_drawMode == MODE_DRAW_POINTS_CONT)
+                       {
+                               // draw line to mouse position to show drawing mode
+                               inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+                               int prevIndex = _track.getNumPoints()-1;
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
+                               inG.drawLine(px, py, _dragToX, _dragToY);
+                       }
                }
                else
                {
@@ -520,9 +536,12 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                final Color secondColour = Config.getColourScheme().getColour(ColourScheme.IDX_SECONDARY);
                final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
 
-               // try to set double line width for painting
-               if (inG instanceof Graphics2D) {
-                       ((Graphics2D) inG).setStroke(new BasicStroke(2.0f));
+               // try to set line width for painting
+               if (inG instanceof Graphics2D)
+               {
+                       int lineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
+                       if (lineWidth < 1 || lineWidth > 4) {lineWidth = 2;}
+                       ((Graphics2D) inG).setStroke(new BasicStroke(lineWidth));
                }
                int pointsPainted = 0;
                // draw track points
@@ -627,11 +646,11 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                }
                        }
                }
-               // Loop over points, drawing blobs for photo points
+               // Loop over points, drawing blobs for photo / audio points
                inG.setColor(secondColour);
                for (int i=0; i<_track.getNumPoints(); i++)
                {
-                       if (_track.getPoint(i).getPhoto() != null)
+                       if (_track.getPoint(i).hasMedia())
                        {
                                int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
                                int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
@@ -774,6 +793,20 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                repaint();
        }
 
+       /**
+        * Create a DataPoint object from the given click coordinates
+        * @param inX x coordinate of click
+        * @param inY y coordinate of click
+        * @return DataPoint with given coordinates and no altitude
+        */
+       private DataPoint createPointFromClick(int inX, int inY)
+       {
+               double lat = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(inY, getHeight()));
+               double lon = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(inX, getWidth()));
+               return new DataPoint(new Latitude(lat, Coordinate.FORMAT_NONE),
+                       new Longitude(lon, Coordinate.FORMAT_NONE), null);
+       }
+
        /**
         * @see javax.swing.JComponent#getMinimumSize()
         */
@@ -806,22 +839,43 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                if (inE.getClickCount() == 1)
                                {
                                        // single click
-                                       int pointIndex = _track.getNearestPointIndex(
-                                                _mapPosition.getXFromPixels(inE.getX(), getWidth()),
-                                                _mapPosition.getYFromPixels(inE.getY(), getHeight()),
-                                                _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
-                                       // Extend selection for shift-click
-                                       if (inE.isShiftDown()) {
-                                               _trackInfo.extendSelection(pointIndex);
+                                       if (_drawMode == MODE_DEFAULT)
+                                       {
+                                               int pointIndex = _track.getNearestPointIndex(
+                                                        _mapPosition.getXFromPixels(inE.getX(), getWidth()),
+                                                        _mapPosition.getYFromPixels(inE.getY(), getHeight()),
+                                                        _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
+                                               // Extend selection for shift-click
+                                               if (inE.isShiftDown()) {
+                                                       _trackInfo.extendSelection(pointIndex);
+                                               }
+                                               else {
+                                                       _trackInfo.selectPoint(pointIndex);
+                                               }
                                        }
-                                       else {
-                                               _trackInfo.selectPoint(pointIndex);
+                                       else if (_drawMode == MODE_DRAW_POINTS_START)
+                                       {
+                                               _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
+                                               _dragToX = inE.getX();
+                                               _dragToY = inE.getY();
+                                               _drawMode = MODE_DRAW_POINTS_CONT;
+                                       }
+                                       else if (_drawMode == MODE_DRAW_POINTS_CONT)
+                                       {
+                                               DataPoint point = createPointFromClick(inE.getX(), inE.getY());
+                                               _app.createPoint(point);
+                                               point.setSegmentStart(false);
                                        }
                                }
                                else if (inE.getClickCount() == 2) {
                                        // double click
-                                       panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
-                                       zoomIn();
+                                       if (_drawMode == MODE_DEFAULT) {
+                                               panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
+                                               zoomIn();
+                                       }
+                                       else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
+                                               _drawMode = MODE_DEFAULT;
+                                       }
                                }
                        }
                        else
@@ -868,13 +922,14 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        public void mouseReleased(MouseEvent inE)
        {
                _recalculate = true;
-               if (_zoomDragging && Math.abs(_dragToX - _dragFromX) > 20 && Math.abs(_dragToY - _dragFromY) > 20)
+               if (_drawMode == MODE_ZOOM_RECT && Math.abs(_dragToX - _dragFromX) > 20
+                       && Math.abs(_dragToY - _dragFromY) > 20)
                {
                        //System.out.println("Finished zoom: " + _dragFromX + ", " + _dragFromY + " to " + _dragToX + ", " + _dragToY);
                        _mapPosition.zoomToPixels(_dragFromX, _dragToX, _dragFromY, _dragToY, getWidth(), getHeight());
+                       _drawMode = MODE_DEFAULT;
                }
                _dragFromX = _dragFromY = -1;
-               _zoomDragging = false;
                repaint();
        }
 
@@ -887,20 +942,19 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                if (!inE.isMetaDown())
                {
                        // Left mouse drag - pan map by appropriate amount
-                       _zoomDragging = false;
                        if (_dragFromX != -1)
                        {
                                panMap(_dragFromX - inE.getX(), _dragFromY - inE.getY());
                                _recalculate = true;
                                repaint();
                        }
-                       _dragFromX = inE.getX();
-                       _dragFromY = inE.getY();
+                       _dragFromX = _dragToX = inE.getX();
+                       _dragFromY = _dragToY = inE.getY();
                }
                else
                {
                        // Right-click and drag - draw rectangle and control zoom
-                       _zoomDragging = true;
+                       _drawMode = MODE_ZOOM_RECT;
                        if (_dragFromX == -1) {
                                _dragFromX = inE.getX();
                                _dragFromY = inE.getY();
@@ -917,7 +971,13 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
         */
        public void mouseMoved(MouseEvent inEvent)
        {
-               // ignore
+               // Ignore unless we're drawing points
+               if (_drawMode == MODE_DRAW_POINTS_CONT)
+               {
+                       _dragToX = inEvent.getX();
+                       _dragToY = inEvent.getY();
+                       repaint();
+               }
        }
 
        /**
@@ -998,8 +1058,11 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        else if (code == KeyEvent.VK_LEFT)
                                rightwardsPan = -PAN_DISTANCE;
                        panMap(rightwardsPan, upwardsPan);
+                       // Check for escape
+                       if (code == KeyEvent.VK_ESCAPE)
+                               _drawMode = MODE_DEFAULT;
                        // Check for backspace key to delete current point (delete key already handled by menu)
-                       if (code == KeyEvent.VK_BACK_SPACE && currPointIndex >= 0) {
+                       else if (code == KeyEvent.VK_BACK_SPACE && currPointIndex >= 0) {
                                _app.deleteCurrentPoint();
                        }
                }
index c2311ae186ac89a077d265be2fba3b35973787b6..35590a6e9a44964179050986d7ce6dc7c8475386 100644 (file)
@@ -69,23 +69,34 @@ public abstract class MapSource
         * @param inUrl url to check
         * @return validated url with correct prefix and trailing slash, or null
         */
-       protected static String fixBaseUrl(String inUrl)
+       public static String fixBaseUrl(String inUrl)
        {
                if (inUrl == null || inUrl.equals("")) {return null;}
-               String url = inUrl;
+               String urlstr = inUrl;
                // check prefix
                try {
-                       new URL(url);
+                       new URL(urlstr);
                }
                catch (MalformedURLException e) {
+                       // fail if protocol specified
+                       if (urlstr.indexOf("://") >= 0) {return null;}
                        // add the http protocol
-                       url = "http://" + url;
+                       urlstr = "http://" + urlstr;
                }
                // check trailing /
-               if (!url.endsWith("/")) {
-                       url = url + "/";
+               if (!urlstr.endsWith("/")) {
+                       urlstr = urlstr + "/";
                }
-               return url;
+               // Validate current url, return null if not ok
+               try {
+                       URL url = new URL(urlstr);
+                       // url host must contain a dot
+                       if (url.getHost().indexOf('.') < 0) {return null;}
+               }
+               catch (MalformedURLException e) {
+                       urlstr = null;
+               }
+               return urlstr;
        }
 
        /**
@@ -114,11 +125,14 @@ public abstract class MapSource
         */
        public String getSiteStrings()
        {
-               String s = "";
+               StringBuilder sb = new StringBuilder();
                for (int i=0; i<getNumLayers(); i++) {
                        String url = getBaseUrl(i);
-                       if (url != null) {s = s + url + ";";}
+                       if (url != null) {
+                               sb.append(url);
+                               sb.append(';');
+                       }
                }
-               return s;
+               return sb.toString();
        }
 }
index c60afb299822c623f7a4abe77a78e485e50bdc00..9fefadf33c78003248e12e119481346c667166d6 100644 (file)
@@ -1,9 +1,7 @@
 package tim.prune.jpeg;
 
 import java.io.File;
-
 import javax.swing.JOptionPane;
-
 import tim.prune.I18nManager;
 
 /**
@@ -12,10 +10,8 @@ import tim.prune.I18nManager;
  * to the external libmetadata-extractor-java library
  * instead of the included modified routines.
  *
- * To use the internal routines, set the USE_INTERNAL_LIBRARY flag to true
- * and include the internal classes in the compiled jar.
- * To use the external library, set the USE_INTERNAL_LIBRARY flag to false
- * and do not export the internal classes.
+ * Switching between internal and external libraries is
+ * handled by the ExifLibrarySwitch
  */
 public abstract class ExifGateway
 {
@@ -71,4 +67,25 @@ public abstract class ExifGateway
                if (_exifLibrary == null || !_exifLibrary.looksOK()) {key = key + ".failed";}
                return key;
        }
+
+
+
+       /**
+        * @param inNumerator numerator from Rational
+        * @param inDenominator denominator from Rational
+        * @return the value of the specified number as a positive <code>double</code>.
+        * Prevents interpretation of 32 bit numbers as negative, and forces a positive answer
+        */
+       public static final double convertToPositiveValue(int inNumerator, int inDenominator)
+       {
+               if (inDenominator == 0) return 0.0;
+               double numeratorDbl = inNumerator;
+               double denomDbl = inDenominator;
+               if (inNumerator >= 0)
+                       return numeratorDbl / denomDbl;
+               final double correction = Math.pow(2.0, 32);
+               numeratorDbl += correction;
+               if (inDenominator < 0) denomDbl += correction;
+               return numeratorDbl / denomDbl;
+       }
 }
index 1c64b4e247fcd83ba4632050667a61fe09c1c5c3..cff2346e531dff458d4b91a1a49822a270087d51 100644 (file)
@@ -42,12 +42,14 @@ public class ExternalExifLibrary implements ExifLibrary
                                {
                                        data.setLatitudeRef(gpsdir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF));
                                        Rational[] latRats = gpsdir.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
+                                       double seconds = ExifGateway.convertToPositiveValue(latRats[2].getNumerator(), latRats[2].getDenominator());
                                        data.setLatitude(new double[] {latRats[0].doubleValue(),
-                                               latRats[1].doubleValue(), latRats[2].doubleValue()});
+                                               latRats[1].doubleValue(), seconds});
                                        data.setLongitudeRef(gpsdir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF));
                                        Rational[] lonRats = gpsdir.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
+                                       seconds = ExifGateway.convertToPositiveValue(lonRats[2].getNumerator(), lonRats[2].getDenominator());
                                        data.setLongitude(new double[] {lonRats[0].doubleValue(),
-                                               lonRats[1].doubleValue(), lonRats[2].doubleValue()});
+                                               lonRats[1].doubleValue(), seconds});
                                }
 
                                // Altitude (if present)
@@ -110,6 +112,7 @@ public class ExternalExifLibrary implements ExifLibrary
                return data;
        }
 
+
        /**
         * Check whether the exifreader class can be correctly resolved
         * @return true if it looks ok
index d4df892770e395569272099de5dcaa9279aba687..384778d7418a77544f60e8f5524adacf580c4040 100644 (file)
@@ -3,6 +3,7 @@ package tim.prune.jpeg.drew;
 import java.io.File;\r
 import java.util.HashMap;\r
 \r
+import tim.prune.jpeg.ExifGateway;\r
 import tim.prune.jpeg.JpegData;\r
 \r
 /**\r
@@ -93,8 +94,7 @@ public class ExifReader
         */\r
        public ExifReader(File inFile) throws JpegException\r
        {\r
-               JpegSegmentData segments = JpegSegmentReader.readSegments(inFile);\r
-               _data = segments.getSegment(JpegSegmentReader.SEGMENT_APP1);\r
+               _data = JpegSegmentReader.readExifSegment(inFile);\r
        }\r
 \r
        /**\r
@@ -343,14 +343,16 @@ public class ExifReader
                                        break;\r
                                case TAG_GPS_LATITUDE:\r
                                        Rational[] latitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       inMetadata.setLatitude(new double[] {latitudes[0].doubleValue(), latitudes[1].doubleValue(), latitudes[2].doubleValue()});\r
+                                       inMetadata.setLatitude(new double[] {latitudes[0].doubleValue(), latitudes[1].doubleValue(),\r
+                                               ExifGateway.convertToPositiveValue(latitudes[2].getNumerator(), latitudes[2].getDenominator())});\r
                                        break;\r
                                case TAG_GPS_LONGITUDE_REF:\r
                                        inMetadata.setLongitudeRef(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
                                        break;\r
                                case TAG_GPS_LONGITUDE:\r
                                        Rational[] longitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       inMetadata.setLongitude(new double[] {longitudes[0].doubleValue(), longitudes[1].doubleValue(), longitudes[2].doubleValue()});\r
+                                       inMetadata.setLongitude(new double[] {longitudes[0].doubleValue(), longitudes[1].doubleValue(),\r
+                                               ExifGateway.convertToPositiveValue(longitudes[2].getNumerator(), longitudes[2].getDenominator())});\r
                                        break;\r
                                case TAG_GPS_ALTITUDE_REF:\r
                                        inMetadata.setAltitudeRef(_data[inTagValueOffset]);\r
index 382e9f5d1e860b439aca966b9e0c87939cde542d..65515a1b0a5c6dca079abcd23313146a553bb23b 100644 (file)
@@ -14,94 +14,64 @@ public class JpegSegmentReader
        /** End of image marker */\r
        private static final byte MARKER_EOI = (byte)0xD9;\r
 \r
-       /** APP0 Jpeg segment identifier -- Jfif data. */\r
-       public static final byte SEGMENT_APP0 = (byte)0xE0;\r
        /** APP1 Jpeg segment identifier -- where Exif data is kept. */\r
-       public static final byte SEGMENT_APP1 = (byte)0xE1;\r
-       /** APP2 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP2 = (byte)0xE2;\r
-       /** APP3 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP3 = (byte)0xE3;\r
-       /** APP4 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP4 = (byte)0xE4;\r
-       /** APP5 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP5 = (byte)0xE5;\r
-       /** APP6 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP6 = (byte)0xE6;\r
-       /** APP7 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP7 = (byte)0xE7;\r
-       /** APP8 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP8 = (byte)0xE8;\r
-       /** APP9 Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APP9 = (byte)0xE9;\r
-       /** APPA Jpeg segment identifier -- can hold Unicode comments. */\r
-       public static final byte SEGMENT_APPA = (byte)0xEA;\r
-       /** APPB Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APPB = (byte)0xEB;\r
-       /** APPC Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APPC = (byte)0xEC;\r
-       /** APPD Jpeg segment identifier -- IPTC data in here. */\r
-       public static final byte SEGMENT_APPD = (byte)0xED;\r
-       /** APPE Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APPE = (byte)0xEE;\r
-       /** APPF Jpeg segment identifier. */\r
-       public static final byte SEGMENT_APPF = (byte)0xEF;\r
-       /** Start Of Image segment identifier. */\r
-       public static final byte SEGMENT_SOI = (byte)0xD8;\r
-       /** Define Quantization Table segment identifier. */\r
-       public static final byte SEGMENT_DQT = (byte)0xDB;\r
-       /** Define Huffman Table segment identifier. */\r
-       public static final byte SEGMENT_DHT = (byte)0xC4;\r
-       /** Start-of-Frame Zero segment identifier. */\r
-       public static final byte SEGMENT_SOF0 = (byte)0xC0;\r
-       /** Jpeg comment segment identifier. */\r
-       public static final byte SEGMENT_COM = (byte)0xFE;\r
+       private static final byte SEGMENT_APP1 = (byte)0xE1;\r
 \r
        /** Magic numbers to mark the beginning of all Jpegs */\r
        private static final int MAGIC_JPEG_BYTE_1 = 0xFF;\r
        private static final int MAGIC_JPEG_BYTE_2 = 0xD8;\r
 \r
 \r
+       /**\r
+        * Get the Exif data segment for the specified file\r
+        * @param inFile File to read\r
+        * @return Exif data segment as byte array\r
+        * @throws JpegException on file read errors or exif data errors\r
+        */\r
+       public static byte[] readExifSegment(File inFile) throws JpegException\r
+       {\r
+               JpegSegmentData data = readSegments(inFile);\r
+               return data.getSegment(SEGMENT_APP1);\r
+       }\r
+\r
+\r
        /**\r
         * Obtain the Jpeg segment data from the specified file\r
         * @param inFile File to read\r
         * @return Jpeg segment data from file\r
         * @throws JpegException on file read errors or exif data errors\r
         */\r
-       public static JpegSegmentData readSegments(File inFile) throws JpegException\r
+       private static JpegSegmentData readSegments(File inFile) throws JpegException\r
        {\r
                JpegSegmentData segmentData = new JpegSegmentData();\r
-\r
                BufferedInputStream bStream = null;\r
 \r
                try\r
                {\r
                        bStream = new BufferedInputStream(new FileInputStream(inFile));\r
-                       int offset = 0;\r
                        // first two bytes should be jpeg magic number\r
-                       int magic1 = bStream.read() & 0xFF;\r
-                       int magic2 = bStream.read() & 0xFF;\r
-                       checkMagicNumbers(magic1, magic2);\r
+                       final int magic1 = bStream.read() & 0xFF;\r
+                       final int magic2 = bStream.read() & 0xFF;\r
+                       if (magic1 != MAGIC_JPEG_BYTE_1 || magic2 != MAGIC_JPEG_BYTE_2) {\r
+                               throw new JpegException("not a jpeg file");\r
+                       }\r
 \r
-                       offset += 2;\r
                        // Loop around segments found\r
+                       boolean foundExif = false;\r
                        do\r
                        {\r
                                // next byte is 0xFF\r
                                byte segmentIdentifier = (byte) (bStream.read() & 0xFF);\r
                                if ((segmentIdentifier & 0xFF) != 0xFF)\r
                                {\r
-                                       throw new JpegException("expected jpeg segment start identifier 0xFF at offset "\r
-                                               + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));\r
+                                       throw new JpegException("expected jpeg segment start 0xFF, not 0x"\r
+                                               + Integer.toHexString(segmentIdentifier & 0xFF));\r
                                }\r
-                               offset++;\r
                                // next byte is <segment-marker>\r
                                byte thisSegmentMarker = (byte) (bStream.read() & 0xFF);\r
-                               offset++;\r
                                // next 2-bytes are <segment-size>: [high-byte] [low-byte]\r
                                byte[] segmentLengthBytes = new byte[2];\r
                                bStream.read(segmentLengthBytes, 0, 2);\r
-                               offset += 2;\r
                                int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);\r
                                // segment length includes size bytes, so subtract two\r
                                segmentLength -= 2;\r
@@ -110,8 +80,11 @@ public class JpegSegmentReader
                                else if (segmentLength < 0)\r
                                        throw new JpegException("segment size would be less than zero");\r
                                byte[] segmentBytes = new byte[segmentLength];\r
-                               bStream.read(segmentBytes, 0, segmentLength);\r
-                               offset += segmentLength;\r
+                               int bytesRead = bStream.read(segmentBytes, 0, segmentLength);\r
+                               // Bail if not all bytes read in one go - otherwise following sections will be out of step\r
+                               if (bytesRead != segmentLength) {\r
+                                       throw new JpegException("Tried to read " + segmentLength + " bytes but only got " + bytesRead);\r
+                               }\r
                                if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF))\r
                                {\r
                                        // The 'Start-Of-Scan' segment comes last so break out of loop\r
@@ -126,9 +99,10 @@ public class JpegSegmentReader
                                {\r
                                        segmentData.addSegment(thisSegmentMarker, segmentBytes);\r
                                }\r
-                               // loop through to the next segment\r
+                               // loop through to the next segment if exif hasn't already been found\r
+                               foundExif = (thisSegmentMarker == SEGMENT_APP1);\r
                        }\r
-                       while (true);\r
+                       while (!foundExif);\r
                }\r
                catch (FileNotFoundException fnfe)\r
                {\r
@@ -153,19 +127,4 @@ public class JpegSegmentReader
                // Return the result\r
                return segmentData;\r
        }\r
-\r
-\r
-       /**\r
-        * Helper method that validates the Jpeg file's magic number.\r
-        * @param inMagic1 first half of magic number\r
-        * @param inMagic2 second half of magic number\r
-        * @throws JpegException if numbers do not match magic numbers expected\r
-        */\r
-       private static void checkMagicNumbers(int inMagic1, int inMagic2) throws JpegException\r
-       {\r
-               if (inMagic1 != MAGIC_JPEG_BYTE_1 || inMagic2 != MAGIC_JPEG_BYTE_2)\r
-               {\r
-                       throw new JpegException("not a jpeg file");\r
-               }\r
-       }\r
-}
\ No newline at end of file
+}\r
index 109aeb85bd7f694fe025ff14da00f50a7f55b86c..2c0b6ce41b4d0c121a387caeefa3052339a3c057 100644 (file)
@@ -29,9 +29,9 @@ menu.range.start=Stel Reeks Begin
 menu.range.end=Stel Reeks Einde
 menu.photo=Foto
 menu.photo.saveexif=Stoor na EXIF
-menu.photo.connect=Las foto by huidige punt
-menu.photo.disconnect=Ontkoppel foto vanaf huidige punt
-menu.photo.delete=Verwyder foto
+function.connecttopoint=Las foto by huidige punt
+function.disconnectfrompoint=Ontkoppel vanaf huidige punt
+function.removephoto=Verwyder foto
 menu.view=Kyk
 menu.view.browser=Kaart in werf blaaier
 menu.view.browser.google=Google Kaarte
index 93f4494fb936acab8786ba78c71ee6ad747f5766..37ab63723ea9093171d87e5769bf78b779da34ed 100644 (file)
@@ -1,5 +1,5 @@
 # Text entries for the Prune application
-# Czech entries as extra
+# Czech entries thanks to prot_d
 
 # Menu entries
 menu.file=Soubor
@@ -10,7 +10,7 @@ menu.track=Trasa
 menu.track.undo=Undo
 menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
 menu.track.deletemarked=Smazat ozna\u010den\u00e9 body
-menu.track.rearrange=P\u0159euspo\u0159\u00e1dat body
+menu.track.rearrange=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
 menu.track.rearrange.start=V\u0161e na po\u010d\u00e1tek
 menu.track.rearrange.end=V\u0161e na konec
 menu.track.rearrange.nearest=Zarovnat body na trasu
@@ -30,9 +30,7 @@ menu.point.editpoint=Upravit bod
 menu.point.deletepoint=Smazat bod
 menu.photo=Fotografie
 menu.photo.saveexif=Ulo\u017eit do Exif
-menu.photo.connect=P\u0159ipojit do bodu
-menu.photo.disconnect=Odpojit od bodu
-menu.photo.delete=Odebrat fotografii
+menu.audio=Audionahr\u00e1vka
 menu.view=Zobrazen\u00ed
 menu.view.showsidebars=Zobrazit panely
 menu.view.browser=Mapa v internetov\u00e9m prohl\u00ed\u017ee\u010di
@@ -49,7 +47,8 @@ menu.map.zoomin=P\u0159ibl\u00ed\u017eit
 menu.map.zoomout=Odd\u00e1lit
 menu.map.zoomfull=\u00dapln\u011b odd\u00e1lit
 menu.map.newpoint=Vytvo\u0159it nov\u00fd bod
-menu.map.connect=Propojit body
+menu.map.drawpoints=Vytvo\u0159it n\u011bkolik bod\u016f
+menu.map.connect=Propojit body trasy
 menu.map.autopan=Automatika zorn\u00e9ho pole
 menu.map.showmap=Zobrazit mapu
 menu.map.showscalebar=Zobrazit m\u011b\u0159\u00edtko
@@ -61,6 +60,7 @@ altkey.menu.range=R
 altkey.menu.point=B
 altkey.menu.view=Z
 altkey.menu.photo=F
+altkey.menu.audio=A
 altkey.menu.settings=N
 altkey.menu.help=P
 
@@ -81,11 +81,11 @@ function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
-function.editwaypointname=Nastavit n\u00e1zev bodu
+function.editwaypointname=Nastavit n\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
 function.compress=Komprimovat trasu
 function.addtimeoffset=P\u0159idat \u010dasov\u00fd posun
 function.addaltitudeoffset=P\u0159idat v\u00fd\u0161kov\u00fd posun
-function.convertnamestotimes=P\u0159ev\u00e9st n\u00e1zvy bod\u016f na \u010dasov\u00e9 zna\u010dky
+function.convertnamestotimes=P\u0159ev\u00e9st n\u00e1zvy v\u00fdzna\u010dn\u00fdch bod\u016f na \u010dasy
 function.deletefieldvalues=Smazat hodnoty pole
 function.findwaypoint=Hledat bod
 function.pastecoordinates=Zadat sou\u0159adnice
@@ -99,14 +99,27 @@ function.setpaths=Nastavit cestu k program\u016fm
 function.getgpsies=St\u00e1hnout trasy z Gpsies
 function.uploadgpsies=Nahr\u00e1t trasu na Gpsies
 function.lookupsrtm=Na\u010d\u00edst nadm. v\u00fd\u0161ku ze SRTM
+function.getwikipedia=Hledat na Wikipedii podle vzd\u00e1lenosti
+function.searchwikipedianames=Hledat na Wikipedii podle jm\u00e9na
+function.downloadosm=St\u00e1hnout data OSM pro oblast
 function.duplicatepoint=Zdvojit bod
 function.setcolours=Nastavit barvy
+function.setlinewidth=Nastavit tlou\u0161\u0165ku \u010d\u00e1ry
 function.setlanguage=Nastavit jazyk
+function.connecttopoint=P\u0159ipojit do bodu
+function.disconnectfrompoint=Odpojit od bodu
+function.removephoto=Odebrat fotografii
 function.correlatephotos=Sladit fotografie podle \u010dasu
 function.rearrangephotos=Uspo\u0159\u00e1dat fotografie
 function.rotatephotoleft=Oto\u010dit fotografii doleva
 function.rotatephotoright=Oto\u010dit fotografii doprava
+function.photopopup=Zobrazit celou fotografii
 function.ignoreexifthumb=Ignorovat n\u00e1hled v Exif
+function.loadaudio=P\u0159idat audionahr\u00e1vky
+function.removeaudio=Odebrat audionahr\u00e1vku
+function.correlateaudios=Sladit audionahr\u00e1vky podle \u010dasu
+function.playaudio=P\u0159ehr\u00e1t audionahr\u00e1vku
+function.stopaudio=Zastavit p\u0159ehr\u00e1v\u00e1n\u00ed
 function.help=Pomoc
 function.showkeys=Zobrazit kl\u00e1vesov\u00e9 zkratky
 function.about=O programu
@@ -149,7 +162,7 @@ dialog.jpegload.progress=Pros\u00edm chvilku strpen\u00ed p\u0159i vyhled\u00e1v
 dialog.gpsload.nogpsbabel=Nenalezen program gpsbabel. Pokra\u010dovat?
 dialog.gpsload.device=Ozna\u010den\u00ed za\u0159\u00edzen\u00ed
 dialog.gpsload.format=Form\u00e1t
-dialog.gpsload.getwaypoints=Na\u010d\u00edst body
+dialog.gpsload.getwaypoints=Na\u010d\u00edst v\u00fdzna\u010dn\u00e9 body
 dialog.gpsload.gettracks=Na\u010d\u00edst trasy
 dialog.gpsload.save=Ulo\u017eit do souboru
 dialog.gpssend.sendwaypoints=Poslat bod
@@ -168,7 +181,7 @@ dialog.save.overwrite.title=Soubor u\u017e existuje
 dialog.save.overwrite.text=Tento soubor u\u017e existuje. Opravdu chcete soubor p\u0159epsat?
 dialog.save.notypesselected=Nebyl vybr\u00e1n ani jeden typ bod\u016f
 dialog.exportkml.text=Nadpis dat
-dialog.exportkml.altitude=V\u00fd\u0161ka nad hladinou mo\u0159e (pro l\u00e9t\u00e1n\u00ed)
+dialog.exportkml.altitude=V\u00fd\u0161ka nad hladinou mo\u0159e (pro letectv\u00ed)
 dialog.exportkml.kmz=Komprimovat do souboru kmz
 dialog.exportkml.exportimages=Vlo\u017eit n\u00e1hledy fotografi\u00ed
 dialog.exportkml.trackcolour=Barva trasy
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Ulo\u017eit body n\u00e1sleduj\u00edc\u00edch typ\u016f:
 dialog.pointtype.track=Body trasy
 dialog.pointtype.waypoint=V\u00fdzna\u010dn\u00e9 body
 dialog.pointtype.photo=M\u00edsta s fotografiemi
+dialog.pointtype.audio=M\u00edsta s audionahr\u00e1vkami
 dialog.pointtype.selection=Jen v\u00fdb\u011br
 dialog.confirmreversetrack.title=Potvr\u010fte obr\u00e1cen\u00ed
 dialog.confirmreversetrack.text=Tato trasa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se obr\u00e1cen\u00edm zm\u011bn\u00ed.\nOpravdu chcete obr\u00e1tit v\u00fdb\u011br?
@@ -214,7 +228,7 @@ dialog.pointedit.table.changed=Zm\u011bn\u011bno
 dialog.pointedit.changevalue.text=Zadejte novou hodnotu pole
 dialog.pointedit.changevalue.title=Upravit pole
 dialog.pointnameedit.name=N\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
-dialog.pointnameedit.uppercase=VELKÁ p\u00edsmena
+dialog.pointnameedit.uppercase=VELK\u00c1 p\u00edsmena
 dialog.pointnameedit.lowercase=mal\u00e1 p\u00edsmena
 dialog.pointnameedit.sentencecase=Po\u010d\u00e1te\u010dn\u00ed P\u00edsmena Velk\u00e1
 dialog.addtimeoffset.add=P\u0159idat \u010das
@@ -278,12 +292,14 @@ dialog.gpsies.activity.motorbiking=Motorka
 dialog.gpsies.activity.snowshoe=Sn\u011b\u017enice
 dialog.gpsies.activity.sailing=Lo\u010f
 dialog.gpsies.activity.skating=Bruslen\u00ed
+dialog.wikipedia.column.name=N\u00e1zev \u010dl\u00e1nku
+dialog.wikipedia.column.distance=Vzd\u00e1lenost
 dialog.correlate.notimestamps=U bod\u016f nejsou \u010dasov\u00e9 zna\u010dky, tak\u017ee nen\u00ed s \u010d\u00edm fotografie sladit.
 dialog.correlate.nouncorrelatedphotos=V\u0161echny fotografie jsou slad\u011bn\u00e9.\nOpravdu chcete pokra\u010dovat?
 dialog.correlate.photoselect.intro=Vyberte jednu z t\u011bchto slad\u011bn\u00fdch fotografi\u00ed pro ur\u010den\u00ed \u010dasov\u00e9ho posunu
-dialog.correlate.photoselect.photoname=N\u00e1zev fotografie
-dialog.correlate.photoselect.timediff=\u010casov\u00fd rozd\u00edl
-dialog.correlate.photoselect.photolater=Vyfoceno pozd\u011bji
+dialog.correlate.select.photoname=N\u00e1zev fotografie
+dialog.correlate.select.timediff=\u010casov\u00fd rozd\u00edl
+dialog.correlate.select.photolater=Vyfoceno pozd\u011bji
 dialog.correlate.options.tip=Tip: kdy\u017e ru\u010dn\u011b slad\u00edte aspo\u0148 jednu fotografii, \u010dasov\u00fd posun bude vypo\u010d\u00edtat za v\u00e1s.
 dialog.correlate.options.intro=Upravte mo\u017enosti automatick\u00e9ho slad\u011bn\u00ed
 dialog.correlate.options.offsetpanel=\u010casov\u00fd posun
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=hodin,
 dialog.correlate.options.offset.minutes=minut a
 dialog.correlate.options.offset.seconds=sekund
 dialog.correlate.options.photolater=Fotografie pozd\u011bj\u0161\u00ed ne\u017e bod
-dialog.correlate.options.pointlater=Bod pozd\u011bj\u0161\u00ed ne\u017e fotografie
+dialog.correlate.options.pointlaterphoto=Bod pozd\u011bj\u0161\u00ed ne\u017e fotografie
+dialog.correlate.options.audiolater=Audio pozd\u011bj\u0161\u00ed ne\u017e bod
+dialog.correlate.options.pointlateraudio=Bod pozd\u011bj\u0161\u00ed ne\u017e audio
 dialog.correlate.options.limitspanel=Limity slad\u011bn\u00ed
 dialog.correlate.options.notimelimit=Bez \u010dasov\u00e9ho limitu
 dialog.correlate.options.timelimit=\u010casov\u00fd limit
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Bez d\u00e9lkov\u00e9ho limitu
 dialog.correlate.options.distancelimit=D\u00e9lkov\u00fd limit
 dialog.correlate.options.correlate=Sladit
 dialog.correlate.alloutsiderange=V\u0161echny fotografie le\u017e\u00ed mimo \u010dasov\u00e9 rozmez\u00ed trasy, tak\u017ee nemohou b\u00fdt slad\u011bny.\nPokuste se zm\u011bnit \u010dasov\u00fd posun nebo ru\u010dn\u011b sla\u010fte aspo\u0148 jednu fotografii.
+dialog.correlate.filetimes=\u010cas z\u00e1znamu souboru znamen\u00e1:
+dialog.correlate.filetimes2=audionahr\u00e1vky
+dialog.correlate.correltimes=Sladit tento okam\u017eik nahr\u00e1vky:
+dialog.correlate.timestamp.beginning=Za\u010d\u00e1tek
+dialog.correlate.timestamp.middle=St\u0159ed
+dialog.correlate.timestamp.end=Konec
+dialog.correlate.audioselect.intro=Vyberte jednu z t\u011bchto slad\u011bn\u00fdch nahr\u00e1vek pro ur\u010den\u00ed \u010dasov\u00e9ho posunu
+dialog.correlate.select.audioname=N\u00e1zev audionahr\u00e1vky
+dialog.correlate.select.audiolater=Audio pozd\u011bj\u0161\u00ed
 dialog.rearrangephotos.desc=Vyberte um\u00edst\u011bn\u00ed a uspo\u0159\u00e1d\u00e1n\u00ed bod\u016f fotografi\u00ed
 dialog.rearrangephotos.tostart=P\u0159en\u00e9st na za\u010d\u00e1tek
 dialog.rearrangephotos.toend=P\u0159en\u00e9st na konec
@@ -380,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Cache s mapami
 dialog.saveconfig.prune.kmzimagewidth=\u0160\u00ed\u0159ka bitmapy KMZ
 dialog.saveconfig.prune.kmzimageheight=V\u00fd\u0161ka bitmapy KMZ
 dialog.saveconfig.prune.colourscheme=Barevn\u00e9 sch\u00e9ma
+dialog.saveconfig.prune.linewidth=Tlou\u0161\u0165ka \u010d\u00e1ry
 dialog.saveconfig.prune.kmltrackcolour=Barva trasy v KML
 dialog.setpaths.intro=Je-li to t\u0159eba, m\u016f\u017eete nastavit cesty k extern\u00edm aplikac\u00edm:
 dialog.setpaths.found=Cesta nalezena?
@@ -409,6 +437,9 @@ dialog.diskcache.dir=Adres\u00e1\u0159 s cache
 dialog.diskcache.createdir=Vytvo\u0159it adres\u00e1\u0159
 dialog.diskcache.nocreate=Adres\u00e1\u0159 nebyl vytvo\u0159en
 dialog.deletefieldvalues.intro=Vyberte pole, kter\u00e9 se m\u00e1 z aktu\u00e1ln\u00edho rozmez\u00ed odstranit
+dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed trasa (1-4)
+dialog.downloadosm.desc=Potvr\u010fte, \u017ee se maj\u00ed k dan\u00e9 oblasti st\u00e1hnout data OSM:
+dialog.searchwikipedianames.search=Vyhledat:
 
 # 3d window
 dialog.3d.title=Trojrozm\u011brn\u00e9 zobrazen\u00ed Prune
@@ -438,16 +469,21 @@ confirm.undo.single=operace vr\u00e1cena
 confirm.undo.multi=operac\u00ed vr\u00e1ceno
 confirm.jpegload.single=fotografie p\u0159id\u00e1na
 confirm.jpegload.multi=fotografie p\u0159id\u00e1ny
-confirm.photo.connect=fotografie propojena
+confirm.media.connect=soubor p\u0159ipojen
 confirm.photo.disconnect=fotografie odpojena
-confirm.correlate.single=fotografie slad\u011bna
-confirm.correlate.multi=fotografie slad\u011bny
+confirm.audio.disconnect=audionahr\u00e1vka odpojena
+confirm.media.removed=odstran\u011bno
+confirm.correlatephotos.single=fotografie slad\u011bna
+confirm.correlatephotos.multi=fotografie slad\u011bny
 confirm.createpoint=bod vytvo\u0159en
 confirm.rotatephoto=fotografie oto\u010dena
 confirm.running=Prob\u00edh\u00e1 ...
 confirm.lookupsrtm1=Nalezeno
 confirm.lookupsrtm2=v\u00fd\u0161kov\u00fdch hodnot
 confirm.deletefieldvalues=Hodnoty pole smaz\u00e1ny
+confirm.audioload=Audionahr\u00e1vky p\u0159id\u00e1ny
+confirm.correlateaudios.single=Audionahr\u00e1vka slad\u011bna
+confirm.correlateaudios.multi=Audionahr\u00e1vky slad\u011bny
 
 # Buttons
 button.ok=OK
@@ -490,6 +526,7 @@ filetype.kmz=soubory KMZ
 filetype.gpx=soubory GPX
 filetype.pov=soubory POV
 filetype.svg=soubory SVG
+filetype.audio=soubory MP3, OGG, WAV
 
 # Display components
 display.nodata=\u017d\u00e1dn\u00e1 data
@@ -524,12 +561,17 @@ details.range.maxspeed=Max. rychlost
 details.range.numsegments=Po\u010det segment\u016f
 details.range.pace=Tempo
 details.range.gradient=Sp\u00e1d
-details.waypointsphotos.waypoints=V\u00fdzna\u010dn\u00e9 body
-details.waypointsphotos.photos=Fotografie
+details.lists.waypoints=V\u00fdzna\u010dn\u00e9 body
+details.lists.photos=Fotografie
+details.lists.audio=Audionahr\u00e1vky
 details.photodetails=Detaily fotografie
 details.nophoto=Fotografie nevybr\u00e1na
 details.photo.loading=Na\u010d\u00edt\u00e1m
-details.photo.connected=P\u0159ipojeno
+details.media.connected=P\u0159ipojeno
+details.audiodetails=Detaily audionahr\u00e1vky
+details.noaudio=Audionahr\u00e1vka nevybr\u00e1na
+details.audio.file=Zvukov\u00fd soubor
+details.audio.playing=p\u0159ehr\u00e1v\u00e1n...
 map.overzoom=P\u0159i tomto p\u0159ibl\u00ed\u017een\u00ed mapa nen\u00ed k dispozici
 
 # Field names
@@ -572,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.cz
+wikipedia.lang=cs
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -582,9 +625,11 @@ cardinal.w=W
 # Undo operations
 undo.load=na\u010d\u00edst data
 undo.loadphotos=na\u010d\u00edst fotografie
+undo.loadaudios=na\u010d\u00edst audionahr\u00e1vky
 undo.editpoint=upravit bod
 undo.deletepoint=smazat bod
-undo.deletephoto=odebrat fotografii
+undo.removephoto=odebrat fotografii
+undo.removeaudio=odebrat audionahr\u00e1vku
 undo.deleterange=smazat rozmez\u00ed
 undo.compress=zkomprimovat trasu
 undo.insert=vlo\u017eit body
@@ -594,15 +639,16 @@ undo.addtimeoffset=p\u0159idat \u010dasov\u00fd posun
 undo.addaltitudeoffset=p\u0159idat v\u00fd\u0161kov\u00fd posun
 undo.rearrangewaypoints=p\u0159euspo\u0159\u00e1dat body
 undo.cutandmove=p\u0159esunout v\u00fdb\u011br
-undo.connectphoto=p\u0159ipojit fotografii
-undo.disconnectphoto=odpojit fotografii
-undo.correlate=sladit fotografie
+undo.connect=p\u0159ipojit
+undo.disconnect=odpojit
+undo.correlatephotos=sladit fotografie
 undo.rearrangephotos=uspo\u0159\u00e1dat fotografie
 undo.createpoint=vytvo\u0159it bod
 undo.rotatephoto=oto\u010dit fotografii
 undo.convertnamestotimes=p\u0159ev\u00e9st n\u00e1zvy na \u010dasy
 undo.lookupsrtm=na\u010d\u00edst nadm. v\u00fd\u0161ky ze SRTM
 undo.deletefieldvalues=smazat hodnoty pol\u00ed
+undo.correlateaudios=sladit audionahr\u00e1vky
 
 # Error messages
 error.save.dialogtitle=Chyba p\u0159i ukl\u00e1d\u00e1n\u00ed
@@ -624,9 +670,9 @@ error.load.othererror=Chyba p\u0159i \u010dten\u00ed souboru:
 error.jpegload.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed fotografi\u00ed
 error.jpegload.nofilesfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory
 error.jpegload.nojpegsfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory jpeg
-error.jpegload.noexiffound=Nenalezena informace EXIF
 error.jpegload.nogpsfound=Nenalezena informace GPS
 error.jpegload.exifreadfailed=Nepoda\u0159ilo se na\u010d\u00edst informaci EXIF. Tu nelze na\u010d\u00edst\nbez intern\u00ed nebo extern\u00ed knihovny.
+error.audioload.nofilesfound=Nebyly nalezeny \u017e\u00e1dn\u00e9 zvukov\u00e9 soubory.
 error.gpsload.unknown=Nezn\u00e1m\u00e1 chyba
 error.undofailed.title=Selhalo undo
 error.undofailed.text=Nepoda\u0159ilo se vr\u00e1tit operaci
@@ -644,3 +690,4 @@ error.lookupsrtm.nonefound=Pro tyto body nen\u00ed k dispozici informace o nadmo
 error.lookupsrtm.nonerequired=U v\u0161ech bod\u016f u\u017e je informaci o v\u00fd\u0161ce, tak\u017ee nen\u00ed co dohled\u00e1vat
 error.gpsies.uploadnotok=Server gpsies vr\u00e1til hl\u00e1\u0161en\u00ed
 error.gpsies.uploadfailed=Chyba - nepoda\u0159ilo se nahr\u00e1t data.
+error.playaudiofailed=Nepoda\u0159ilo se p\u0159ehr\u00e1t zvukov\u00fd soubor.
index cc950599b6d7de41e762f627283b4b5bb0751655..a2a45baa6e3190a4d381773d10462a8af927c53e 100644 (file)
@@ -30,9 +30,10 @@ menu.point.editpoint=Punkt bearbeiten
 menu.point.deletepoint=Punkt l\u00f6schen
 menu.photo=Foto
 menu.photo.saveexif=Exif Daten speichern
-menu.photo.connect=Mit Punkt verkn\u00fcpfen
-menu.photo.disconnect=Vom Punkt trennen
-menu.photo.delete=Foto entfernen
+function.connecttopoint=Mit Punkt verkn\u00fcpfen
+function.disconnectfrompoint=Vom Punkt trennen
+function.removephoto=Foto entfernen
+menu.audio=Audio
 menu.view=Ansicht
 menu.view.showsidebars=Seitenleisten anzeigen
 menu.view.browser=Karte in Browser
@@ -44,6 +45,7 @@ menu.map.zoomin=Hineinzoomen
 menu.map.zoomout=Herauszoomen
 menu.map.zoomfull=Auf Bildschirmgr\u00f6\u00dfe zoomen
 menu.map.newpoint=Neuen Punkt erzeugen
+menu.map.drawpoints=Punktereihe aufzeichnen
 menu.map.connect=Trackpunkte mit Linie anzeigen
 menu.map.autopan=Autozentrierung
 menu.map.showmap=Karte zeigen
@@ -56,6 +58,7 @@ altkey.menu.range=B
 altkey.menu.point=P
 altkey.menu.view=A
 altkey.menu.photo=F
+altkey.menu.audio=U
 altkey.menu.settings=E
 altkey.menu.help=H
 
@@ -94,14 +97,24 @@ function.setpaths=Programmpfade setzen
 function.getgpsies=Gpsies Tracks holen
 function.uploadgpsies=Daten zum Gpsies hochladen
 function.lookupsrtm=H\u00f6hendaten von SRTM holen
+function.getwikipedia=Wikipediaartikeln in der N\u00e4he nachschlagen
+function.searchwikipedianames=Wikipedia mit Name durchsuchen
+function.downloadosm=OSM Daten f\u00fcr dieses Gebiet herunterladen
 function.duplicatepoint=Punkt verdoppeln
 function.setcolours=Farben einstellen
+function.setlinewidth=Liniedicke einstellen
 function.setlanguage=Sprache einstellen
 function.correlatephotos=Fotos korrelieren
 function.rearrangephotos=Fotos reorganisieren
 function.rotatephotoleft=Foto nach Links drehen
 function.rotatephotoright=Foto nach Rechts drehen
+function.photopopup=Fotofenster anzeigen
 function.ignoreexifthumb=Exif Vorschaubild ignorieren
+function.loadaudio=Audiodateien laden
+function.removeaudio=Audiodatei entfernen
+function.correlateaudios=Audios korrelieren
+function.playaudio=Audiodatei abspielen
+function.stopaudio=Abspielen abbrechen
 function.help=Hilfe
 function.showkeys=Tastenkombinationen anzeigen
 function.about=\u00dcber Prune
@@ -188,6 +201,7 @@ dialog.pointtype.desc=Folgende Punkttypen speichern:
 dialog.pointtype.track=Trackpunkte
 dialog.pointtype.waypoint=Wegpunkte
 dialog.pointtype.photo=Fotopunkte
+dialog.pointtype.audio=Audiopunkte
 dialog.pointtype.selection=Nur aktuellen Bereich
 dialog.confirmreversetrack.title=Umkehrung best\u00e4tigen
 dialog.confirmreversetrack.text=Diese Daten enthalten Zeitangaben, die bei einer Umkehrung in falscher Reihenfolge erscheinen w\u00fcrden.\nSind Sie sicher, dass Sie diesen Bereich umkehren wollen?
@@ -273,13 +287,15 @@ dialog.gpsies.activity.motorbiking=Motorrad
 dialog.gpsies.activity.snowshoe=Schneeschuh
 dialog.gpsies.activity.sailing=Segeln
 dialog.gpsies.activity.skating=Inline-Skating
+dialog.wikipedia.column.name=Artikelname
+dialog.wikipedia.column.distance=Entfernung
 dialog.correlate.notimestamps=Die Punkte enthalten keine Zeitangaben, deshalb k\u00f6nnen die Fotos nicht zugeordnet werden.
 dialog.correlate.nouncorrelatedphotos=Alle Fotos sind schon zugeordnet.\nWollen Sie trotzdem fortfahren?
 dialog.correlate.photoselect.intro=W\u00e4hlen Sie eines dieser Fotos aus, um die Zeitdifferenz zu berechnen
-dialog.correlate.photoselect.photoname=Bezeichnung des Fotos
-dialog.correlate.photoselect.timediff=Zeitdifferenz
-dialog.correlate.photoselect.photolater=Foto sp\u00e4ter
-dialog.correlate.options.tip=Tipp: Mit mindestens einem manuell korrelierten Foto kann die Zeitdifferenz automatisch berechnet werden.
+dialog.correlate.select.photoname=Bezeichnung des Fotos
+dialog.correlate.select.timediff=Zeitdifferenz
+dialog.correlate.select.photolater=Foto sp\u00e4ter
+dialog.correlate.options.tip=Tipp: Mit mindestens einem manuell verbundenen Element kann die Zeitdifferenz automatisch berechnet werden.
 dialog.correlate.options.intro=W\u00e4hlen Sie die Optionen f\u00fcr die Korrelation aus
 dialog.correlate.options.offsetpanel=Zeitunterschied
 dialog.correlate.options.offset=Unterschied
@@ -287,7 +303,9 @@ dialog.correlate.options.offset.hours=Stunden,
 dialog.correlate.options.offset.minutes=Minuten und
 dialog.correlate.options.offset.seconds=Sekunden
 dialog.correlate.options.photolater=Foto sp\u00e4ter als Punkt
-dialog.correlate.options.pointlater=Punkt sp\u00e4ter als Foto
+dialog.correlate.options.pointlaterphoto=Punkt sp\u00e4ter als Foto
+dialog.correlate.options.audiolater=Audio sp\u00e4ter als Punkt
+dialog.correlate.options.pointlateraudio=Punkt sp\u00e4ter als Audio
 dialog.correlate.options.limitspanel=Korrelation Grenzen
 dialog.correlate.options.notimelimit=Keine Zeitgrenzen
 dialog.correlate.options.timelimit=Zeitgrenzen
@@ -295,6 +313,15 @@ dialog.correlate.options.nodistancelimit=Keine Distanzgrenzen
 dialog.correlate.options.distancelimit=Distanzgrenzen
 dialog.correlate.options.correlate=Korrelieren
 dialog.correlate.alloutsiderange=Alle Fotos sind au\u00dferhalb des Track Zeitraums. Sie k\u00f6nnen nicht korreliert werden.\nVersuchen Sie es mit einem anderen Offset oder binden Sie manuell mindestens ein Foto ein.
+dialog.correlate.filetimes=Die Datei Zeitstempel zeigen:
+dialog.correlate.filetimes2=der Tonspuren
+dialog.correlate.correltimes=F\u00fcr das Korrelieren, folgendes verwenden:
+dialog.correlate.timestamp.beginning=Anfang
+dialog.correlate.timestamp.middle=Mitte
+dialog.correlate.timestamp.end=Ende
+dialog.correlate.audioselect.intro=W\u00e4hlen Sie eines dieser Audios aus, um die Zeitdifferenz zu berechnen
+dialog.correlate.select.audioname=Audio Name
+dialog.correlate.select.audiolater=Audio sp\u00e4ter
 dialog.rearrangephotos.desc=Setzen Sie das Ziel und die Reihenfolge der Fotopunkte
 dialog.rearrangephotos.tostart=Am Anfang
 dialog.rearrangephotos.toend=Am Ende
@@ -375,6 +402,7 @@ dialog.saveconfig.prune.diskcache=Kartenordner
 dialog.saveconfig.prune.kmzimagewidth=Bildbreite in KMZ
 dialog.saveconfig.prune.kmzimageheight=Bildh\u00f6he in KMZ
 dialog.saveconfig.prune.colourscheme=Farbschema
+dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML Trackfarbe
 dialog.setpaths.intro=Sie k\u00f6nnen hier die Pfade f\u00fcr externe Applikationen setzen:
 dialog.setpaths.found=Pfad gefunden?
@@ -404,16 +432,18 @@ dialog.diskcache.dir=Kartenordner
 dialog.diskcache.createdir=Ordner anlegen
 dialog.diskcache.nocreate=Ordner wurde nicht angelegt
 dialog.deletefieldvalues.intro=W\u00e4hlen Sie das Feld aus, die Sie l\u00f6schen m\u00f6chten
+dialog.setlinewidth.text=Geben Sie die Dicke der Linien ein (1-4)
+dialog.downloadosm.desc=Best\u00e4tigen um rohe OSM Daten f\u00fcr den Gebiet herunterzuladen:
+dialog.searchwikipedianames.search=Suche nach:
 
 # 3d window
 dialog.3d.title=Prune 3D Ansicht
-dialog.3d.altitudecap=Minimum H\u00f6henskala
 dialog.3d.altitudefactor=Vervielfachungsfaktor für Höhen
 dialog.3dlines.title=Prune Gitterlinien
 dialog.3dlines.empty=Keine Linien zum Anzeigen!
 dialog.3dlines.intro=Hier sind die Linien f\u00fcr die 3D Ansicht
 
-# Confirm messages || These are displayed as confirmation in the status bar
+# Confirm messages
 confirm.loadfile=Daten aus Datei geladen
 confirm.save.ok1=Es wurden
 confirm.save.ok2=Punkte gespeichert nach
@@ -434,18 +464,23 @@ confirm.undo.single=Operation r\u00fcckg\u00e4ngig gemacht
 confirm.undo.multi=Operationen r\u00fcckg\u00e4ngig gemacht
 confirm.jpegload.single=Foto wurde geladen
 confirm.jpegload.multi=Fotos wurden geladen
-confirm.photo.connect=Foto verbunden
+confirm.media.connect=Media verbunden
 confirm.photo.disconnect=Foto getrennt
-confirm.correlate.single=Foto wurde korreliert
-confirm.correlate.multi=Fotos wurden korreliert
+confirm.audio.disconnect=Audio getrennt
+confirm.correlatephotos.single=Foto wurde korreliert
+confirm.correlatephotos.multi=Fotos wurden korreliert
 confirm.createpoint=Punkt erzeugt
 confirm.rotatephoto=Foto gedreht
 confirm.running=In Bearbeitung ...
 confirm.lookupsrtm1=Es wurden
 confirm.lookupsrtm2=H\u00f6henwerte gefunden
 confirm.deletefieldvalues=Feldwerte gelöscht
+confirm.audioload=Audiodateien geladen
+confirm.media.removed=entfernt
+confirm.correlateaudios.single=Audio wurde korreliert
+confirm.correlateaudios.multi=Audios wurden korreliert
 
-# Buttons || These are all the texts for buttons
+# Buttons
 button.ok=OK
 button.back=Zur\u00fcck
 button.next=Vorw\u00e4rts
@@ -486,6 +521,7 @@ filetype.kmz=KMZ Dateien
 filetype.gpx=GPX Dateien
 filetype.pov=POV Dateien
 filetype.svg=SVG Dateien
+filetype.audio=MP3, OGG, WAV Dateien
 
 # Display components
 display.nodata=Keine Daten geladen
@@ -520,12 +556,17 @@ details.range.maxspeed=H\u00f6chstgeschwindigkeit
 details.range.numsegments=Anzahl Abschnitte
 details.range.pace=Tempo
 details.range.gradient=Gef\u00e4lle
-details.waypointsphotos.waypoints=Wegpunkte
-details.waypointsphotos.photos=Fotos
+details.lists.waypoints=Wegpunkte
+details.lists.photos=Fotos
 details.photodetails=Fotodetails
 details.nophoto=Kein Foto ausgew\u00e4hlt
 details.photo.loading=Laden
-details.photo.connected=Verbunden
+details.media.connected=Verbunden
+details.lists.audio=Audio
+details.audiodetails=Audiodetails
+details.noaudio=Keine Audiodatei ausgew\u00e4hlt
+details.audio.file=Audiodatei
+details.audio.playing=wird abgespielt...
 map.overzoom=Keine Karten f\u00fcr diesen Zoomfaktor verf\u00fcgbar
 
 # Field names
@@ -562,6 +603,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.de
+wikipedia.lang=de
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -572,9 +614,11 @@ cardinal.w=W
 # Undo operations
 undo.load=Daten laden
 undo.loadphotos=Fotos laden
+undo.loadaudios=Audiodateien laden
 undo.editpoint=Punkt bearbeiten
 undo.deletepoint=Punkt l\u00f6schen
-undo.deletephoto=Foto entfernen
+undo.removephoto=Foto entfernen
+undo.removeaudio=Audiodatei entfernen
 undo.deleterange=Bereich l\u00f6schen
 undo.compress=Track komprimieren
 undo.insert=Punkte hinzuf\u00fcgen
@@ -584,15 +628,16 @@ undo.addtimeoffset=Zeitverschiebung aufrechnen
 undo.addaltitudeoffset=H\u00f6henverschiebung aufrechnen
 undo.rearrangewaypoints=Wegpunkte reorganisieren
 undo.cutandmove=Bereich verschieben
-undo.connectphoto=Foto verbinden
-undo.disconnectphoto=Foto trennen
-undo.correlate=Fotos korrelieren
+undo.connect=verbinden
+undo.disconnect=trennen
+undo.correlatephotos=Fotos korrelieren
 undo.rearrangephotos=Fotos reorganisieren
 undo.createpoint=Punkt erzeugen
 undo.rotatephoto=Foto umdrehen
 undo.convertnamestotimes=Namen in Zeitstempel umwandeln
 undo.lookupsrtm=H\u00f6hendaten von SRTM holen
 undo.deletefieldvalues=Feldwerte löschen
+undo.correlateaudios=Audios korrelieren
 
 # Error messages
 error.save.dialogtitle=Fehler beim Speichern
@@ -614,9 +659,9 @@ error.load.othererror=Fehler beim Lesen der Datei:
 error.jpegload.dialogtitle=Fehler beim Laden von Fotos
 error.jpegload.nofilesfound=Keine Dateien gefunden
 error.jpegload.nojpegsfound=Keine Jpeg Dateien gefunden
-error.jpegload.noexiffound=Keine EXIF Information gefunden
 error.jpegload.nogpsfound=Keine GPS Information gefunden
 error.jpegload.exifreadfailed=EXIF Aufruf fehlgeschlagen. Keine EXIF Information k\u00f6nnen gelesen werden\nohne einen internen oder externen Bibliothek.
+error.audioload.nofilesfound=Keine Audiodateien gefunden
 error.gpsload.unknown=Unbekannter Fehler
 error.undofailed.title=Undo fehlgeschlagen
 error.undofailed.text=Operation konnte nicht r\u00fcckg\u00e4ngig gemacht werden
@@ -634,3 +679,4 @@ error.lookupsrtm.nonefound=Keine H\u00f6hendaten verf
 error.lookupsrtm.nonerequired=Alle Punkte haben schon Höhendaten
 error.gpsies.uploadnotok=Der Gpsies Server hat geantwortet
 error.gpsies.uploadfailed=Das Hochladen ist fehlgeschlagen
+error.playaudiofailed=Das Abspielen der Audiodatei ist fehlgeschlagen
index 21a47c4d00a450c7f9c864503399ec9ab408535a..b4a3cb07ff1bac70d4bc2d16e7123f3d2265f45c 100644 (file)
@@ -30,9 +30,10 @@ menu.point.editpoint=Punkt editiere
 menu.point.deletepoint=Punkt lösche
 menu.photo=Föteli
 menu.photo.saveexif=Exif Date speicherä
-menu.photo.connect=Mitem Punkt verbindä
-menu.photo.disconnect=Vonem Punkt trännä
-menu.photo.delete=Föteli entfernä
+function.connecttopoint=Mitem Punkt verbindä
+function.disconnectfrompoint=Vonem Punkt trännä
+function.removephoto=Föteli entfernä
+menu.audio=Audio
 menu.view=Aasicht
 menu.view.showsidebars=Seiteleischten aazeige
 menu.view.browser=Karte inem Browser
@@ -44,6 +45,7 @@ menu.map.zoomin=Innezoome
 menu.map.zoomout=Uusezoome
 menu.map.zoomfull=Zoome zum ganzes Bild
 menu.map.newpoint=Noii Punkt
+menu.map.drawpoints=Noii Punkte uufzeichnä
 menu.map.connect=Trackpünktli verbindä
 menu.map.autopan=Autopan
 menu.map.showmap=Karte zeigä
@@ -56,6 +58,7 @@ altkey.menu.range=B
 altkey.menu.point=P
 altkey.menu.view=A
 altkey.menu.photo=F
+altkey.menu.audio=U
 altkey.menu.settings=I
 altkey.menu.help=H
 
@@ -86,22 +89,32 @@ function.deletefieldvalues=Werte von nem F
 function.pastecoordinates=Noii Koordinaten iigebe
 function.charts=Diagramme
 function.show3d=Drüü-D Aasicht
-function.distances=Distanze
+function.distances=Entfärnige
 function.fullrangedetails=Zuesätzlichi Beriichinfos
 function.setmapbg=Karte Hintegrund setzä
 function.getgpsies=Gpsies Tracks holä
 function.uploadgpsies=Date zum Gpsies uufaladä
 function.lookupsrtm=Höhendate vonem SRTM hole
+function.getwikipedia=Im Wikipedia in dr Nöchi naaluege
+function.searchwikipedianames=Wikipedia mit Name durasueche
+function.downloadosm=OSM-Date für dere Gebiet abaladä
 function.duplicatepoint=Punkt verdopplä
 function.correlatephotos=Fötelis korrelierä
 function.rearrangephotos=Fötelis reorganisierä
 function.rotatephotoleft=Föteli nach Links dräyä
 function.rotatephotoright=Föteli nach Rächts dräyä
+function.photopopup=Fötelifänschter aazeigä
 function.ignoreexifthumb=Exif Vorschaubildli ignorierä
-function.setkmzimagesize=Bildligrösse inem KMZ setze
-function.setpaths=Programmepfade setze
-function.setcolours=Farben setze
-function.setlanguage=Sproch setze
+function.loadaudio=Audiofiles lade
+function.removeaudio=Audiodatei entfernä
+function.correlateaudios=Audios korrelierä
+function.playaudio=Audiofile abspielä
+function.stopaudio=Abspielen abbrächä
+function.setkmzimagesize=Bildligrösse inem KMZ setzä
+function.setpaths=Programmepfade setzä
+function.setcolours=Farben setzä
+function.setlinewidth=Liniedicke setzä
+function.setlanguage=Sproch setzä
 function.help=Hilfe
 function.showkeys=Tastekombinatione aazeige
 function.about=Über Prune
@@ -188,6 +201,7 @@ dialog.pointtype.desc=Folgende Punkttype speichere:
 dialog.pointtype.track=Trackpunkte
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Fötelipunkte
+dialog.pointtype.audio=Audiopunkte
 dialog.pointtype.selection=Nur aktuelli Beriich
 dialog.confirmreversetrack.title=Umdrehig bestätige
 dialog.confirmreversetrack.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Umkehrig usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend dn Beriich umkehre?
@@ -241,7 +255,7 @@ dialog.charts.svgwidth=SVG Breiti
 dialog.charts.svgheight=SVG Höhi
 dialog.charts.needaltitudeortimes=Ohni Höhi Date und au ohne Ziit, isch es nöd möglech, Diagramme z zeigä.
 dialog.charts.gnuplotnotfound=Gnuplot isch mit dem Pfad nöd gfunde worde
-dialog.distances.intro=Dischtanze per Luftlinie zwüschet Punkte
+dialog.distances.intro=Entfärnige per Luftlinie zwüschet Punkte
 dialog.distances.column.from=Vom Punkt
 dialog.distances.column.to=Zum Punkt
 dialog.distances.currentpoint=Aktuelli Punkt
@@ -273,13 +287,15 @@ dialog.gpsies.activity.motorbiking=Motorrad
 dialog.gpsies.activity.snowshoe=Schneeschuh
 dialog.gpsies.activity.sailing=Segle
 dialog.gpsies.activity.skating=Inline-Skate
+dialog.wikipedia.column.name=Artikelname
+dialog.wikipedia.column.distance=Entfärnig
 dialog.correlate.notimestamps=Es hät kei Ziitstämpel inem Track innä, so s'isch nöd möglech die Fötelis zu korrelierä.
 dialog.correlate.nouncorrelatedphotos=Alle Fötelis sin scho korreliert.\nWend Sie trotzdem fortsetzä?
 dialog.correlate.photoselect.intro=Wählet Sie eini vo deren Föteli uus, um die Ziitdifferänz zu berächnä
-dialog.correlate.photoselect.photoname=Föteli Name
-dialog.correlate.photoselect.timediff=Ziitdifferänz
-dialog.correlate.photoselect.photolater=Föteli spöter
-dialog.correlate.options.tip=Tipp: Mit mindeschtens einem korrelierten Föteli, die Ziitdifferänz kann automatisch berächnet werdä.
+dialog.correlate.select.photoname=Föteli Name
+dialog.correlate.select.timediff=Ziitdifferänz
+dialog.correlate.select.photolater=Föteli spöter
+dialog.correlate.options.tip=Tipp: Mit mindeschtens einem verbundenen Elemänt kann die Ziitdifferänz automatisch berächnet werdä.
 dialog.correlate.options.intro=Wählet Sie die Optione uus für die Korrelierig
 dialog.correlate.options.offsetpanel=Ziitunterschied
 dialog.correlate.options.offset=Unterschied
@@ -287,7 +303,9 @@ dialog.correlate.options.offset.hours=Schtund
 dialog.correlate.options.offset.minutes=Minutä und
 dialog.correlate.options.offset.seconds=Sekundä
 dialog.correlate.options.photolater=Föteli spöter alsem Punkt
-dialog.correlate.options.pointlater=Punkt spöter alsem Föteli
+dialog.correlate.options.pointlaterphoto=Punkt spöter alsem Föteli
+dialog.correlate.options.audiolater=Audio spöter alsem Punkt
+dialog.correlate.options.pointlateraudio=Punkt spöter alsem Audio
 dialog.correlate.options.limitspanel=Korrelation Gränzä
 dialog.correlate.options.notimelimit=Kei Ziitgränzä
 dialog.correlate.options.timelimit=Ziitgränzä
@@ -295,9 +313,18 @@ dialog.correlate.options.nodistancelimit=Kei Distanzgr
 dialog.correlate.options.distancelimit=Distanzgränzä
 dialog.correlate.options.correlate=Korrelierä
 dialog.correlate.alloutsiderange=Alli Fötelis sin uusserhalb vonem Track Ziitruum, so chönne nöd korreliert werdä.\nVersuechet Sie mitenem anderen Offset oder verbindet Sie manuell mindeschtens eis Föteli.
+dialog.correlate.filetimes=Die Datei Zeitstempel zeigen:
+dialog.correlate.filetimes2=der Tonspuren
+dialog.correlate.correltimes=Fürs Korreliere, folgendes verwände:
+dialog.correlate.timestamp.beginning=Aafang
+dialog.correlate.timestamp.middle=Mitti
+dialog.correlate.timestamp.end=Ände
+dialog.correlate.audioselect.intro=Wählet Sie eini vo deren Audios uus, um die Ziitdifferänz zu berächnä
+dialog.correlate.select.audioname=Audio Name
+dialog.correlate.select.audiolater=Audio spöter
 dialog.rearrangephotos.desc=Bitte Ziel und Reihefolge von den Punkten setze
 dialog.rearrangephotos.tostart=zum Aafang
-dialog.rearrangephotos.toend=zum Ende
+dialog.rearrangephotos.toend=zum Ände
 dialog.rearrangephotos.nosort=Nöd sortiere
 dialog.rearrangephotos.sortbyfilename=per Filename sortiere
 dialog.rearrangephotos.sortbytime=per Ziit sortiere
@@ -354,7 +381,7 @@ dialog.checkversion.releasedate1=Die noii Version isch am
 dialog.checkversion.releasedate2=ussecho.
 dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na http://activityworkshop.net/software/prune/download.html.
 dialog.keys.intro=Aastatt d'Muus könnet Sie diese Tastekombinationen nutze
-dialog.keys.keylist=<table><tr><td>Pfiil Taste</td><td>Karte verschiebe</td></tr><tr><td>Strg + links, rächts Pfiil</td><td>Vorherigi oder nöchsti Punkt markiere</td></tr><tr><td>Strg + uuf, aba Pfiil</td><td>Ii- oder Uusezoome</td></tr><tr><td>Strg + Bild uuf, ab</td><td>Vorherigi oder nöchsti Segmänt markiere</td></tr><tr><td>Strg + Pos1, Ende</td><td>Erschti oder letschti Punkt markiere</td></tr><tr><td>Entf</td><td>Aktuelli Punkt lösche</td></tr></table>
+dialog.keys.keylist=<table><tr><td>Pfiil Taste</td><td>Karte verschiebe</td></tr><tr><td>Strg + links, rächts Pfiil</td><td>Vorherigi oder nöchsti Punkt markiere</td></tr><tr><td>Strg + uuf, aba Pfiil</td><td>Ii- oder Uusezoome</td></tr><tr><td>Strg + Bild uuf, ab</td><td>Vorherigi oder nöchsti Segmänt markiere</td></tr><tr><td>Strg + Pos1, Ände</td><td>Erschti oder letschti Punkt markiere</td></tr><tr><td>Entf</td><td>Aktuelli Punkt lösche</td></tr></table>
 dialog.keys.normalmodifier=Strg
 dialog.keys.macmodifier=Kommando
 dialog.saveconfig.desc=Die folgendi Iinstellige könne gspeicheret werde :
@@ -375,6 +402,7 @@ dialog.saveconfig.prune.diskcache=Kartenordner
 dialog.saveconfig.prune.kmzimagewidth=Bildbreiti im KMZ
 dialog.saveconfig.prune.kmzimageheight=Bildhöchi im KMZ
 dialog.saveconfig.prune.colourscheme=Farbeschema
+dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML Trackfarb
 dialog.setpaths.intro=Sie könnet dann die Pfade für dia Applikatione setzä:
 dialog.setpaths.found=Pfad gfunde?
@@ -404,10 +432,12 @@ dialog.diskcache.dir=Kartenordner
 dialog.diskcache.createdir=Ordner kreiere
 dialog.diskcache.nocreate=Ordner isch nöd kreiert worde
 dialog.deletefieldvalues.intro=Wählet Sie s Fäld uus zum lösche
+dialog.setlinewidth.text=Gäbet Sie die Dicke vonen Linien ii (1-4)
+dialog.downloadosm.desc=Best\ätige um rohi OSM Date fürn Gebiet aba zlade:
+dialog.searchwikipedianames.search=Sueche na:
 
 # 3d window
 dialog.3d.title=Prune Drüü-d Aasicht
-dialog.3d.altitudecap=Minimum Höhenskala
 dialog.3d.altitudefactor=Höchivervilfachigsfaktor
 dialog.3dlines.title=Prune Gitterlinie
 dialog.3dlines.empty=Kei Linie zum aazeigä!
@@ -434,16 +464,21 @@ confirm.undo.single=Operation r
 confirm.undo.multi=Operatione rückgängig gmacht worde.
 confirm.jpegload.single=Föteli isch glade worde
 confirm.jpegload.multi=Fötelis sin glade worde
-confirm.photo.connect=Föteli verbundä
+confirm.media.connect=Media verbundä
 confirm.photo.disconnect=Föteli gtrännt
-confirm.correlate.single=Föteli isch korreliert worde
-confirm.correlate.multi=Fötelis sin korreliert worde
+confirm.audio.disconnect=Audio gtrännt
+confirm.correlatephotos.single=Föteli isch korreliert worde
+confirm.correlatephotos.multi=Fötelis sin korreliert worde
 confirm.createpoint=Punkt kreiert worde
 confirm.rotatephoto=Föteli umgedräit worde
 confirm.running=Am Laufe ...
 confirm.lookupsrtm1=Es sin
 confirm.lookupsrtm2=Höhenwerte gfunde
-confirm.deletefieldvalues=Feldwärte glöscht
+confirm.deletefieldvalues=Feldwärte glöscht worde
+confirm.audioload=Audiofiles glade worde
+confirm.media.removed=entfärnt
+confirm.correlateaudios.single=Audiofile isch korreliert worde
+confirm.correlateaudios.multi=Audiofiles sin korreliert worde
 
 # Buttons
 button.ok=OK
@@ -486,6 +521,7 @@ filetype.kmz=KMZ Dateie
 filetype.gpx=GPX Dateie
 filetype.pov=POV Dateie
 filetype.svg=SVG Dateie
+filetype.audio=MP3, OGG, WAV Dateie
 
 # Display components
 display.nodata=Kei Date glade worde
@@ -520,12 +556,17 @@ details.range.maxspeed=H
 details.range.numsegments=Aazahl Segmänte
 details.range.pace=Tempo
 details.range.gradient=Gefälle
-details.waypointsphotos.waypoints=Waypoints
-details.waypointsphotos.photos=Fötelis
+details.lists.waypoints=Waypoints
+details.lists.photos=Fötelis
 details.photodetails=Details vonem Föteli
 details.nophoto=Kei föteli selektiert
 details.photo.loading=Ladä
-details.photo.connected=Verbundä
+details.media.connected=Verbundä
+details.lists.audio=Audio
+details.audiodetails=Audiodetails
+details.noaudio=Kei Audiofile selektiert
+details.audio.file=Audiofile
+details.audio.playing=am abschpielä...
 map.overzoom=Kei Karte mit diesem Zoom
 
 # Field names
@@ -562,6 +603,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.ch
+wikipedia.lang=als
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -572,9 +614,11 @@ cardinal.w=W
 # Undo operations
 undo.load=Date ladä
 undo.loadphotos=Fötelis ladä
+undo.loadaudios=Audiofiles ladä
 undo.editpoint=Punkt editierä
 undo.deletepoint=Punkt löschä
-undo.deletephoto=Föteli entfärnä
+undo.removephoto=Föteli entfärnä
+undo.removeaudio=Audiofile entfärnä
 undo.deleterange=Beriich löschä
 undo.compress=Track komprimierä
 undo.insert=Punkte innätuä
@@ -584,15 +628,16 @@ undo.addtimeoffset=ziitverschiebig zutue
 undo.addaltitudeoffset=höchiverschiebig zutue
 undo.rearrangewaypoints=Waypoints reorganisierä
 undo.cutandmove=Selektion movä
-undo.connectphoto=Föteli verbindä
-undo.disconnectphoto=Föteli trännä
-undo.correlate=Fötelis korrelierä
+undo.connect=verbindä
+undo.disconnect=trännä
+undo.correlatephotos=Fötelis korrelierä
 undo.rearrangephotos=Fötelis reorganisierä
 undo.createpoint=Punkt kreierä
 undo.rotatephoto=Föteli umadräya
 undo.convertnamestotimes=Name ins Ziitstämple verwondlä
 undo.lookupsrtm=Höhendate vonem SRTM holä
 undo.deletefieldvalues=Feldwärte löschä
+undo.correlateaudios=Audios korrelierä
 
 # Error messages
 error.save.dialogtitle=Fähle bim Speichere
@@ -612,11 +657,11 @@ error.load.unknownxml=Unbekanntes xml Format:
 error.load.noxmlinzip=Kei xml im Zip File gfunde
 error.load.othererror=Fähle bim Läse:
 error.jpegload.dialogtitle=Fähle bim Lade von Fötelis
-error.jpegload.nofilesfound=Kei Dateie gfunde
+error.jpegload.nofilesfound=Kei Files gfunde
 error.jpegload.nojpegsfound=Kei Jpegs gfunde
-error.jpegload.noexiffound=Kei EXIF Information gfunde
 error.jpegload.nogpsfound=Kei GPS Information gfunde
 error.jpegload.exifreadfailed=EXIF Uufruef isch fehlgschlage. Kei EXIF Infos könnet gläse werde\nohni nen interni oder extärni Bibliothek.
+error.audioload.nofilesfound=Kei Audiofiles gfunde
 error.gpsload.unknown=Unbekannts Fähler
 error.undofailed.title=Undo isch fehlgschlage worde
 error.undofailed.text=Operation kann nöd rückgängig gmacht werde
@@ -634,3 +679,4 @@ error.lookupsrtm.nonefound=Kei H
 error.lookupsrtm.nonerequired=Alle Punkte han die Höhendate scho.  Nüüt z'tue.
 error.gpsies.uploadnotok=Der Gpsies Server hät gseit gha
 error.gpsies.uploadfailed=S Uufalade isch fehlgschlage
+error.playaudiofailed=S Abschpiele vonem File isch fehlgschlage
index 42b1d5ee6a2b5179064df4b55ec3cc4ea8283ce4..869af6d0298e35fe4bfdf3fbc42aa96ff814e7cd 100644 (file)
@@ -30,9 +30,7 @@ menu.point.editpoint=Edit point
 menu.point.deletepoint=Delete point
 menu.photo=Photo
 menu.photo.saveexif=Save to Exif
-menu.photo.connect=Connect to point
-menu.photo.disconnect=Disconnect from point
-menu.photo.delete=Remove photo
+menu.audio=Audio
 menu.view=View
 menu.view.showsidebars=Show sidebars
 menu.view.browser=Map in a browser window
@@ -49,6 +47,7 @@ menu.map.zoomin=Zoom in
 menu.map.zoomout=Zoom out
 menu.map.zoomfull=Zoom to full scale
 menu.map.newpoint=Create new point
+menu.map.drawpoints=Create series of points
 menu.map.connect=Connect track points
 menu.map.autopan=Autopan
 menu.map.showmap=Show map
@@ -61,6 +60,7 @@ altkey.menu.track=T
 altkey.menu.point=P
 altkey.menu.view=V
 altkey.menu.photo=O
+altkey.menu.audio=A
 altkey.menu.settings=S
 altkey.menu.help=H
 
@@ -96,16 +96,29 @@ function.fullrangedetails=Full range details
 function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
+function.getwikipedia=Get nearby Wikipedia articles
+function.searchwikipedianames=Search Wikipedia by name
+function.downloadosm=Download OSM data for area
 function.duplicatepoint=Duplicate point
+function.connecttopoint=Connect to point
+function.disconnectfrompoint=Disconnect from point
+function.removephoto=Remove photo
 function.correlatephotos=Correlate photos
 function.rearrangephotos=Rearrange photos
 function.rotatephotoleft=Rotate photo left
 function.rotatephotoright=Rotate photo right
+function.photopopup=Show photo popup
 function.ignoreexifthumb=Ignore exif thumbnail
+function.loadaudio=Add audio files
+function.removeaudio=Remove audio file
+function.correlateaudios=Correlate audios
+function.playaudio=Play audio file
+function.stopaudio=Stop audio file
 function.setmapbg=Set map background
 function.setkmzimagesize=Set KMZ image size
 function.setpaths=Set program paths
 function.setcolours=Set colours
+function.setlinewidth=Set line width
 function.setlanguage=Set language
 function.help=Help
 function.showkeys=Show shortcut keys
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Save the following point types:
 dialog.pointtype.track=Track points
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Photo points
+dialog.pointtype.audio=Audio points
 dialog.pointtype.selection=Just selection
 dialog.confirmreversetrack.title=Confirm reversal
 dialog.confirmreversetrack.text=This track contains timestamp information, which will be out of sequence after a reversal.\nAre you sure you want to reverse this section?
@@ -278,13 +292,15 @@ dialog.gpsies.activity.motorbiking=Motorbiking
 dialog.gpsies.activity.snowshoe=Snowshoeing
 dialog.gpsies.activity.sailing=Sailing
 dialog.gpsies.activity.skating=Skating
+dialog.wikipedia.column.name=Article name
+dialog.wikipedia.column.distance=Distance
 dialog.correlate.notimestamps=There are no timestamps in the data points, so there is nothing to correlate with the photos.
 dialog.correlate.nouncorrelatedphotos=There are no uncorrelated photos.\nAre you sure you want to continue?
 dialog.correlate.photoselect.intro=Select one of these correlated photos to use as the time offset
-dialog.correlate.photoselect.photoname=Photo name
-dialog.correlate.photoselect.timediff=Time difference
-dialog.correlate.photoselect.photolater=Photo later
-dialog.correlate.options.tip=Tip: By manually correlating at least one photo, the time offset can be calculated for you.
+dialog.correlate.select.photoname=Photo name
+dialog.correlate.select.timediff=Time difference
+dialog.correlate.select.photolater=Photo later
+dialog.correlate.options.tip=Tip: By manually connecting at least one item, the time offset can be calculated for you.
 dialog.correlate.options.intro=Select the options for automatic correlation
 dialog.correlate.options.offsetpanel=Time offset
 dialog.correlate.options.offset=Offset
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=hours,
 dialog.correlate.options.offset.minutes=minutes and
 dialog.correlate.options.offset.seconds=seconds
 dialog.correlate.options.photolater=Photo later than point
-dialog.correlate.options.pointlater=Point later than photo
+dialog.correlate.options.pointlaterphoto=Point later than photo
+dialog.correlate.options.audiolater=Audio later than point
+dialog.correlate.options.pointlateraudio=Point later than audio
 dialog.correlate.options.limitspanel=Correlation limits
 dialog.correlate.options.notimelimit=No time limit
 dialog.correlate.options.timelimit=Time limit
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=No distance limit
 dialog.correlate.options.distancelimit=Distance limit
 dialog.correlate.options.correlate=Correlate
 dialog.correlate.alloutsiderange=All photos are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one photo.
+dialog.correlate.filetimes=File timestamps denote:
+dialog.correlate.filetimes2=of audio clip
+dialog.correlate.correltimes=For correlation, use:
+dialog.correlate.timestamp.beginning=Beginning
+dialog.correlate.timestamp.middle=Middle
+dialog.correlate.timestamp.end=End
+dialog.correlate.audioselect.intro=Select one of these correlated audios to use as the time offset
+dialog.correlate.select.audioname=Audio name
+dialog.correlate.select.audiolater=Audio later
 dialog.rearrangephotos.desc=Select the destination and sort order of the photo points
 dialog.rearrangephotos.tostart=Move to start
 dialog.rearrangephotos.toend=Move to end
@@ -380,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Map cache
 dialog.saveconfig.prune.kmzimagewidth=KMZ image width
 dialog.saveconfig.prune.kmzimageheight=KMZ image height
 dialog.saveconfig.prune.colourscheme=Colour scheme
+dialog.saveconfig.prune.linewidth=Line width
 dialog.saveconfig.prune.kmltrackcolour=KML track colour
 dialog.setpaths.intro=If you need to, you can choose the paths to the external applications:
 dialog.setpaths.found=Path found?
@@ -409,16 +437,18 @@ dialog.diskcache.dir=Cache directory
 dialog.diskcache.createdir=Create directory
 dialog.diskcache.nocreate=Cache directory not created
 dialog.deletefieldvalues.intro=Select the field to delete for the current range
+dialog.setlinewidth.text=Enter the thickness of lines to draw for the tracks (1-4)
+dialog.downloadosm.desc=Confirm to download the raw OSM data for the specified area:
+dialog.searchwikipedianames.search=Search for:
 
 # 3d window
 dialog.3d.title=Prune Three-d view
-dialog.3d.altitudecap=
 dialog.3d.altitudefactor=Altitude exaggeration factor
 dialog.3dlines.title=Prune gridlines
 dialog.3dlines.empty=No gridlines to display!
 dialog.3dlines.intro=These are the gridlines for the three-d view
 
-# Confirm messages || These are displayed as confirmation in the status bar
+# Confirm messages
 confirm.loadfile=Data loaded from file
 confirm.save.ok1=Successfully saved
 confirm.save.ok2=points to file
@@ -439,16 +469,21 @@ confirm.undo.single=operation undone
 confirm.undo.multi=operations undone
 confirm.jpegload.single=photo was added
 confirm.jpegload.multi=photos were added
-confirm.photo.connect=photo connected
+confirm.media.connect=media connected
 confirm.photo.disconnect=photo disconnected
-confirm.correlate.single=photo was correlated
-confirm.correlate.multi=photos were correlated
+confirm.audio.disconnect=audio disconnected
+confirm.media.removed=removed
+confirm.correlatephotos.single=photo was correlated
+confirm.correlatephotos.multi=photos were correlated
 confirm.rotatephoto=photo rotated
 confirm.createpoint=point created
 confirm.running=Running ...
 confirm.lookupsrtm1=Found
 confirm.lookupsrtm2=altitude values
 confirm.deletefieldvalues=Field values deleted
+confirm.audioload=Audio files added
+confirm.correlateaudios.single=audio was correlated
+confirm.correlateaudios.multi=audios were correlated
 
 # Buttons
 button.ok=OK
@@ -491,6 +526,7 @@ filetype.kmz=KMZ files
 filetype.gpx=GPX files
 filetype.pov=POV files
 filetype.svg=SVG files
+filetype.audio=MP3, OGG, WAV files
 
 # Display components
 display.nodata=No data loaded
@@ -525,12 +561,17 @@ details.range.maxspeed=Maximum speed
 details.range.numsegments=Number of segments
 details.range.pace=Pace
 details.range.gradient=Gradient
-details.waypointsphotos.waypoints=Waypoints
-details.waypointsphotos.photos=Photos
+details.lists.waypoints=Waypoints
+details.lists.photos=Photos
+details.lists.audio=Audio
 details.photodetails=Photo details
 details.nophoto=No photo selected
 details.photo.loading=Loading
-details.photo.connected=Connected
+details.media.connected=Connected
+details.audiodetails=Audio details
+details.noaudio=No audio file selected
+details.audio.file=Audio file
+details.audio.playing=playing...
 map.overzoom=No maps available at this zoom level
 
 # Field names
@@ -573,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.co.uk
+wikipedia.lang=en
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -583,9 +625,11 @@ cardinal.w=W
 # Undo operations
 undo.load=load data
 undo.loadphotos=load photos
+undo.loadaudios=load audio files
 undo.editpoint=edit point
 undo.deletepoint=delete point
-undo.deletephoto=remove photo
+undo.removephoto=remove photo
+undo.removeaudio=remove audio file
 undo.deleterange=delete range
 undo.compress=compress track
 undo.insert=insert points
@@ -595,15 +639,16 @@ undo.addtimeoffset=add time offset
 undo.addaltitudeoffset=add altitude offset
 undo.rearrangewaypoints=rearrange waypoints
 undo.cutandmove=move section
-undo.connectphoto=connect photo
-undo.disconnectphoto=disconnect photo
-undo.correlate=correlate photos
+undo.connect=connect
+undo.disconnect=disconnect
+undo.correlatephotos=correlate photos
 undo.rearrangephotos=rearrange photos
 undo.rotatephoto=rotate photo
 undo.createpoint=create point
 undo.convertnamestotimes=convert names to times
 undo.lookupsrtm=lookup altitudes from SRTM
 undo.deletefieldvalues=delete field values
+undo.correlateaudios=correlate audios
 
 # Error messages
 error.save.dialogtitle=Error saving data
@@ -625,9 +670,9 @@ error.load.othererror=Error reading file:
 error.jpegload.dialogtitle=Error loading photos
 error.jpegload.nofilesfound=No files found
 error.jpegload.nojpegsfound=No jpeg files found
-error.jpegload.noexiffound=No EXIF information found
 error.jpegload.nogpsfound=No GPS information found
 error.jpegload.exifreadfailed=Failed to read EXIF information. No EXIF information can be read\nwithout either an internal or external library.
+error.audioload.nofilesfound=No audio files found
 error.gpsload.unknown=Unknown error
 error.undofailed.title=Undo failed
 error.undofailed.text=Failed to undo operation
@@ -645,3 +690,4 @@ error.lookupsrtm.nonefound=No altitude values available for these points
 error.lookupsrtm.nonerequired=All points already have altitudes, so there's nothing to lookup
 error.gpsies.uploadnotok=The gpsies server returned the message
 error.gpsies.uploadfailed=The upload failed with the error
+error.playaudiofailed=Failed to play audio file
index aa76d8f734b6800c43de0576f6911247a86e7f99..c554ab365a2832b8a7b27e9a9e22e6aaf2cf5004 100644 (file)
@@ -30,9 +30,7 @@ menu.point.editpoint=Editar punto
 menu.point.deletepoint=Eliminar punto
 menu.photo=Foto
 menu.photo.saveexif=Guardar Exif
-menu.photo.connect=Conectar con punto
-menu.photo.disconnect=Desconectar de punto
-menu.photo.delete=Eliminar foto
+menu.audio=Audio
 menu.view=Ver
 menu.view.showsidebars=Mostrar barras laterales
 menu.view.browser=Mapa en una ventana del navegador
@@ -49,6 +47,7 @@ menu.map.zoomin=Ampliar zoom
 menu.map.zoomout=Reducir zoom
 menu.map.zoomfull=Mostrar todo
 menu.map.newpoint=Crear un punto nuevo
+menu.map.drawpoints=Crear series de puntos
 menu.map.connect=Conectar puntos de track
 menu.map.autopan=Posicionar autom\u00e1ticamente
 menu.map.showmap=Mostrar el mapa
@@ -61,6 +60,7 @@ altkey.menu.range=R
 altkey.menu.point=U
 altkey.menu.view=V
 altkey.menu.photo=F
+altkey.menu.audio=D
 altkey.menu.settings=P
 altkey.menu.help=Y
 
@@ -99,14 +99,27 @@ function.setpaths=Configurar rutas del programas
 function.getgpsies=Bajar ruta de Gpsies
 function.uploadgpsies=Subir recorrido a Gpsies
 function.lookupsrtm=Obtener altitudes de SRTM
+function.getwikipedia=Obtener art\u00edculos de Wikipedia cercanos
+function.searchwikipedianames=Buscar en Wikipedia por nombre
+function.downloadosm=Descargar datos OSM del \u00e1rea
 function.duplicatepoint=Duplicar punto
 function.setcolours=Establecer color
+function.setlinewidth=Establecer ancho de l\u00ednea
 function.setlanguage=Establecer lenguaje
+function.connecttopoint=Conectar con punto
+function.disconnectfrompoint=Desconectar de punto
+function.removephoto=Eliminar foto
 function.correlatephotos=Correlacionar fotos
 function.rearrangephotos=Reacomodar fotos
 function.rotatephotoleft=Girar a la izquierda
 function.rotatephotoright=Girar a la derecha
+function.photopopup=Mostrar foto en ventana emergente
 function.ignoreexifthumb=Ignorar miniatura exif
+function.loadaudio=A\u00f1adir archivos de audio
+function.removeaudio=Eliminar archivo de audio
+function.correlateaudios=Correlacionar audios
+function.playaudio=Reproducir archivo de audio
+function.stopaudio=Detener reproducci\u00f3n de audio
 function.help=Ayuda
 function.showkeys=Mostrar teclas o combinaciones de atajo
 function.about=Acerca de Prune
@@ -116,13 +129,13 @@ function.diskcache=Guardar mapas en disco
 
 # Dialogs
 dialog.exit.confirm.title=Salir de Prune
-dialog.exit.confirm.text=¿Los datos han sido modificados. Desea salir de Prune?
-dialog.openappend.title=¿Agregar a datos existentes
-dialog.openappend.text=¿Agregar estos datos a los datos ya guardados?
+dialog.exit.confirm.text=\u00bfLos datos han sido modificados. Desea salir de Prune?
+dialog.openappend.title=\u00bfAgregar a datos existentes
+dialog.openappend.text=\u00bfAgregar estos datos a los datos ya guardados?
 dialog.deletepoint.title=Borrar punto
-dialog.deletepoint.deletephoto=¿Borrar la foto tambien?
+dialog.deletepoint.deletephoto=\u00bfBorrar la foto tambien?
 dialog.deletephoto.title=Borrar foto
-dialog.deletephoto.deletepoint=¿Borrar el punto tambien?
+dialog.deletephoto.deletepoint=\u00bfBorrar el punto tambien?
 dialog.openoptions.title=Opciones de abrir
 dialog.openoptions.filesnippet=Extraer archivo
 dialog.load.table.field=Campo
@@ -146,7 +159,7 @@ dialog.jpegload.loadjpegswithoutcoords=Fotos sin coordenadas tambien
 dialog.jpegload.loadjpegsoutsidearea=Incluir fotos fuera del \u00e1rea
 dialog.jpegload.progress.title=Cargando fotos
 dialog.jpegload.progress=Por favor espere mientras se buscan las fotos
-dialog.gpsload.nogpsbabel=No se ha encontrado el programa gpsbabel. ¿Desea continuar?
+dialog.gpsload.nogpsbabel=No se ha encontrado el programa gpsbabel. \u00bfDesea continuar?
 dialog.gpsload.device=Dispositivo
 dialog.gpsload.format=Formato
 dialog.gpsload.getwaypoints=Cargar waypoints
@@ -165,7 +178,7 @@ dialog.save.coordinateunits=Unidades de las coordenadas
 dialog.save.altitudeunits=Unidades de las altitudes
 dialog.save.timestampformat=Formato del tiempo
 dialog.save.overwrite.title=El archivo ya existe
-dialog.save.overwrite.text=El archivo ya existe, ¿desea sobreescribirlo?
+dialog.save.overwrite.text=El archivo ya existe, \u00bfdesea sobreescribirlo?
 dialog.save.notypesselected=No se han seleccionado tipos de puntos
 dialog.exportkml.text=Descripci\u00f3n para los datos
 dialog.exportkml.altitude=Absoluta altitudes (para aviaci\u00f3n)
@@ -186,17 +199,19 @@ dialog.exportpov.ballsandsticks=Balas en palos
 dialog.exportpov.tubesandwalls=Tubos y paredes
 dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Est\u00e1 seguro de que desea continuar?
 dialog.exportsvg.text=Seleccione los par\u00e1metros para exportar a SVG
-dialog.exportsvg.phi=Ángulo de azimuth \u03d5
-dialog.exportsvg.theta=Ángulo de elevaci\u00f3n
+dialog.exportsvg.phi=\u00c1ngulo de azimuth \u03d5
+dialog.exportsvg.theta=\u00c1ngulo de elevaci\u00f3n
 dialog.exportsvg.gradients=Usar degradado para sombras
 dialog.pointtype.desc=Salvar los siguientes tipos de puntos:
 dialog.pointtype.track=Puntos de track
+dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Puntos de foto
+dialog.pointtype.audio=Puntos de audio
 dialog.pointtype.selection=Solo selecci\u00f3n
 dialog.confirmreversetrack.title=Confirmar inversi\u00f3n
-dialog.confirmreversetrack.text=Este track contiene informaci\u00f3n sobre la fecha, que estar\u00e1 fuera de secuencia despu\u00e9s de la inversi\u00f3n. ¿Est\u00e1 seguro que desea invertir esta secci\u00f3n?
+dialog.confirmreversetrack.text=Este track contiene informaci\u00f3n sobre la fecha, que estar\u00e1 fuera de secuencia despu\u00e9s de la inversi\u00f3n. \u00bfEst\u00e1 seguro que desea invertir esta secci\u00f3n?
 dialog.confirmcutandmove.title=Confirmar accion cortar/pegar
-dialog.confirmcutandmove.text=Este track contiene informaci\u00f3n sobre la fecha, que estar\u00e1 fuera de secuencia despu\u00e9s de mover.\n¿Esta seguro que desea mover esta secci\u00f3n?
+dialog.confirmcutandmove.text=Este track contiene informaci\u00f3n sobre la fecha, que estar\u00e1 fuera de secuencia despu\u00e9s de mover.\n\u00bfEsta seguro que desea mover esta secci\u00f3n?
 dialog.interpolate.title=Interpolar puntos
 dialog.interpolate.parameter.text=N\u00famero de los puntos a insertar entre los puntos elegidos
 dialog.undo.title=Deshacer
@@ -204,7 +219,7 @@ dialog.undo.pretext=Por favor, seleccione la operaci\u00f3n(es) a deshacer
 dialog.undo.none.title=No se puede deshacer
 dialog.undo.none.text=Ninguna operaci\u00f3n a deshacer
 dialog.clearundo.title=Despejar la lista de deshacer
-dialog.clearundo.text=¿Esta seguro que desea despejar la lista de deshacer?, ¡se perder\u00e1 toda la informaci\u00f3n!
+dialog.clearundo.text=\u00bfEsta seguro que desea despejar la lista de deshacer?, ¡se perder\u00e1 toda la informaci\u00f3n!
 dialog.pointedit.title=Editar punto
 dialog.pointedit.text=Seleccione cada campo a editar y use el bot\u00f3n 'Editar' para modificar el valor
 dialog.pointedit.table.field=Campo
@@ -227,7 +242,7 @@ dialog.findwaypoint.search=Buscar
 dialog.saveexif.title=Guardar Exif
 dialog.saveexif.intro=Seleccione fotos a guardar
 dialog.saveexif.nothingtosave=Coordenadas no modificadas, nada que guardar
-dialog.saveexif.noexiftool=No se encuentra el programa exiftool. ¿Desea continuar?
+dialog.saveexif.noexiftool=No se encuentra el programa exiftool. \u00bfDesea continuar?
 dialog.saveexif.table.photoname=Nombre de la foto
 dialog.saveexif.table.status=Estado
 dialog.saveexif.table.save=Guardar
@@ -277,12 +292,14 @@ dialog.gpsies.activity.motorbiking=En moto
 dialog.gpsies.activity.snowshoe=Raquetas de nieve
 dialog.gpsies.activity.sailing=Vela
 dialog.gpsies.activity.skating=Patinaje
+dialog.wikipedia.column.name=Nombre del art\u00edculo
+dialog.wikipedia.column.distance=Distancia
 dialog.correlate.notimestamps=No hay informaci\u00f3n de tiempo para los puntos, as\u00ed que no hay nada que correlacionar con las fotos.
-dialog.correlate.nouncorrelatedphotos=No hay fotos no correlacionadas.\n¿Est\u00e1 seguro de que desea continuar?
+dialog.correlate.nouncorrelatedphotos=No hay fotos no correlacionadas.\n\u00bfEst\u00e1 seguro de que desea continuar?
 dialog.correlate.photoselect.intro=Seleccione una de estas fotos correlacionadas para usar como margen de tiempo
-dialog.correlate.photoselect.photoname=Nombre de la foto
-dialog.correlate.photoselect.timediff=Diferencia de tiempo
-dialog.correlate.photoselect.photolater=Foto m\u00e1s adelante
+dialog.correlate.select.photoname=Nombre de la foto
+dialog.correlate.select.timediff=Diferencia de tiempo
+dialog.correlate.select.photolater=Foto m\u00e1s adelante
 dialog.correlate.options.tip=Sugerencia: Correlacionando al menos una foto manualmente, el margen de tiempo se calcula autom\u00e1ticamente.
 dialog.correlate.options.intro=Seleccionar las opciones para correlaci\u00f3n autom\u00e1tica
 dialog.correlate.options.offsetpanel=Margen de tiempo
@@ -291,7 +308,9 @@ dialog.correlate.options.offset.hours=horas,
 dialog.correlate.options.offset.minutes=minutos y
 dialog.correlate.options.offset.seconds=segundos
 dialog.correlate.options.photolater=Foto despu\u00e9s de punto
-dialog.correlate.options.pointlater=Punto despu\u00e9s de foto
+dialog.correlate.options.pointlaterphoto=Punto despu\u00e9s de foto
+dialog.correlate.options.audiolater=Audio despu\u00e9s de punto
+dialog.correlate.options.pointlateraudio=Punto despu\u00e9s de audio
 dialog.correlate.options.limitspanel=L\u00edmites de correlaci\u00f3n
 dialog.correlate.options.notimelimit=Sin l\u00edmite de tiempo
 dialog.correlate.options.timelimit=L\u00edmite de tiempo
@@ -299,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Sin l\u00edmite de distancia
 dialog.correlate.options.distancelimit=L\u00edmite de distancia
 dialog.correlate.options.correlate=Correlacionar
 dialog.correlate.alloutsiderange=Todas las fotos est\u00e1n fuera del margen horario del track, por lo que ninguna puede ser correlada.\nIntente cambiar el margen o correle manualmente al menos una foto.
+dialog.correlate.filetimes=Las marcas del archivo denotan:
+dialog.correlate.filetimes2=de sonido
+dialog.correlate.correltimes=Para correlacionar use:
+dialog.correlate.timestamp.beginning=Comienzo
+dialog.correlate.timestamp.middle=Mitad
+dialog.correlate.timestamp.end=Final
+dialog.correlate.audioselect.intro=Seleccione uno de estos audios correlacionados para usarlo como margen temporal.
+dialog.correlate.select.audioname=Nombre del audio
+dialog.correlate.select.audiolater=Audio m\u00e1s adelante
 dialog.rearrangephotos.desc=Seleccionar el destino y sortear el orden de los puntos de las fotos
 dialog.rearrangephotos.tostart=Mover al comienzo
 dialog.rearrangephotos.toend=Mover al final
@@ -367,7 +395,7 @@ dialog.saveconfig.prune.languagefile=Archivo de lenguaje
 dialog.saveconfig.prune.gpsdevice=Dispositivo GPS
 dialog.saveconfig.prune.gpsformat=Formato GPS
 dialog.saveconfig.prune.povrayfont=Fuente povray
-dialog.saveconfig.prune.metricunits=¿Usar unidades m\u00e9tricas?
+dialog.saveconfig.prune.metricunits=\u00bfUsar unidades m\u00e9tricas?
 dialog.saveconfig.prune.gnuplotpath=Ruta a gnuplot
 dialog.saveconfig.prune.gpsbabelpath=Ruta a gpsbabel
 dialog.saveconfig.prune.exiftoolpath=Ruta a exiftool
@@ -377,12 +405,13 @@ dialog.saveconfig.prune.diskcache=Memoria intermedia de mapas
 dialog.saveconfig.prune.kmzimagewidth=Ancho de im\u00e1genes en KMZ
 dialog.saveconfig.prune.kmzimageheight=Alto de im\u00e1genes en KMZ
 dialog.saveconfig.prune.colourscheme=Color de esquema
+dialog.saveconfig.prune.linewidth=Ancho de l\u00ednea
 dialog.saveconfig.prune.kmltrackcolour=Color de pista de KML
 dialog.setpaths.intro=Si usted necesita, puede escoger las rutas a aplicaciones externas
-dialog.setpaths.found=¿Ruta encontrada?
+dialog.setpaths.found=\u00bfRuta encontrada?
 dialog.addaltitude.noaltitudes=Los rangos seleccionados no contienen altitudes
 dialog.addaltitude.desc=Desplazamiento de altitud a a\u00f1adir
-dialog.lookupsrtm.overwritezeros=¿Sobrescribir valores de altitud nulos?
+dialog.lookupsrtm.overwritezeros=\u00bfSobrescribir valores de altitud nulos?
 dialog.setcolours.intro=Haga clic sobre una placa de color para cambiar el color
 dialog.setcolours.background=Fondo
 dialog.setcolours.borders=Bordes
@@ -406,10 +435,12 @@ dialog.diskcache.dir=Directorio de mapas
 dialog.diskcache.createdir=Crear directorio
 dialog.diskcache.nocreate=No se ha creado el directorio de mapas
 dialog.deletefieldvalues.intro=Seleccionar el campo a eliminar para el rango actual
+dialog.setlinewidth.text=Introduzca la anchura de las l\u00edneas a dibujar para los recorridos (1-4)
+dialog.downloadosm.desc=Confirmar la descarga de datos en bruto de OSM para el \u00e1rea especificada.
+dialog.searchwikipedianames.search=Buscar:
 
 # 3d window
 dialog.3d.title=Prune vista 3-D
-dialog.3d.altitudecap=Escala de las altitudes
 dialog.3d.altitudefactor=Factor de exageraci\u00f3n de altura
 dialog.3dlines.title=Cuadr\u00edcula Prune
 dialog.3dlines.empty=¡No hay ninguna cuadr\u00edcula!
@@ -436,16 +467,21 @@ confirm.undo.single=operaci\u00f3n deshecha
 confirm.undo.multi=operaci\u00f3n(es) deshechas(s)
 confirm.jpegload.single=Foto incluida
 confirm.jpegload.multi=Fotos incluidas
-confirm.photo.connect=Foto conectada
+confirm.media.connect=Medio conectada
 confirm.photo.disconnect=Foto desconectada
-confirm.correlate.single=foto fue correlacionada
-confirm.correlate.multi=fotos fueron correlacionadas
+confirm.audio.disconnect=Audio desconectado
+confirm.media.removed=Eliminado
+confirm.correlatephotos.single=foto fue correlacionada
+confirm.correlatephotos.multi=fotos fueron correlacionadas
 confirm.createpoint=punto creado
 confirm.rotatephoto=foto rotada
 confirm.running=Trabajando ...
 confirm.lookupsrtm1=Encontrados
 confirm.lookupsrtm2=valor de altitud para la funci\u00f3n de b\u00fasqueda SRTM
 confirm.deletefieldvalues=Valores del campo eliminados
+confirm.audioload=A\u00f1adidos archivos de audio
+confirm.correlateaudios.single=El audio fue correlacionado
+confirm.correlateaudios.multi=Los audios fueron correlacionados
 
 # Buttons
 button.ok=Aceptar
@@ -488,6 +524,7 @@ filetype.kmz=Archivos KMZ
 filetype.gpx=Archivos GPX
 filetype.pov=Archivos POV
 filetype.svg=Archivos SVG
+filetype.audio=Archivos MP3, OGG, WAV
 
 # Display components
 display.nodata=Ning\u00fan dato cargado
@@ -522,12 +559,17 @@ details.range.maxspeed=Velocidad m\u00e1xima
 details.range.numsegments=N\u00famero de segmentos
 details.range.pace=Ritmo
 details.range.gradient=Gradiente
-details.waypointsphotos.waypoints=Waypoints
-details.waypointsphotos.photos=Fotos
+details.lists.waypoints=Waypoints
+details.lists.photos=Fotos
+details.lists.audio=Audio
 details.photodetails=Detalles de la foto
 details.nophoto=Ninguna foto seleccionada
 details.photo.loading=Cargando
-details.photo.connected=Conectada
+details.media.connected=Conectada
+details.audiodetails=Detalles de audio
+details.noaudio=No se ha seleccionado ning\u00fan archivo de audio
+details.audio.file=Archivo de audio
+details.audio.playing=Reproduciendo...
 map.overzoom=No existen mapas disponibles con este nivel de enfoque
 
 # Field names
@@ -570,6 +612,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.es
+wikipedia.lang=es
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -580,9 +623,11 @@ cardinal.w=O
 # Undo operations
 undo.load=cargar datos
 undo.loadphotos=cargar fotos
+undo.loadaudios=Cargar archivos de audio
 undo.editpoint=editar punto
 undo.deletepoint=eliminar punto
-undo.deletephoto=eliminar foto
+undo.removephoto=eliminar foto
+undo.removeaudio=Eliminar archivos de audio
 undo.deleterange=eliminar rango
 undo.compress=comprimir track
 undo.insert=insertar puntos
@@ -592,15 +637,16 @@ undo.addtimeoffset=a\u00f1adir margen de tiempo
 undo.addaltitudeoffset=a\u00f1adir margen de altitud
 undo.rearrangewaypoints=reordenar waypoints
 undo.cutandmove=mover secci\u00f3n
-undo.connectphoto=conectar foto
-undo.disconnectphoto=desconectar foto
-undo.correlate=correlacionar fotos
+undo.connect=Conectar
+undo.disconnect=Desconectar
+undo.correlatephotos=correlacionar fotos
 undo.rearrangephotos=reordenar fotos
 undo.createpoint=crear punto
 undo.rotatephoto=girar foto
 undo.convertnamestotimes=convertir nombres a tiempo
 undo.lookupsrtm=obtener altitudes de SRTM
 undo.deletefieldvalues=Eliminar valores de campo
+undo.correlateaudios=Correlacionar audios
 
 # Error messages
 error.save.dialogtitle=Fallo al guardar datos
@@ -622,9 +668,9 @@ error.load.othererror=Fallo al cargar datos:
 error.jpegload.dialogtitle=Error cargando fotos
 error.jpegload.nofilesfound=No se encuentra ning\u00fan archivo
 error.jpegload.nojpegsfound=No se encuentra ning\u00fan archivo jpeg
-error.jpegload.noexiffound=No se encuentra informaci\u00f3n EXIF
 error.jpegload.nogpsfound=No se encuentra informaci\u00f3n GPS
 error.jpegload.exifreadfailed=Fallo al leer la informaci\u00f3n EXIF. No se puede leer ninguna informaci\u00f3n EXIF\ncon las librer\u00edas internas ni externas.
+error.audioload.nofilesfound=No se encontraron archivos de audio
 error.gpsload.unknown=Error desconocido
 error.undofailed.title=Fallo al deshacer
 error.undofailed.text=No ha sido posible deshacer la operaci\u00f3n
@@ -642,3 +688,4 @@ error.lookupsrtm.nonefound=No se encontraron valores de altitud
 error.lookupsrtm.nonerequired=Todos los puntos tienen altitudes, as\u00ed que no hay nada que buscar.
 error.gpsies.uploadnotok=El servidor de gpsies ha devuelto el mensaje
 error.gpsies.uploadfailed=La carga ha fallado con el error
+error.playaudiofailed=Fallo reproduciendo archivo de audio
index b2aaefea5901325b9bb0242571da0197b05d4869..2e9d558f0e63fca59936e87a219b8884a75d745f 100644 (file)
@@ -30,9 +30,9 @@ menu.point.editpoint=\u062a\u0646\u0638\u064a\u0645\u0627\u062a \u0646\u0642\u06
 menu.point.deletepoint=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0646\u0642\u0637\u0647
 menu.photo=\u0639\u06a9\u0633
 menu.photo.saveexif=\u0630\u062e\u064a\u0631\u0647 \u062f\u0631 \u0641\u0627\u064a\u0644 \u0636\u0645\u064a\u0645\u0647 \u0639\u06a9\u0633
-menu.photo.connect=\u0627\u062a\u0635\u0627\u0644 \u0628\u0647 \u0646\u0642\u0637\u0647
-menu.photo.disconnect=\u0642\u0637\u0639 \u0627\u062a\u0635\u0627\u0644 \u0627\u0632 \u0646\u0642\u0637\u0647
-menu.photo.delete=\u0628\u0631\u062f\u0627\u0634\u062a\u0646 \u0639\u06a9\u0633
+function.connecttopoint=\u0627\u062a\u0635\u0627\u0644 \u0628\u0647 \u0646\u0642\u0637\u0647
+function.disconnectfrompoint=\u0642\u0637\u0639 \u0627\u062a\u0635\u0627\u0644 \u0627\u0632 \u0646\u0642\u0637\u0647
+function.removephoto=\u0628\u0631\u062f\u0627\u0634\u062a\u0646 \u0639\u06a9\u0633
 menu.view=\u062f\u064a\u062f
 menu.view.browser=\u0646\u0642\u0634\u0647 \u062f\u0631\u062c\u0633\u062a\u062c\u0648\u06af\u0631
 menu.view.browser.google=Google Maps
index e608c9d9f1c83eca4164ebf7706fc645b0dfd077..8c7f1b3456d6a7e795e717231c21daa2e4590100 100644 (file)
@@ -30,9 +30,9 @@ menu.point.editpoint=Editer le point
 menu.point.deletepoint=Supprimer le point
 menu.photo=Photo
 menu.photo.saveexif=Enregistrer dans les Exif
-menu.photo.connect=Relier au point
-menu.photo.disconnect=D\u00e9tacher du point
-menu.photo.delete=Retirer la photo
+function.connecttopoint=Relier au point
+function.disconnectfrompoint=D\u00e9tacher du point
+function.removephoto=Retirer la photo
 menu.view=Affichage
 menu.view.browser=Ouvrir la carte dans le navigateur
 menu.view.browser.google=Google maps
@@ -102,6 +102,7 @@ function.correlatephotos=Corr\u00e9ler les photos
 function.rearrangephotos=R\u00e9arranger les photos
 function.rotatephotoleft=Tourner la photo vers la gauche
 function.rotatephotoright=Tourner la photo vers la droite
+function.photopopup=Montrer la photo
 function.ignoreexifthumb=Ignorer l\u2019aper\u00e7u Exif
 function.help=Aide
 function.showkeys=Montrer les raccourcis clavier
@@ -266,9 +267,9 @@ dialog.gpsies.activity.skating=Skating
 dialog.correlate.notimestamps=Les points n'ont pas d'indication de temps, il n'est pas possible de les corr\u00e9ler.
 dialog.correlate.nouncorrelatedphotos=Il n'y a pas de photos non-corr\u00e9l\u00e9es.\nVoulez-vous continuer ?
 dialog.correlate.photoselect.intro=S\u00e9lectionner une de ces photos corr\u00e9l\u00e9es pour d\u00e9finir le d\u00e9calage de temps
-dialog.correlate.photoselect.photoname=Nom de la photo
-dialog.correlate.photoselect.timediff=Diff\u00e9rence de temps
-dialog.correlate.photoselect.photolater=Photo prise plus tard
+dialog.correlate.select.photoname=Nom de la photo
+dialog.correlate.select.timediff=Diff\u00e9rence de temps
+dialog.correlate.select.photolater=Photo prise plus tard
 dialog.correlate.options.tip=Astuce : En corr\u00e9lant manuellement au moins une photo, le d\u00e9calage de temps peut \u00eatre calcul\u00e9 pour vous.
 dialog.correlate.options.intro=S\u00e9lectionner les options pour la corr\u00e9lation automatique
 dialog.correlate.options.offsetpanel=D\u00e9calage de temps
@@ -277,7 +278,7 @@ dialog.correlate.options.offset.hours=heures,
 dialog.correlate.options.offset.minutes=minutes et
 dialog.correlate.options.offset.seconds=secondes
 dialog.correlate.options.photolater=Photo post\u00e9rieure au point
-dialog.correlate.options.pointlater=Point post\u00e9rieur \u00e0 la photo
+dialog.correlate.options.pointlaterphoto=Point post\u00e9rieur \u00e0 la photo
 dialog.correlate.options.limitspanel=Limites de corr\u00e9lation
 dialog.correlate.options.notimelimit=Pas de limite de temps
 dialog.correlate.options.timelimit=Limite de temps
@@ -395,7 +396,6 @@ dialog.diskcache.nocreate=Le r\u00e9pertoire cache n'est pas cr\u00e9\u00e9
 
 # 3d window
 dialog.3d.title=Vue 3D de Prune
-dialog.3d.altitudecap=Etendue d'altitude minimale
 dialog.3dlines.title=Grille de Prune
 dialog.3dlines.empty=Pas de grille \u00e0 afficher !
 dialog.3dlines.intro=Ceci est la grille pour la vue 3D
@@ -421,17 +421,17 @@ confirm.undo.single=op\u00e9ration annul\u00e9e
 confirm.undo.multi=op\u00e9rations annul\u00e9es
 confirm.jpegload.single=la photo a \u00e9t\u00e9 ajout\u00e9e
 confirm.jpegload.multi=les photos ont \u00e9t\u00e9 ajout\u00e9es
-confirm.photo.connect=photo reli\u00e9e
+confirm.media.connect=m\u00e9dia reli\u00e9e
 confirm.photo.disconnect=photo d\u00e9tach\u00e9e
-confirm.correlate.single=photo a \u00e9t\u00e9 corr\u00e9l\u00e9e
-confirm.correlate.multi=photos ont \u00e9t\u00e9 corr\u00e9l\u00e9es
+confirm.correlatephotos.single=photo a \u00e9t\u00e9 corr\u00e9l\u00e9e
+confirm.correlatephotos.multi=photos ont \u00e9t\u00e9 corr\u00e9l\u00e9es
 confirm.createpoint=Point cr\u00e9\u00e9
 confirm.rotatephoto=Photo tourn\u00e9e
 confirm.running=En cours...
 confirm.lookupsrtm1=Trouv\u00e9
 confirm.lookupsrtm2=valeurs d'altitude
 
-# Buttons || These are all the texts for buttons
+# Buttons
 button.ok=OK
 button.back=Retour
 button.next=Prochain
@@ -505,12 +505,12 @@ details.range.maxspeed=Vitesse maximum
 details.range.numsegments=Nombre de segments
 details.range.pace=Allure
 details.range.gradient=Pente
-details.waypointsphotos.waypoints=Waypoints
-details.waypointsphotos.photos=Photos
+details.lists.waypoints=Waypoints
+details.lists.photos=Photos
 details.photodetails=D\u00e9tails de la photo
 details.nophoto=Pas de photo
 details.photo.loading=Chargement
-details.photo.connected=Reli\u00e9e
+details.media.connected=Reli\u00e9e
 map.overzoom=Aucune carte disponible \u00e0 ce niveau de zoom
 
 # Field names
@@ -553,6 +553,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.fr
+wikipedia.lang=fr
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -565,7 +566,7 @@ undo.load=charger les donn\u00e9es
 undo.loadphotos=charger les photos
 undo.editpoint=\u00e9diter le point
 undo.deletepoint=effacer le point
-undo.deletephoto=retirer la photo
+undo.removephoto=retirer la photo
 undo.deleterange=effacer l'\u00e9tendue
 undo.compress=compresser la trace
 undo.insert=ins\u00e9rer les points
@@ -575,9 +576,9 @@ undo.addtimeoffset=ajouter d\u00e9calage d'heure
 undo.addaltitudeoffset=ajouter d\u00e9calage d'altitude
 undo.rearrangewaypoints=r\u00e9arranger les waypoints
 undo.cutandmove=d\u00e9placer la s\u00e9lection
-undo.connectphoto=relier la photo
-undo.disconnectphoto=d\u00e9tacher la photo
-undo.correlate=corr\u00e9ler les photos
+undo.connect=relier
+undo.disconnect=d\u00e9tacher
+undo.correlatephotos=corr\u00e9ler les photos
 undo.rearrangephotos=R\u00e9arranger les photos
 undo.createpoint=ajouter un point
 undo.rotatephoto=Tourner la photo
@@ -604,7 +605,6 @@ error.load.othererror=Erreur \u00e0 la lecture du fichier :
 error.jpegload.dialogtitle=Erreur au chargement des photos
 error.jpegload.nofilesfound=Aucun fichier trouv\u00e9
 error.jpegload.nojpegsfound=Aucun fichier jpeg trouv\u00e9
-error.jpegload.noexiffound=Aucune information EXIF trouv\u00e9e
 error.jpegload.nogpsfound=Aucune information GPS trouv\u00e9e
 error.jpegload.exifreadfailed=Information EXIF illisible. Aucune information EXIF ne peut \u00eatre lue\nsans une librairie interne ou externe.
 error.gpsload.unknown=Erreur inconnue
diff --git a/tim/prune/lang/prune-texts_hu.properties b/tim/prune/lang/prune-texts_hu.properties
new file mode 100644 (file)
index 0000000..29840dd
--- /dev/null
@@ -0,0 +1,693 @@
+# Text entries for the Prune application
+# Hungarian entries thanks to Gy\u00f6rgy Ball\u00f3
+
+# Menu entries
+menu.file=F\u00e1jl
+menu.file.addphotos=F\u00e9nyk\u00e9pek hozz\u00e1ad\u00e1sa
+menu.file.save=Ment\u00e9s sz\u00f6vegk\u00e9nt
+menu.file.exit=Kil\u00e9p\u00e9s
+menu.track=Nyomvonal
+menu.track.undo=Visszavon\u00e1s
+menu.track.clearundo=Visszavon\u00e1si lista t\u00f6rl\u00e9se
+menu.track.deletemarked=Jel\u00f6lt pontok t\u00f6rl\u00e9se
+menu.track.rearrange=\u00datpontok \u00fajrarendez\u00e9se
+menu.track.rearrange.start=\u00d6sszes a f\u00e1jl elej\u00e9re
+menu.track.rearrange.end=\u00d6sszes a f\u00e1jl v\u00e9g\u00e9re
+menu.track.rearrange.nearest=Egyenk\u00e9nt a legk\u00f6zelebbi nyomponthoz
+menu.range=Tartom\u00e1ny
+menu.range.all=Mindet kijel\u00f6l
+menu.range.none=Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se
+menu.range.start=Tartom\u00e1ny kezdet\u00e9nek be\u00e1ll\u00edt\u00e1sa
+menu.range.end=Tartom\u00e1ny v\u00e9g\u00e9nek be\u00e1ll\u00edt\u00e1sa
+menu.range.deleterange=Tartom\u00e1ny t\u00f6rl\u00e9se
+menu.range.interpolate=Interpol\u00e1ci\u00f3
+menu.range.average=Kijel\u00f6l\u00e9s \u00e1tlaga
+menu.range.reverse=Tartom\u00e1ny megford\u00edt\u00e1sa
+menu.range.mergetracksegments=Nyomvonalszakaszok egyes\u00edt\u00e9se
+menu.range.cutandmove=Kijel\u00f6l\u00e9s kiv\u00e1g\u00e1sa \u00e9s mozgat\u00e1sa
+menu.point=Pont
+menu.point.editpoint=Pont szerkeszt\u00e9se
+menu.point.deletepoint=Pont t\u00f6rl\u00e9se
+menu.photo=F\u00e9nyk\u00e9p
+menu.photo.saveexif=Ment\u00e9s Exifbe
+menu.audio=Hang
+menu.view=N\u00e9zet
+menu.view.showsidebars=Oldals\u00e1vok megjelen\u00edt\u00e9se
+menu.view.browser=T\u00e9rk\u00e9p b\u00f6ng\u00e9sz\u0151ablakban
+menu.view.browser.google=Google T\u00e9rk\u00e9p
+menu.view.browser.openstreetmap=OpenStreetMap
+menu.view.browser.mapquest=Mapquest
+menu.view.browser.yahoo=Yahoo! Maps
+menu.view.browser.bing=Bing Maps
+menu.settings=Be\u00e1ll\u00edt\u00e1sok
+menu.settings.onlinemode=T\u00e9rk\u00e9pek bet\u00f6lt\u00e9se az internetr\u0151l
+menu.help=S\u00fag\u00f3
+# Popup menu for map
+menu.map.zoomin=Nagy\u00edt\u00e1s
+menu.map.zoomout=Kicsiny\u00edt\u00e9s
+menu.map.zoomfull=Nagy\u00edt\u00e1s a teljes m\u00e9retre
+menu.map.newpoint=\u00daj pont l\u00e9trehoz\u00e1sa
+menu.map.drawpoints=Pontsorozat l\u00e9trehoz\u00e1sa
+menu.map.connect=Nyompontok \u00f6sszek\u00f6t\u00e9se
+menu.map.autopan=Automatikus mozgat\u00e1s
+menu.map.showmap=T\u00e9rk\u00e9p megjelen\u00edt\u00e9se
+menu.map.showscalebar=M\u00e9retar\u00e1ny megjelen\u00edt\u00e9se
+
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=V
+altkey.menu.range=T
+altkey.menu.point=P
+altkey.menu.view=N
+altkey.menu.photo=K
+altkey.menu.audio=H
+altkey.menu.settings=B
+altkey.menu.help=S
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=O
+shortcut.menu.file.load=L
+shortcut.menu.file.save=S
+shortcut.menu.track.undo=Z
+shortcut.menu.edit.compress=C
+shortcut.menu.range.all=A
+shortcut.menu.help.help=H
+
+# Functions
+function.open=F\u00e1jl megnyit\u00e1sa
+function.loadfromgps=Adatok let\u00f6lt\u00e9se GPS-r\u0151l
+function.sendtogps=Adatok felt\u00f6lt\u00e9se GPS-re
+function.exportkml=Export\u00e1l\u00e1s KML-be
+function.exportgpx=Export\u00e1l\u00e1s GPX-be
+function.exportpov=Export\u00e1l\u00e1s POV-ba
+function.exportsvg=Export\u00e1l\u00e1s SVG-be
+function.editwaypointname=\u00datpont nev\u00e9nek szerkeszt\u00e9se
+function.compress=Nyomvonal t\u00f6m\u00f6r\u00edt\u00e9se
+function.addtimeoffset=Id\u0151eltol\u00e1s hozz\u00e1ad\u00e1sa
+function.addaltitudeoffset=Magass\u00e1geltol\u00e1s hozz\u00e1ad\u00e1sa
+function.convertnamestotimes=\u00datpontok neveinek konvert\u00e1l\u00e1sa id\u0151pontokk\u00e1
+function.deletefieldvalues=Mez\u0151 \u00e9rt\u00e9keinek t\u00f6rl\u00e9se
+function.findwaypoint=\u00datpont keres\u00e9se
+function.pastecoordinates=\u00daj koordin\u00e1t\u00e1k megad\u00e1sa
+function.charts=Diagramok
+function.show3d=3D n\u00e9zet
+function.distances=T\u00e1vols\u00e1gok
+function.fullrangedetails=Teljes tartom\u00e1ny r\u00e9szletei
+function.setmapbg=H\u00e1tt\u00e9rk\u00e9p be\u00e1ll\u00edt\u00e1sa
+function.setkmzimagesize=KMZ k\u00e9pm\u00e9ret be\u00e1ll\u00edt\u00e1sa
+function.setpaths=Program\u00fatvonalak be\u00e1ll\u00edt\u00e1sa
+function.getgpsies=Gpsies nyomvonalak let\u00f6lt\u00e9se
+function.uploadgpsies=Nyomvonal felt\u00f6lt\u00e9se Gpsiesra
+function.lookupsrtm=Magass\u00e1gok let\u00f6lt\u00e9se SRTM-r\u0151l
+function.getwikipedia=K\u00f6zeli Wikip\u00e9dia sz\u00f3cikkek let\u00f6lt\u00e9se
+function.searchwikipedianames=Keres\u00e9s a Wikip\u00e9di\u00e1ban n\u00e9v szerint
+function.downloadosm=OSM adatok let\u00f6lt\u00e9se a ter\u00fcletr\u0151l
+function.duplicatepoint=Pont kett\u0151z\u00e9se
+function.setcolours=Sz\u00ednek be\u00e1ll\u00edt\u00e1sa
+function.setlinewidth=Vonalsz\u00e9less\u00e9g be\u00e1ll\u00edt\u00e1sa
+function.setlanguage=Nyelv be\u00e1ll\u00edt\u00e1sa
+function.connecttopoint=Kapcsol\u00e1s ponthoz
+function.disconnectfrompoint=Lev\u00e1laszt\u00e1s pontr\u00f3l
+function.removephoto=F\u00e9nyk\u00e9p elt\u00e1vol\u00edt\u00e1sa
+function.correlatephotos=F\u00e9nyk\u00e9pek megfeleltet\u00e9se
+function.rearrangephotos=F\u00e9nyk\u00e9pek \u00fajrarendez\u00e9se
+function.rotatephotoleft=F\u00e9nyk\u00e9p forgat\u00e1sa balra
+function.rotatephotoright=F\u00e9nyk\u00e9p forgat\u00e1sa jobbra
+function.photopopup=F\u00e9nyk\u00e9p felugr\u00f3 ablak megjelen\u00edt\u00e9se
+function.ignoreexifthumb=Exif miniat\u0171r figyelmen k\u00edv\u00fcl hagy\u00e1sa
+function.loadaudio=Hangf\u00e1jlok hozz\u00e1ad\u00e1sa
+function.removeaudio=Hangf\u00e1jl elt\u00e1vol\u00edt\u00e1sa
+function.correlateaudios=Hangok megfeleltet\u00e9se
+function.playaudio=Hangf\u00e1jl lej\u00e1tsz\u00e1sa
+function.stopaudio=Hangf\u00e1jl meg\u00e1ll\u00edt\u00e1sa
+function.help=S\u00fag\u00f3
+function.showkeys=Gyorsbillenty\u0171k megjelen\u00edt\u00e9se
+function.about=A Prune n\u00e9vjegye
+function.checkversion=\u00daj verzi\u00f3 keres\u00e9se
+function.saveconfig=Be\u00e1ll\u00edt\u00e1sok ment\u00e9se
+function.diskcache=T\u00e9rk\u00e9pek ment\u00e9se lemezre
+
+# Dialogs
+dialog.exit.confirm.title=Kil\u00e9p\u00e9s a Prune-b\u00f3l
+dialog.exit.confirm.text=Az adatok nincsenek elmentve. Biztos benne, hogy kil\u00e9p?
+dialog.openappend.title=Hozz\u00e1f\u0171z\u00e9s a megl\u00e9v\u0151 adatokhoz
+dialog.openappend.text=Hozz\u00e1f\u0171zi ezeket az adatokat a m\u00e1r bet\u00f6lt\u00f6tt adatokhoz?
+dialog.deletepoint.title=Pont t\u00f6rl\u00e9se
+dialog.deletepoint.deletephoto=T\u00f6rli a f\u00e9nyk\u00e9pet, amely ehhez a ponthoz tartozik?
+dialog.deletephoto.title=F\u00e9nyk\u00e9p t\u00f6rl\u00e9se
+dialog.deletephoto.deletepoint=T\u00f6rli a pontot, amely ehhez a f\u00e9nyk\u00e9phez tartozik?
+dialog.openoptions.title=Be\u00e1ll\u00edt\u00e1sok megnyit\u00e1sa
+dialog.openoptions.filesnippet=F\u00e1jl kivonata
+dialog.load.table.field=Mez\u0151
+dialog.load.table.datatype=Adatt\u00edpus
+dialog.load.table.description=Le\u00edr\u00e1s
+dialog.delimiter.label=Mez\u0151elv\u00e1laszt\u00f3
+dialog.delimiter.comma=Vessz\u0151 ,
+dialog.delimiter.tab=Tabul\u00e1tor
+dialog.delimiter.space=Sz\u00f3k\u00f6z
+dialog.delimiter.semicolon=Pontosvessz\u0151 ;
+dialog.delimiter.other=Egy\u00e9b
+dialog.openoptions.deliminfo.records=rekord
+dialog.openoptions.deliminfo.fields=mez\u0151vel
+dialog.openoptions.deliminfo.norecords=Nincsenek rekordok
+dialog.openoptions.altitudeunits=Magass\u00e1g egys\u00e9ge
+dialog.open.contentsdoubled=Ez a f\u00e1jl minden egyes pont k\u00e9t p\u00e9ld\u00e1ny\u00e1t tartalmazza,\negyszer mint \u00fatpont, m\u00e1sodszor mint nyompont.
+dialog.selecttracks.intro=Nyomvonal vagy nyomvonalak kiv\u00e1laszt\u00e1sa bet\u00f6lt\u00e9shez
+dialog.selecttracks.noname=N\u00e9vtelen
+dialog.jpegload.subdirectories=Alk\u00f6nyvt\u00e1rakban is
+dialog.jpegload.loadjpegswithoutcoords=Koordin\u00e1t\u00e1k n\u00e9lk\u00fcli k\u00e9peket is
+dialog.jpegload.loadjpegsoutsidearea=A jelenlegi ter\u00fcleten k\u00edv\u00fcli k\u00e9peket is
+dialog.jpegload.progress.title=F\u00e9nyk\u00e9pek bet\u00f6lt\u00e9se
+dialog.jpegload.progress=K\u00e9rem, v\u00e1rjon, am\u00edg a f\u00e9nyk\u00e9pek keres\u00e9se tart
+dialog.gpsload.nogpsbabel=A gpsbabel program nem tal\u00e1lhat\u00f3. Folytatja?
+dialog.gpsload.device=Eszk\u00f6z neve
+dialog.gpsload.format=Form\u00e1tum
+dialog.gpsload.getwaypoints=\u00datpontok bet\u00f6lt\u00e9se
+dialog.gpsload.gettracks=Nyomvonalak bet\u00f6lt\u00e9se
+dialog.gpsload.save=Ment\u00e9s f\u00e1jlba
+dialog.gpssend.sendwaypoints=\u00datpontok k\u00fcld\u00e9se
+dialog.gpssend.sendtracks=Nyomvonalak k\u00fcld\u00e9se
+dialog.gpssend.trackname=Nyomvonal neve
+dialog.saveoptions.title=F\u00e1jl ment\u00e9se
+dialog.save.fieldstosave=Mentend\u0151 mez\u0151k
+dialog.save.table.field=Mez\u0151
+dialog.save.table.hasdata=Tartalmaz adatot
+dialog.save.table.save=Ment\u00e9s
+dialog.save.headerrow=Fejl\u00e9csor a kimenetbe
+dialog.save.coordinateunits=Koordin\u00e1ta egys\u00e9ge
+dialog.save.altitudeunits=Magass\u00e1g egys\u00e9ge
+dialog.save.timestampformat=Id\u0151b\u00e9lyeg form\u00e1tuma
+dialog.save.overwrite.title=A f\u00e1jl m\u00e1r l\u00e9tezik
+dialog.save.overwrite.text=Ez a f\u00e1jl m\u00e1r l\u00e9tezik. Biztos benne, hogy fel\u00fcl\u00edrja a f\u00e1jlt?
+dialog.save.notypesselected=Nincs pontt\u00edpus kiv\u00e1lasztva
+dialog.exportkml.text=C\u00edm az adatokhoz
+dialog.exportkml.altitude=Abszol\u00fat magass\u00e1gok (rep\u00fcl\u00e9shez)
+dialog.exportkml.kmz=T\u00f6m\u00f6r\u00edt\u00e9s kmz f\u00e1jl k\u00e9sz\u00edt\u00e9s\u00e9hez
+dialog.exportkml.exportimages=K\u00e9pminiat\u0171r\u00f6k export\u00e1l\u00e1sa kmz-be
+dialog.exportkml.trackcolour=Nyomvonal sz\u00edne
+dialog.exportgpx.name=N\u00e9v
+dialog.exportgpx.desc=Le\u00edr\u00e1s
+dialog.exportgpx.includetimestamps=Id\u0151b\u00e9lyegek is
+dialog.exportgpx.copysource=Forr\u00e1s xml m\u00e1sol\u00e1sa
+dialog.exportpov.text=Adja meg a param\u00e9tereket a POV exporthoz
+dialog.exportpov.font=Bet\u0171t\u00edpus
+dialog.exportpov.camerax=X kamera
+dialog.exportpov.cameray=Y kamera
+dialog.exportpov.cameraz=Z kamera
+dialog.exportpov.modelstyle=Modell st\u00edlusa
+dialog.exportpov.ballsandsticks=Goly\u00f3k \u00e9s botok
+dialog.exportpov.tubesandwalls=Cs\u00f6vek \u00e9s falak
+dialog.exportpov.warningtracksize=Ez a nyomvonal nagy sz\u00e1m\u00fa pontot tartalmaz, amelyet a Java3D nem biztos, hogy meg tud jelen\u00edteni.\nBiztos benne, hogy folytatni szeretn\u00e9?
+dialog.exportsvg.text=Param\u00e9terek kiv\u00e1laszt\u00e1sa az SVG exporthoz
+dialog.exportsvg.phi=Ir\u00e1nysz\u00f6g \u03d5
+dialog.exportsvg.theta=Emel\u00e9s sz\u00f6ge \u03b8
+dialog.exportsvg.gradients=\u00c1tmenetek haszn\u00e1lata az \u00e1rny\u00e9kol\u00e1shoz
+dialog.pointtype.desc=A k\u00f6vetkez\u0151 pontt\u00edpusok ment\u00e9se:
+dialog.pointtype.track=Nyompontok
+dialog.pointtype.waypoint=\u00datpontok
+dialog.pointtype.photo=F\u00e9nyk\u00e9ppontok
+dialog.pointtype.audio=Hangpontok
+dialog.pointtype.selection=Csak a kijel\u00f6lt
+dialog.confirmreversetrack.title=Megford\u00edt\u00e1s meger\u0151s\u00edt\u00e9se
+dialog.confirmreversetrack.text=Ez a nyomvonal id\u0151b\u00e9lyeg-inform\u00e1ci\u00f3t tartalmaz, amely sorrendje megford\u00edt\u00e1s ut\u00e1n megv\u00e1ltozik.\n Biztos benne, hogy megford\u00edtja a kijel\u00f6l\u00e9st?
+dialog.confirmcutandmove.title=Kiv\u00e1g\u00e1s \u00e9s mozgat\u00e1s meger\u0151s\u00edt\u00e9se
+dialog.confirmcutandmove.text=Ez a nyomvonal id\u0151b\u00e9lyeg-inform\u00e1ci\u00f3t tartalmaz, amely sorrendje mozgat\u00e1s ut\u00e1n megv\u00e1ltozik.\n Biztos benne, hogy mozgatja a kijel\u00f6l\u00e9st?
+dialog.interpolate.title=Pontok interpol\u00e1l\u00e1sa
+dialog.interpolate.parameter.text=Pontok sz\u00e1ma, amely a k\u00e9t kiv\u00e1lasztott pont k\u00f6z\u00e9 besz\u00farand\u00f3
+dialog.undo.title=M\u0171velet(ek) visszavon\u00e1sa
+dialog.undo.pretext=V\u00e1lassza ki a visszavonand\u00f3 m\u0171velet(ek)et
+dialog.undo.none.title=Nem vonhat\u00f3 vissza
+dialog.undo.none.text=Nincs visszavonhat\u00f3 m\u0171velet!
+dialog.clearundo.title=Visszavon\u00e1si lista t\u00f6rl\u00e9se
+dialog.clearundo.text=Biztos benne, hogy t\u00f6r\u00f6lni szeretn\u00e9 a visszavon\u00e1si list\u00e1t?\nMinden visszavon\u00e1si inform\u00e1ci\u00f3 el fog veszni!
+dialog.pointedit.title=Pont szerkeszt\u00e9se
+dialog.pointedit.text=V\u00e1lassza ki egyenk\u00e9nt a mez\u0151ket, amelyeket szerkeszteni szeretne, majd az \u00e9rt\u00e9k m\u00f3dos\u00edt\u00e1s\u00e1hoz haszn\u00e1lja a "Szerkeszt\u00e9s" gombot
+dialog.pointedit.table.field=Mez\u0151
+dialog.pointedit.table.value=\u00c9rt\u00e9k
+dialog.pointedit.table.changed=M\u00f3dosult
+dialog.pointedit.changevalue.text=Adjon meg egy \u00faj \u00e9rt\u00e9ket a mez\u0151h\u00f6z
+dialog.pointedit.changevalue.title=Mez\u0151 szerkeszt\u00e9se
+dialog.pointnameedit.name=\u00datpont neve
+dialog.pointnameedit.uppercase=NAGYBET\u0170S
+dialog.pointnameedit.lowercase=kisbet\u0171s
+dialog.pointnameedit.sentencecase=Nagy Kezd\u0151bet\u0171s
+dialog.addtimeoffset.add=Id\u0151 hozz\u00e1ad\u00e1sa
+dialog.addtimeoffset.subtract=Id\u0151 kivon\u00e1sa
+dialog.addtimeoffset.days=Nap
+dialog.addtimeoffset.hours=\u00d3ra
+dialog.addtimeoffset.minutes=Perc
+dialog.addtimeoffset.notimestamps=Nem adhat\u00f3 hozz\u00e1 id\u0151eltol\u00e1s, mivel a kijel\u00f6l\u00e9s nem tartalmaz id\u0151b\u00e9lyeg-inform\u00e1ci\u00f3t
+dialog.findwaypoint.intro=Adja meg az \u00fatpont nev\u00e9nek egy r\u00e9sz\u00e9t
+dialog.findwaypoint.search=Keres\u00e9s
+dialog.saveexif.title=Exif ment\u00e9se
+dialog.saveexif.intro=V\u00e1lassza ki a mentend\u0151 f\u00e9nyk\u00e9peket a jel\u00f6l\u0151n\u00e9gyzetek haszn\u00e1lat\u00e1val
+dialog.saveexif.nothingtosave=A koordin\u00e1taadatok nem m\u00f3dosultak, nincs mit menteni
+dialog.saveexif.noexiftool=Az exiftool program nem tal\u00e1lhat\u00f3. Folytatja?
+dialog.saveexif.table.photoname=F\u00e9nyk\u00e9p neve
+dialog.saveexif.table.status=\u00c1llapot
+dialog.saveexif.table.save=Ment\u00e9s
+dialog.saveexif.photostatus.connected=\u00d6sszekapcsolva
+dialog.saveexif.photostatus.disconnected=Lev\u00e1lasztva
+dialog.saveexif.photostatus.modified=M\u00f3dos\u00edtva
+dialog.saveexif.overwrite=F\u00e1jlok fel\u00fcl\u00edr\u00e1sa
+dialog.saveexif.force=K\u00e9nyszer\u00edt\u00e9s kisebb hib\u00e1k ellen\u00e9re
+dialog.charts.xaxis=X tengely
+dialog.charts.yaxis=Y tengely
+dialog.charts.output=Kimenet
+dialog.charts.screen=Kimenet k\u00e9perny\u0151re
+dialog.charts.svg=Kimenet SVG f\u00e1jlba
+dialog.charts.svgwidth=SVG sz\u00e9less\u00e9ge
+dialog.charts.svgheight=SVG magass\u00e1ga
+dialog.charts.needaltitudeortimes=Diagramok k\u00e9sz\u00edt\u00e9s\u00e9hez a nyomvonalnak tartalmaznia kell magass\u00e1gi vagy id\u0151inform\u00e1ci\u00f3kat
+dialog.charts.gnuplotnotfound=A gnuplot a megadott \u00fatvonalon nem tal\u00e1lhat\u00f3
+dialog.distances.intro=T\u00e1vols\u00e1gok l\u00e9gvonalban a pontok k\u00f6z\u00f6tt
+dialog.distances.column.from=Indul\u00f3 pont
+dialog.distances.column.to=V\u00e9gpont
+dialog.distances.currentpoint=Jelenlegi pont
+dialog.distances.toofewpoints=Ehhez a funkci\u00f3hoz \u00fatpontok kellenek, amelyek k\u00f6z\u00f6tt a t\u00e1vols\u00e1g sz\u00e1m\u00edt\u00e1sra ker\u00fcl
+dialog.fullrangedetails.intro=Itt vannak a r\u00e9szletei a kiv\u00e1lasztott tartom\u00e1nynak
+dialog.setmapbg.intro=V\u00e1lassza ki az egyik t\u00e9rk\u00e9pforr\u00e1st, vagy adjon hozz\u00e1 egy \u00fajat
+dialog.addmapsource.title=\u00daj t\u00e9rk\u00e9pforr\u00e1s hozz\u00e1ad\u00e1sa
+dialog.addmapsource.sourcename=Forr\u00e1s neve
+dialog.addmapsource.layer1url=Els\u0151 r\u00e9teg URL-je
+dialog.addmapsource.layer2url=Opcion\u00e1lis m\u00e1sodik r\u00e9teg URL-je
+dialog.addmapsource.maxzoom=Maxim\u00e1lis nagy\u00edt\u00e1si szint
+dialog.addmapsource.cloudstyle=St\u00edlus sz\u00e1ma
+dialog.addmapsource.noname=N\u00e9vtelen
+dialog.gpsies.column.name=Nyomvonal neve
+dialog.gpsies.column.length=Hossz
+dialog.gpsies.description=Le\u00edr\u00e1s
+dialog.gpsies.nodescription=Nincs le\u00edr\u00e1s
+dialog.gpsies.nonefound=Nem tal\u00e1lhat\u00f3 nyomvonal
+dialog.gpsies.username=Gpsies felhaszn\u00e1l\u00f3n\u00e9v
+dialog.gpsies.password=Gpsies jelsz\u00f3
+dialog.gpsies.keepprivate=A nyomvonal maradjon priv\u00e1t
+dialog.gpsies.confirmopenpage=Megnyitja a weboldalt a felt\u00f6lt\u00f6tt nyomvonal sz\u00e1m\u00e1ra?
+dialog.gpsies.activities=Tev\u00e9kenys\u00e9gt\u00edpusok
+dialog.gpsies.activity.trekking=T\u00far\u00e1z\u00e1s
+dialog.gpsies.activity.walking=S\u00e9ta
+dialog.gpsies.activity.jogging=Fut\u00e1s
+dialog.gpsies.activity.biking=Ker\u00e9kp\u00e1roz\u00e1s
+dialog.gpsies.activity.motorbiking=Motorker\u00e9kp\u00e1roz\u00e1s
+dialog.gpsies.activity.snowshoe=H\u00f3talpas s\u00e9ta
+dialog.gpsies.activity.sailing=Vitorl\u00e1z\u00e1s
+dialog.gpsies.activity.skating=Korcsoly\u00e1z\u00e1s
+dialog.wikipedia.column.name=Sz\u00f3cikk neve
+dialog.wikipedia.column.distance=T\u00e1vols\u00e1g
+dialog.correlate.notimestamps=Nincsenek id\u0151b\u00e9lyegek az adatpontokon, \u00edgy nem feleltethet\u0151 meg semmi a f\u00e9nyk\u00e9pekkel.
+dialog.correlate.nouncorrelatedphotos=Nincsenek megfeleltetlen f\u00e9nyk\u00e9pek.\nBiztos benne, hogy folytatja?
+dialog.correlate.photoselect.intro=V\u00e1lasszon egyet ezek k\u00f6z\u00fcl a megfeleltetett f\u00e9nyk\u00e9pek k\u00f6z\u00fcl az id\u0151eltol\u00e1s haszn\u00e1lat\u00e1hoz
+dialog.correlate.select.photoname=F\u00e9nyk\u00e9p neve
+dialog.correlate.select.timediff=Id\u0151k\u00fcl\u00f6nbs\u00e9g
+dialog.correlate.select.photolater=K\u00e9s\u0151bbi f\u00e9nyk\u00e9p
+dialog.correlate.options.tip=Tipp: legal\u00e1bb egy elem k\u00e9zzel t\u00f6rt\u00e9n\u0151 \u00f6sszekapcsol\u00e1s\u00e1val az id\u0151eltol\u00e1s kisz\u00e1m\u00edthat\u00f3.
+dialog.correlate.options.intro=V\u00e1lassza ki az opci\u00f3kat az automatikus megfeleltet\u00e9shez
+dialog.correlate.options.offsetpanel=Id\u0151eltol\u00e1s
+dialog.correlate.options.offset=Eltol\u00e1s
+dialog.correlate.options.offset.hours=\u00f3ra,
+dialog.correlate.options.offset.minutes=perc \u00e9s
+dialog.correlate.options.offset.seconds=m\u00e1sodperc
+dialog.correlate.options.photolater=A f\u00e9nyk\u00e9p k\u00e9s\u0151bbi, mint a pont
+dialog.correlate.options.pointlaterphoto=A pont k\u00e9s\u0151bbi, mint a f\u00e9nyk\u00e9p
+dialog.correlate.options.audiolater=A hang k\u00e9s\u0151bbi, mint a pont
+dialog.correlate.options.pointlateraudio=A pont k\u00e9s\u0151bbi, mint a hang
+dialog.correlate.options.limitspanel=Megfeleltet\u00e9s korl\u00e1tai
+dialog.correlate.options.notimelimit=Nincs id\u0151korl\u00e1t
+dialog.correlate.options.timelimit=Id\u0151korl\u00e1t
+dialog.correlate.options.nodistancelimit=Nincs t\u00e1vols\u00e1gkorl\u00e1t
+dialog.correlate.options.distancelimit=T\u00e1vols\u00e1gkorl\u00e1t
+dialog.correlate.options.correlate=Megfeleltet\u00e9s
+dialog.correlate.alloutsiderange=Az \u00f6sszes f\u00e9nyk\u00e9p a nyomvonal id\u0151tartom\u00e1ny\u00e1n k\u00edv\u00fcl esik, \u00edgy egyik sem feleltethet\u0151 meg.\nPr\u00f3b\u00e1lja m\u00f3dos\u00edtani az eltol\u00e1st, vagy k\u00e9zzel megfeleltetni legal\u00e1bb egy f\u00e9nyk\u00e9pet.
+dialog.correlate.filetimes=F\u00e1jl-id\u0151b\u00e9lyegek jelzik:
+dialog.correlate.filetimes2=a hangf\u00e1jlnak
+dialog.correlate.correltimes=A megfeleltet\u00e9shez haszn\u00e1lja:
+dialog.correlate.timestamp.beginning=Elej\u00e9t
+dialog.correlate.timestamp.middle=K\u00f6zep\u00e9t
+dialog.correlate.timestamp.end=V\u00e9g\u00e9t
+dialog.correlate.audioselect.intro=V\u00e1lasszon egyet ezek k\u00f6z\u00fcl a megfeleltetett f\u00e9nyk\u00e9pek k\u00f6z\u00fcl az id\u0151eltol\u00e1s haszn\u00e1lat\u00e1hoz
+dialog.correlate.select.audioname=Hang neve
+dialog.correlate.select.audiolater=Hang k\u00e9s\u0151bb
+dialog.rearrangephotos.desc=V\u00e1lassza ki a f\u00e9nyk\u00e9ppontok c\u00e9lj\u00e1t \u00e9s rendez\u00e9si sorrendj\u00e9t
+dialog.rearrangephotos.tostart=Mozgat\u00e1s a kezdet\u00e9hez
+dialog.rearrangephotos.toend=Mozgat\u00e1s a v\u00e9g\u00e9hez
+dialog.rearrangephotos.nosort=Ne rendezze
+dialog.rearrangephotos.sortbyfilename=Rendez\u00e9s f\u00e1jln\u00e9v szerint
+dialog.rearrangephotos.sortbytime=Rendez\u00e9s id\u0151 szerint
+dialog.compress.nonefound=Nem t\u00e1vol\u00edthat\u00f3 el adatpont
+dialog.compress.closepoints.title=K\u00f6zeli pontok elt\u00e1vol\u00edt\u00e1sa
+dialog.compress.closepoints.paramdesc=Hat\u00f3t\u00e1vols\u00e1g
+dialog.compress.wackypoints.title=Kisz\u00e1m\u00edthatatlan pontok elt\u00e1vol\u00edt\u00e1sa
+dialog.compress.wackypoints.paramdesc=T\u00e1vols\u00e1gt\u00e9nyez\u0151
+dialog.compress.singletons.title=Egyke pontok elt\u00e1vol\u00edt\u00e1sa
+dialog.compress.singletons.paramdesc=T\u00e1vols\u00e1gt\u00e9nyez\u0151
+dialog.compress.duplicates.title=Kett\u0151z\u00f6tt pontok elt\u00e1vol\u00edt\u00e1sa
+dialog.compress.summarylabel=T\u00f6rlend\u0151 pontok
+dialog.pastecoordinates.desc=Adja meg vagy illessze be a koordin\u00e1t\u00e1kat ide
+dialog.pastecoordinates.coords=Koordin\u00e1t\u00e1k
+dialog.pastecoordinates.nothingfound=Ellen\u0151rizze a koordin\u00e1t\u00e1kat, \u00e9s pr\u00f3b\u00e1lja \u00f3jra
+dialog.help.help=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a \n http://activityworkshop.net/software/prune/\nwebhelyet.
+dialog.about.version=Verzi\u00f3
+dialog.about.build=Build
+dialog.about.summarytext1=A Prune egy program GPS vev\u0151kr\u0151l sz\u00e1rmaz\u00f3 adatok bet\u00f6lt\u00e9s\u00e9re, megjelen\u00edt\u00e9s\u00e9re \u00e9s szerkeszt\u00e9s\u00e9re.
+dialog.about.summarytext2=Gnu GPL licenc alatt ker\u00fclt kiad\u00e1sra a szabad, ny\u00edlt, vil\u00e1gm\u00e9ret\u0171 haszn\u00e1lathoz \u00e9s fejleszt\u00e9shez.<br>M\u00e1sol\u00e1sa, terjeszt\u00e9se \u00e9s m\u00f3dos\u00edt\u00e1sa megengedett \u00e9s \u00f6szt\u00f6nz\u00f6tt<br>a mell\u00e9kelt <code>license.txt</code> f\u00e1jlban r\u00f6gz\u00edtett felt\u00e9telek szerint
+dialog.about.summarytext3=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a <code style="font-weight:bold">http://activityworkshop.net/</code> webhelyet.
+dialog.about.languages=El\u00e9rhet\u0151 nyelvek
+dialog.about.translatedby=Magyar sz\u00f6veg: Ball\u00f3 Gy\u00f6rgy
+dialog.about.systeminfo=Rendszerinform\u00e1ci\u00f3
+dialog.about.systeminfo.os=Oper\u00e1ci\u00f3s rendszer
+dialog.about.systeminfo.java=Java futtat\u00f3k\u00f6rnyezet
+dialog.about.systeminfo.java3d=Java3d telep\u00edtve
+dialog.about.systeminfo.povray=Povray telep\u00edtve
+dialog.about.systeminfo.exiftool=Exiftool telep\u00edtve
+dialog.about.systeminfo.gpsbabel=Gpsbabel telep\u00edtve
+dialog.about.systeminfo.gnuplot=Gnuplot telep\u00edtve
+dialog.about.systeminfo.exiflib=Exif f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r
+dialog.about.systeminfo.exiflib.internal=Be\u00e9p\u00edtett
+dialog.about.systeminfo.exiflib.internal.failed=Be\u00e9p\u00edtett (nem tal\u00e1lhat\u00f3)
+dialog.about.systeminfo.exiflib.external=K\u00fcls\u0151
+dialog.about.systeminfo.exiflib.external.failed=K\u00fcls\u0151 (nem tal\u00e1lhat\u00f3)
+dialog.about.yes=Igen
+dialog.about.no=Nem
+dialog.about.credits=K\u00e9sz\u00edt\u0151k
+dialog.about.credits.code=Prune k\u00f3dj\u00e1t \u00edrta:
+dialog.about.credits.exifcode=Exif k\u00f3d:
+dialog.about.credits.icons=N\u00e9h\u00e1ny ikon sz\u00e1rmazik:
+dialog.about.credits.translators=Ford\u00edt\u00f3k
+dialog.about.credits.translations=Ford\u00edt\u00e1st seg\u00edtette
+dialog.about.credits.devtools=Fejleszt\u0151eszk\u00f6z\u00f6k
+dialog.about.credits.othertools=Egy\u00e9b eszk\u00f6z\u00f6k
+dialog.about.credits.thanks=K\u00f6sz\u00f6net:
+dialog.about.readme=Olvassel
+dialog.checkversion.error=A verzi\u00f3sz\u00e1m nem ellen\u0151rizhet\u0151.\nEllen\u0151rizze az internetkapcsolatot.
+dialog.checkversion.uptodate=A Prune leg\u00fajabb verzi\u00f3j\u00e1t haszn\u00e1lja.
+dialog.checkversion.newversion1=El\u00e9rhet\u0151 a Prune \u00faj verzi\u00f3ja! A leg\u00fajabb veri\u00f3 most:
+dialog.checkversion.newversion2=.
+dialog.checkversion.releasedate1=Ez az \u00faj verzi\u00f3 kiad\u00e1sra ker\u00fclt:
+dialog.checkversion.releasedate2=.
+dialog.checkversion.download=Az \u00faj verzi\u00f3 let\u00f6lt\u00e9s\u00e9hez keresse fel a http://activityworkshop.net/software/prune/download.html webhelyet.
+dialog.keys.intro=A k\u00f6vetkez\u0151 gyorsbillenty\u0171k haszn\u00e1lhat\u00f3k az eg\u00e9r haszn\u00e1lata helyett
+dialog.keys.keylist=<table><tr><td>Ny\u00edlbillenty\u0171k</td><td>T\u00e9rk\u00e9p mozgat\u00e1sa balra, jobbra, fel, le</td></tr><tr><td>Ctrl + bal, jobb ny\u00edl</td><td>El\u0151z\u0151 vagy k\u00f6vetkez\u0151 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + fel, le ny\u00edl</td><td>Nagy\u00edt\u00e1s vagy kicsiny\u00edt\u00e9s</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>El\u0151z\u0151, k\u00f6vetkez\u0151 szakasz kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + Home, End</td><td>Els\u0151, utols\u00f3 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Del</td><td>Jelenlegi pont t\u00f6rl\u00e9se</td></tr></table>
+dialog.keys.normalmodifier=Ctrl
+dialog.keys.macmodifier=Command
+dialog.saveconfig.desc=A k\u00f6vetkez\u0151 be\u00e1ll\u00edt\u00e1sok menthet\u0151k egy konfigur\u00e1ci\u00f3s f\u00e1jlba
+dialog.saveconfig.prune.trackdirectory=Nyomvonalak k\u00f6nyvt\u00e1ra
+dialog.saveconfig.prune.photodirectory=F\u00e9nyk\u00e9pek k\u00f6nyvt\u00e1ra
+dialog.saveconfig.prune.languagecode=Nyelv k\u00f3dja (HU)
+dialog.saveconfig.prune.languagefile=Nyelvi f\u00e1jl
+dialog.saveconfig.prune.gpsdevice=GPS eszk\u00f6z
+dialog.saveconfig.prune.gpsformat=GPS form\u00e1tum
+dialog.saveconfig.prune.povrayfont=Povray bet\u0171t\u00edpus
+dialog.saveconfig.prune.metricunits=Metrikus m\u00e9rt\u00e9krendszer haszn\u00e1lata?
+dialog.saveconfig.prune.gnuplotpath=\u00datvonal a gnuplothoz
+dialog.saveconfig.prune.gpsbabelpath=\u00datvonal a gpsbabelhez
+dialog.saveconfig.prune.exiftoolpath=\u00datvonal az exiftoolhoz
+dialog.saveconfig.prune.mapsource=Kiv\u00e1lasztott t\u00e9rk\u00e9pforr\u00e1s
+dialog.saveconfig.prune.mapsourcelist=T\u00e9rk\u00e9pforr\u00e1sok
+dialog.saveconfig.prune.diskcache=T\u00e9rk\u00e9p-gyors\u00edt\u00f3t\u00e1r
+dialog.saveconfig.prune.kmzimagewidth=KMZ k\u00e9psz\u00e9less\u00e9g
+dialog.saveconfig.prune.kmzimageheight=KMZ k\u00e9pmagass\u00e1g
+dialog.saveconfig.prune.colourscheme=Sz\u00edns\u00e9ma
+dialog.saveconfig.prune.linewidth=Vonalsz\u00e9less\u00e9g
+dialog.saveconfig.prune.kmltrackcolour=KML nyomvonal sz\u00edne
+dialog.setpaths.intro=Ha sz\u00fcks\u00e9ges, kiv\u00e1laszthatja a k\u00fcls\u0151 alkalmaz\u00e1sok \u00fatvonalait:
+dialog.setpaths.found=\u00datvonal megtal\u00e1lhat\u00f3?
+dialog.addaltitude.noaltitudes=A kiv\u00e1lasztott tartom\u00e1ny nem tartalmaz magass\u00e1gi \u00e9rt\u00e9keket
+dialog.addaltitude.desc=Magass\u00e1ki eltol\u00e1s, amely hozz\u00e1adand\u00f3
+dialog.lookupsrtm.overwritezeros=Fel\u00fcl\u00edrja a nulla magass\u00e1g \u00e9rt\u00e9ket?
+dialog.setcolours.intro=A sz\u00edn m\u00f3dos\u00edt\u00e1s\u00e1hoz kattintson egy sz\u00ednfoltra
+dialog.setcolours.background=H\u00e1tt\u00e9r
+dialog.setcolours.borders=Szeg\u00e9lyek
+dialog.setcolours.lines=Vonalak
+dialog.setcolours.primary=Els\u0151dleges
+dialog.setcolours.secondary=M\u00e1sodlagos
+dialog.setcolours.point=Pontok
+dialog.setcolours.selection=Kijel\u00f6l\u00e9s
+dialog.setcolours.text=Sz\u00f6veg
+dialog.colourchooser.title=Sz\u00edn kiv\u00e1laszt\u00e1sa
+dialog.colourchooser.red=Piros
+dialog.colourchooser.green=Z\u00f6ld
+dialog.colourchooser.blue=K\u00e9k
+dialog.setlanguage.firstintro=Kiv\u00e1laszthat egy be\u00e9p\u00edtett nyelvet,<p>vagy v\u00e1lasszon egy sz\u00f6vegf\u00e1jlt helyette.
+dialog.setlanguage.secondintro=Mentenie kell a be\u00e1ll\u00edt\u00e1sokat, majd<p>a nyelv v\u00e1lt\u00e1s\u00e1hoz ind\u00edtsa \u00fajra a Prune-t.
+dialog.setlanguage.language=Nyelv
+dialog.setlanguage.languagefile=Nyelvi f\u00e1jl
+dialog.setlanguage.endmessage=Most mentse a be\u00e1ll\u00edt\u00e1sokat, \u00e9s ind\u00edtsa \u00fajra a Prune-t,\nhogy a nyelv v\u00e1lt\u00e1sa \u00e9rv\u00e9nybe l\u00e9pjen
+dialog.diskcache.save=T\u00e9rk\u00e9pek ment\u00e9se a lemezre
+dialog.diskcache.dir=Gyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra
+dialog.diskcache.createdir=K\u00f6nyvt\u00e1r l\u00e9trehoz\u00e1sa
+dialog.diskcache.nocreate=A gyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra nem ker\u00fclt l\u00e9trehoz\u00e1sra
+dialog.deletefieldvalues.intro=V\u00e1lassza ki a t\u00f6rlend\u0151 mez\u0151t a jelenlegi tartom\u00e1nyban
+dialog.setlinewidth.text=Adja meg a rajzoland\u00f3 vonalak vastags\u00e1g\u00e1t a nyomvonalak sz\u00e1m\u00e1ra (1-4)
+dialog.downloadosm.desc=Nyers OSM adatok let\u00f6lt\u00e9s\u00e9nek meger\u0151s\u00edt\u00e9se a megadott ter\u00fcletre:
+dialog.searchwikipedianames.search=Keres\u00e9s erre:
+
+# 3d window
+dialog.3d.title=Prune 3D n\u00e9zet
+dialog.3d.altitudefactor=Magass\u00e1gi ny\u00fajt\u00e1si t\u00e9nyez\u0151
+dialog.3dlines.title=Prune r\u00e1csvonalak
+dialog.3dlines.empty=Nincsenek megjelen\u00edthet\u0151 r\u00e1csvonalak!
+dialog.3dlines.intro=Ezek a r\u00e1csvonalak a 3D n\u00e9zethez
+
+# Confirm messages
+confirm.loadfile=Adatok f\u00e1jlb\u00f3l bet\u00f6ltve
+confirm.save.ok1=
+confirm.save.ok2=pont f\u00e1jlba ment\u00e9se sikeres
+confirm.deletepoint.single=adatpont t\u00f6r\u00f6lve
+confirm.deletepoint.multi=adatpont t\u00f6r\u00f6lve
+confirm.point.edit=pont szerkesztve
+confirm.mergetracksegments=Nyomvonalszakaszok egyes\u00edtve
+confirm.reverserange=Tartom\u00e1ny megford\u00edtva
+confirm.addtimeoffset=Id\u0151eltol\u00e1s hozz\u00e1adva
+confirm.addaltitudeoffset=Magass\u00e1geltol\u00e1s hozz\u00e1adva
+confirm.rearrangewaypoints=\u00datpontok \u00fajrarendezve
+confirm.rearrangephotos=F\u00e9nyk\u00e9pek \u00fajrarendezve
+confirm.cutandmove=Kijel\u00f6l\u00e9s \u00e1thelyezve
+confirm.convertnamestotimes=\u00datpont nevei konvert\u00e1lva
+confirm.saveexif.ok1=Mentve
+confirm.saveexif.ok2=k\u00e9pf\u00e1jl
+confirm.undo.single=m\u0171velet visszavonva
+confirm.undo.multi=m\u0171velet visszavonva
+confirm.jpegload.single=f\u00e9nyk\u00e9p hozz\u00e1adva
+confirm.jpegload.multi=f\u00e9nyk\u00e9p hozz\u00e1adva
+confirm.media.connect=m\u00e9dium \u00f6sszekapcsolva
+confirm.photo.disconnect=f\u00e9nyk\u00e9p lev\u00e1lasztva
+confirm.audio.disconnect=hang lev\u00e1lasztva
+confirm.media.removed=elt\u00e1vol\u00edtva
+confirm.correlatephotos.single=f\u00e9nyk\u00e9p megfeleltetve
+confirm.correlatephotos.multi=f\u00e9nyk\u00e9p megfeleltetve
+confirm.createpoint=pont l\u00e9trehozva
+confirm.rotatephoto=f\u00e9nyk\u00e9p elforgatva
+confirm.running=Futtat\u00e1s...
+confirm.lookupsrtm1=
+confirm.lookupsrtm2=magass\u00e1gi \u00e9rt\u00e9k tal\u00e1lhat\u00f3
+confirm.deletefieldvalues=Mez\u0151 \u00e9rt\u00e9kei t\u00f6r\u00f6lve
+confirm.audioload=Hangf\u00e1jl hozz\u00e1adva
+confirm.correlateaudios.single=hangf\u00e1jl megfeleltetve
+confirm.correlateaudios.multi=hangf\u00e1jl megfeleltetve
+
+# Buttons
+button.ok=OK
+button.back=El\u0151z\u0151
+button.next=K\u00f6vetkez\u0151
+button.finish=K\u00e9sz
+button.cancel=M\u00e9gse
+button.overwrite=Fel\u00fcl\u00edr\u00e1s
+button.moveup=Mozgat\u00e1s feljebb
+button.movedown=Mozgat\u00e1s lejjebb
+button.showlines=Sorok megjelen\u00edt\u00e9se
+button.edit=Szerkeszt\u00e9s
+button.exit=Kil\u00e9p\u00e9s
+button.close=Bez\u00e1r\u00e1s
+button.continue=Folytat\u00e1s
+button.yes=Igen
+button.no=Nem
+button.yestoall=Igen, mindet
+button.notoall=Nem, egyiket se
+button.select=Kijel\u00f6l\u00e9s
+button.selectall=Mindent kijel\u00f6l
+button.selectnone=Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se
+button.preview=El\u0151n\u00e9zet
+button.load=Bet\u00f6lt\u00e9s
+button.upload=Felt\u00f6lt\u00e9s
+button.guessfields=Mez\u0151k kital\u00e1l\u00e1sa
+button.showwebpage=Weboldal megjelen\u00edt\u00e9se
+button.check=Ellen\u0151rz\u00e9s
+button.resettodefaults=Vissza\u00e1ll\u00edt\u00e1s az alap\u00e9rtelmezettre
+button.browse=B\u00f6ng\u00e9sz\u00e9s...
+button.addnew=\u00daj hozz\u00e1ad\u00e1sa
+button.delete=T\u00f6rl\u00e9s
+
+# File types
+filetype.txt=TXT f\u00e1jlok
+filetype.jpeg=JPG f\u00e1jlok
+filetype.kmlkmz=KML, KMZ f\u00e1jlok
+filetype.kml=KML f\u00e1jlok
+filetype.kmz=KMZ f\u00e1jlok
+filetype.gpx=GPX f\u00e1jlok
+filetype.pov=POV f\u00e1jlok
+filetype.svg=SVG f\u00e1jlok
+filetype.audio=MP3, OGG, WAV f\u00e1jlok
+
+# Display components
+display.nodata=Nincs adat bet\u00f6ltve
+display.noaltitudes=A nyomvonal nem tartalmaz magass\u00e1gi adatot
+display.notimestamps=A nyomvonal nem tartalmaz id\u0151b\u00e9lyegeket
+details.trackdetails=Nyomvonal r\u00e9szletei
+details.notrack=Nincs adat bet\u00f6ltve
+details.track.points=Pontok
+details.track.file=F\u00e1jl
+details.track.numfiles=F\u00e1jlok sz\u00e1ma
+details.pointdetails=Pont r\u00e9szletei
+details.index.selected=Jelenlegi:
+details.index.of=\u00f6sszesen:
+details.nopointselection=Nincs pont kijel\u00f6lve
+details.photofile=F\u00e9nyk\u00e9pf\u00e1jl
+details.norangeselection=Nincs tartom\u00e1ny kijel\u00f6lve
+details.rangedetails=Tartom\u00e1ny r\u00e9szletei
+details.range.selected=Kiv\u00e1lasztva:
+details.range.to=\u00f6sszesen:
+details.altitude.to=\u00f6sszesen:
+details.range.climb=Emelked\u00e9s
+details.range.descent=Lejt\u00e9s
+details.coordformat=Koordin\u00e1ta form\u00e1tuma
+details.distanceunits=T\u00e1vols\u00e1g egys\u00e9ge
+display.range.time.secs=mp
+display.range.time.mins=p
+display.range.time.hours=\u00f3
+display.range.time.days=n
+details.range.avespeed=\u00c1tlagsebess\u00e9g
+details.range.avemovingspeed=Mozg\u00e1si \u00e1tlag
+details.range.maxspeed=Maxim\u00e1lis sebess\u00e9g
+details.range.numsegments=Szakaszok sz\u00e1ma
+details.range.pace=Iram
+details.range.gradient=Szintk\u00fcl\u00f6nbs\u00e9g
+details.lists.waypoints=\u00datpontok
+details.lists.photos=F\u00e9nyk\u00e9pek
+details.lists.audio=Hang
+details.photodetails=F\u00e9nyk\u00e9p r\u00e9szletei
+details.nophoto=Nincs f\u00e9nyk\u00e9p kiv\u00e1lasztva
+details.photo.loading=Bet\u00f6lt\u00e9s
+details.media.connected=\u00d6sszekapcsolva
+details.audiodetails=Hang r\u00e9szletei
+details.noaudio=Nincs hang kiv\u00e1lasztva
+details.audio.file=Hangf\u00e1jl
+details.audio.playing=lej\u00e1tsz\u00e1s...
+map.overzoom=Nem \u00e9rhet\u0151 el t\u00e9rk\u00e9p ezen a nagy\u00edt\u00e1si szinten
+
+# Field names
+fieldname.latitude=Sz\u00e9less\u00e9g
+fieldname.longitude=Hossz\u00fas\u00e1g
+fieldname.altitude=Magass\u00e1g
+fieldname.timestamp=Id\u00f5
+fieldname.time=Id\u0151
+fieldname.waypointname=N\u00e9v
+fieldname.waypointtype=T\u00edpus
+fieldname.newsegment=Szakasz
+fieldname.custom=Egy\u00e9ni
+fieldname.prefix=Mez\u0151
+fieldname.distance=T\u00e1vols\u00e1g
+fieldname.movingdistance=Mozg\u00e1si t\u00e1vols\u00e1g
+fieldname.duration=Id\u0151tartam
+fieldname.speed=Sebess\u00e9g
+fieldname.verticalspeed=F\u00fcgg\u0151leges sebess\u00e9g
+
+# Measurement units
+units.original=Eredeti
+units.default=Alap\u00e9rtelmezett
+units.metres=m\u00e9ter
+units.metres.short=m
+units.feet=l\u00e1b
+units.feet.short=ft
+units.kilometres=kilom\u00e9ter
+units.kilometres.short=km
+units.kmh=km/h
+units.miles=m\u00e9rf\u00f6ld
+units.miles.short=mi
+units.mph=mph
+units.metrespersec=m/s
+units.feetpersec=ft/s
+units.hours=\u00f3ra
+units.degminsec=Sz\u00f6g-sz\u00f6gperc-sz\u00f6gm\u00e1sodperc
+units.degmin=Sz\u00f6g-sz\u00f6gperc
+units.deg=Sz\u00f6g
+units.iso8601=ISO 8601
+
+# External urls
+url.googlemaps=maps.google.hu
+wikipedia.lang=hu
+
+# Cardinals for 3d plots
+cardinal.n=\u00c9
+cardinal.s=D
+cardinal.e=K
+cardinal.w=Ny
+
+# Undo operations
+undo.load=adatok bet\u00f6lt\u00e9se
+undo.loadphotos=f\u00e9nyk\u00e9pek bet\u00f6lt\u00e9se
+undo.loadaudios=hangf\u00e1jlok bet\u00f6lt\u00e9se
+undo.editpoint=pont szerkeszt\u00e9se
+undo.deletepoint=pont t\u00f6rl\u00e9se
+undo.removephoto=f\u00e9nyk\u00e9p elt\u00e1vol\u00edt\u00e1sa
+undo.removeaudio=hangf\u00e1jl elt\u00e1vol\u00edt\u00e1sa
+undo.deleterange=tartom\u00e1ny t\u00f6rl\u00e9se
+undo.compress=nyomvonal t\u00f6m\u00f6r\u00edt\u00e9se
+undo.insert=pontok besz\u00far\u00e1sa
+undo.reverse=tartom\u00e1ny megford\u00edt\u00e1sa
+undo.mergetracksegments=nyomvonalszakaszok egyes\u00edt\u00e9se
+undo.addtimeoffset=id\u0151eltol\u00e1s hozz\u00e1ad\u00e1sa
+undo.addaltitudeoffset=magass\u00e1geltol\u00e1s hozz\u00e1ad\u00e1sa
+undo.rearrangewaypoints=\u00fatpontok \u00fajrarendez\u00e9se
+undo.cutandmove=kijel\u00f6l\u00e9s mozgat\u00e1sa
+undo.connect=\u00f6sszekapcsol\u00e1s
+undo.disconnect=lev\u00e1laszt\u00e1s
+undo.correlatephotos=f\u00e9nyk\u00e9pek megfeleltet\u00e9se
+undo.rearrangephotos=f\u00e9nyk\u00e9pek \u00fajrarendez\u00e9se
+undo.createpoint=pont l\u00e9trehoz\u00e1sa
+undo.rotatephoto=f\u00e9nyk\u00e9p forgat\u00e1sa
+undo.convertnamestotimes=nevek konvert\u00e1l\u00e1sa id\u0151v\u00e9
+undo.lookupsrtm=magass\u00e1gi adatok let\u00f6lt\u00e9se SRTM-r\u0151l
+undo.deletefieldvalues=mez\u0151 \u00e9rt\u00e9keinek t\u00f6rl\u00e9se
+undo.correlateaudios=hang megfeleltet\u00e9se
+
+# Error messages
+error.save.dialogtitle=Hiba az adatok ment\u00e9sekor
+error.save.nodata=Nincs mentend\u0151 adat
+error.save.failed=Az adatok f\u00e1jlba ment\u00e9se nem siker\u00fclt
+error.saveexif.filenotfound=F\u00e9nyk\u00e9pf\u00e1jl keres\u00e9se nem siker\u00fclt
+error.saveexif.cannotoverwrite1=A(z)
+error.saveexif.cannotoverwrite2=f\u00e9nyk\u00e9pf\u00e1jl csak olvashat\u00f3, \u00e9s nem \u00edrhat\u00f3 fel\u00fcl. \u00cdrjuk m\u00e1solatba?
+error.saveexif.failed1=
+error.saveexif.failed2=k\u00e9p ment\u00e9se nem siker\u00fclt
+error.saveexif.forced1=
+error.saveexif.forced2=k\u00e9p er\u00f6ltet\u00e9st ig\u00e9nyelt
+error.load.dialogtitle=Hiba az adatok bet\u00f6lt\u00e9sekor
+error.load.noread=A f\u00e1jl nem olvashat\u00f3
+error.load.nopoints=Nem tal\u00e1lhat\u00f3 koordin\u00e1tainform\u00e1ci\u00f3 a f\u00e1jlban
+error.load.unknownxml=Ismeretlen xml form\u00e1tum:
+error.load.noxmlinzip=Nem tal\u00e1lhat\u00f3 xml f\u00e1jl a zip f\u00e1jlon bel\u00fcl
+error.load.othererror=Hiba a f\u00e1jl olvas\u00e1sa sor\u00e1n:
+error.jpegload.dialogtitle=Hiba a k\u00e9pek bet\u00f6lt\u00e9sekor
+error.jpegload.nofilesfound=Nem tal\u00e1lhat\u00f3 f\u00e1jl
+error.jpegload.nojpegsfound=Nem tal\u00e1lhat\u00f3 jpeg f\u00e1jl
+error.jpegload.nogpsfound=Nem tal\u00e1lhat\u00f3 GPS inform\u00e1ci\u00f3
+error.jpegload.exifreadfailed=Az EXIF inform\u00e1ci\u00f3 olvas\u00e1sa nem siker\u00fclt. Nem olvasat\u00f3 EXIF inform\u00e1ci\u00f3\nbe\u00e9p\u00edtett vagy k\u00fcls\u0151 f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r n\u00e9lk\u00fcl.
+error.audioload.nofilesfound=Nem tal\u00e1lhat\u00f3 hangf\u00e1jl
+error.gpsload.unknown=Ismeretlen hiba
+error.undofailed.title=A visszavon\u00e1s nem siker\u00fclt
+error.undofailed.text=A m\u0171velet visszavon\u00e1sa nem siker\u00fclt
+error.function.noop.title=A funkci\u00f3 nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
+error.rearrange.noop=A pontok \u00fajrarendez\u00e9se nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
+error.function.notavailable.title=A funkci\u00f3 nem \u00e9rhet\u0151 el
+error.function.nojava3d=Ehhez a funkci\u00f3hoz a Java3d f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r sz\u00fcks\u00e9ges,\n amely a Sun.com webhelyr\u0151l \u00e9rhet\u0151 el.
+error.3d=Hiba t\u00f6rt\u00e9nt a 3d megjelen\u00edt\u00e9ssel
+error.readme.notfound=Az olvassel f\u00e1jl nem tal\u00e1lhat\u00f3
+error.osmimage.dialogtitle=Hiba a t\u00e9rk\u00e9p bet\u00f6lt\u00e9sekor
+error.osmimage.failed=A t\u00e9rk\u00e9p bet\u00f6lt\u00e9se nem siker\u00fclt. Ellen\u0151rizze az internetkapcsolatot.
+error.language.wrongfile=\u00dagy t\u0171nik, hogy a kiv\u00e1lasztott f\u00e1jl nem egy nyelvi f\u00e1jl a Prune-hoz
+error.convertnamestotimes.nonames=A nevek nem konvert\u00e1lhat\u00f3k id\u0151adatokk\u00e1
+error.lookupsrtm.nonefound=Nem \u00e9rhet\u0151 el magass\u00e1gi \u00e9rt\u00e9k ezekhez a pontokhoz
+error.lookupsrtm.nonerequired=Az \u00f6sszes pont m\u00e1r rendelkezik magass\u00e1gadatokkal, \u00edgy nincs mit keresni
+error.gpsies.uploadnotok=A gpsies szerver a k\u00f6vetkez\u0151 \u00fczenetet adta vissza
+error.gpsies.uploadfailed=A felt\u00f6lt\u00e9s nem siker\u00fclt a k\u00f6vetkez\u0151 hib\u00e1val
+error.playaudiofailed=A hangf\u00e1jl lej\u00e1tsz\u00e1sa nem siker\u00fclt
index 42e2b3762e090c46e6c12726817ee0120b085895..ec4bc255e678a54492e37ce5fb00999801161e98 100644 (file)
@@ -17,9 +17,9 @@ menu.range.all=Pilih semua
 menu.range.none=Tidak memilih
 menu.photo=Foto
 menu.photo.saveexif=Simpan ke Exif
-menu.photo.connect=Hubungkan ke titik
-menu.photo.disconnect=Putuskan dari titik
-menu.photo.delete=Menghapus foto
+function.connecttopoint=Hubungkan ke titik
+function.disconnectfrompoint=Putuskan dari titik
+function.removephoto=Menghapus foto
 menu.view=Lihat
 menu.settings=Pengaturan
 menu.view.browser=Peta di browser
@@ -105,7 +105,7 @@ details.pointdetails=Rincian titik
 details.nopointselection=Tidak ada titik
 details.norangeselection=Tidak ada jangkauan
 details.rangedetails=Rincian jangkauan
-details.waypointsphotos.photos=Foto
+details.lists.photos=Foto
 details.photodetails=Rincian foto
 details.nophoto=Tidak ada foto
 details.photo.loading=Membuka
index 6c07c73c323c8601d19f6d142115c7f8df68e9a8..bf19b955ce146563310ba03d27dafafbfb080dfc 100644 (file)
@@ -1,5 +1,5 @@
 # Text entries for the Prune application
-# Italian entries as extra
+# Italian entries thanks to josatoc
 
 # Menu entries
 menu.file=File
@@ -30,10 +30,8 @@ menu.point.editpoint=Edita Punto
 menu.point.deletepoint=Cancella Punto
 menu.photo=Foto
 menu.photo.saveexif=Salva su Exif
-menu.photo.connect=Collega al punto
-menu.photo.disconnect=Scollega dal punto
-menu.photo.delete=Rimuovi foto
-menu.view=Visualizza
+menu.audio=Audio
+menu.view=Vista
 menu.view.showsidebars=Mostra barre laterali
 menu.view.browser=Mappa sul browser
 menu.view.browser.google=Google maps
@@ -49,6 +47,7 @@ menu.map.zoomin=Zoom +
 menu.map.zoomout=Zoom -
 menu.map.zoomfull=Zoom tutto
 menu.map.newpoint=Crea nuovo punto
+menu.map.drawpoints=Crea serie di punti
 menu.map.connect=Aggancia ai punti
 menu.map.autopan=Autopan
 menu.map.showmap=Mostra sulla mappa
@@ -61,6 +60,7 @@ altkey.menu.range=S
 altkey.menu.point=P
 altkey.menu.view=V
 altkey.menu.photo=O
+altkey.menu.audio=U
 altkey.menu.settings=R
 altkey.menu.help=A
 
@@ -99,14 +99,27 @@ function.setpaths=Configura percorsi programmi
 function.getgpsies=Ottieni traccie da Gpsies
 function.uploadgpsies=Carica traccia su Gpsies
 function.lookupsrtm=Ottieni quote da SRTM
+function.getwikipedia=Ottieni informazioni da Wikipedia
+function.searchwikipedianames=Cerca il nome in Wikipedia
+function.downloadosm=Scarica dati OSM dell'area
 function.duplicatepoint=Duplica punto corrente in coda
 function.setcolours=Scegli colori
+function.setlinewidth=Scegli la lo spessore
 function.setlanguage=Scegli la lingua
+function.connecttopoint=Collega al punto
+function.disconnectfrompoint=Scollega dal punto
+function.removephoto=Rimuovi foto
 function.correlatephotos=Correla le foto
 function.rearrangephotos=Riordina foto
 function.rotatephotoleft=Ruota foto a sinistra
 function.rotatephotoright=Ruota foto a destra
+function.photopopup=Mostra la foto in un riquadro
 function.ignoreexifthumb=Ignora anteprima foto exif
+function.loadaudio=Aggiungi ripresa audio
+function.removeaudio=Rimuovi ripresa audio
+function.correlateaudios=Correla ripresa audio
+function.playaudio=Riproduci ripresa audio
+function.stopaudio=Ferma ripresa audio
 function.help=Aiuto
 function.showkeys=Visualizza tasti scelta rapida
 function.about=Informazioni su Prune
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Salva i tipi di punti seguenti:
 dialog.pointtype.track=Punti traccia
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Punti foto
+dialog.pointtype.audio=Punti audio
 dialog.pointtype.selection=Solo la selezione
 dialog.confirmreversetrack.title=Conferma l'inversione
 dialog.confirmreversetrack.text=Questa traccia contiene informazioni sull'orario di scatto che possono essere messe fuori sequenza dopo l'inversione.\nSei sicuro di voler invertire questa sezione?
@@ -278,12 +292,14 @@ dialog.gpsies.activity.motorbiking=Motocicletta
 dialog.gpsies.activity.snowshoe=Trekking sulla neve
 dialog.gpsies.activity.sailing=Navigazione
 dialog.gpsies.activity.skating=Pattinaggio
+dialog.wikipedia.column.name=Titolo articolo
+dialog.wikipedia.column.distance=Distanza
 dialog.correlate.notimestamps=Non ci sono informazioni temporali tra i dati dei punti, non c'\u00e8 niente per collegarli con le foto.
 dialog.correlate.nouncorrelatedphotos=Non ci sono foto non correlate.\nSei sicuro di voler continuare?
 dialog.correlate.photoselect.intro=Selezione una delle foto correlate da usare come scarto dell'orario
-dialog.correlate.photoselect.photoname=Nome della foto
-dialog.correlate.photoselect.timediff=Differenza di orario
-dialog.correlate.photoselect.photolater=Foto scattata dopo il punto
+dialog.correlate.select.photoname=Nome della foto
+dialog.correlate.select.timediff=Differenza di orario
+dialog.correlate.select.photolater=Foto scattata dopo il punto
 dialog.correlate.options.tip=Consiglio: Con il collegamento manuale di almeno una foto, lo scarto di orario viene calcolato per te
 dialog.correlate.options.intro=Selezione le opzioni per la correlazione automatica
 dialog.correlate.options.offsetpanel=Scarto di orario
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=ore,
 dialog.correlate.options.offset.minutes=minuti e
 dialog.correlate.options.offset.seconds=secondi
 dialog.correlate.options.photolater=Foto scattata dopo il punto
-dialog.correlate.options.pointlater=Punto successivo allo scatto della foto
+dialog.correlate.options.pointlaterphoto=Punto successivo allo scatto della foto
+dialog.correlate.options.audiolater=Ripresa audio dopo il punto
+dialog.correlate.options.pointlateraudio=Punto successivo alla ripresa audio
 dialog.correlate.options.limitspanel=Limiti di correlamento
 dialog.correlate.options.notimelimit=Nessun limite di tempo
 dialog.correlate.options.timelimit=Limite di tempo
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Nessun limite di distanza
 dialog.correlate.options.distancelimit=Distanza limite
 dialog.correlate.options.correlate=Correlate
 dialog.correlate.alloutsiderange=Tutte le foto sono fuori dall'orario della traccia, e nessuna pu\u00f2 essere correlata.\nProva a cambiare lo scarto o correla manualmente almeno una foto.
+dialog.correlate.filetimes=I dati temporali mostrano:
+dialog.correlate.filetimes2=della ripresa audio
+dialog.correlate.correltimes=Per la correlazione usa:
+dialog.correlate.timestamp.beginning=Inizio
+dialog.correlate.timestamp.middle=Met\u00e0
+dialog.correlate.timestamp.end=Fine
+dialog.correlate.audioselect.intro=Seleziona una di queste riprese audio correlate come scarto temporale
+dialog.correlate.select.audioname=Nome ripresa audio
+dialog.correlate.select.audiolater=Ripresa audio successiva
 dialog.rearrangephotos.desc=Seleziona la destinazione e l'ordine dei punti foto
 dialog.rearrangephotos.tostart=Sposta all'inizio
 dialog.rearrangephotos.toend=Sposta alla fine
@@ -380,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Cache delle mappe
 dialog.saveconfig.prune.kmzimagewidth=larghezza immagine KMZ
 dialog.saveconfig.prune.kmzimageheight=altezza immagine KMZ
 dialog.saveconfig.prune.colourscheme=Schema colori
+dialog.saveconfig.prune.linewidth=Spessore linea
 dialog.saveconfig.prune.kmltrackcolour=Colore della traccia KML
 dialog.setpaths.intro=Se necessario, puoi indicare il percorso delle applicazioni esterne:
 dialog.setpaths.found=trovato?
@@ -409,10 +437,12 @@ dialog.diskcache.dir=Cartella della cache
 dialog.diskcache.createdir=Crea cartella
 dialog.diskcache.nocreate=Cartella della cache non creata
 dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
+dialog.setlinewidth.text=Specifica il tratteggio delle linee per disegnare la traccia (1-4)
+dialog.downloadosm.desc=Conferma lo scarico dei dati raw OSM per l'area specificata:
+dialog.searchwikipedianames.search=Cerca per:
 
 # 3d window
 dialog.3d.title=Visione Prune in 3D
-dialog.3d.altitudecap=Intervallo altitudine minimo
 dialog.3d.altitudefactor=Fattore di moltiplicazione della quota
 dialog.3dlines.title=Griglia di Prune
 dialog.3dlines.empty=Nessuna griglia mostrata!
@@ -439,16 +469,21 @@ confirm.undo.single=operazione annullate
 confirm.undo.multi=operazioni annullate
 confirm.jpegload.single=foto \u00e8 stata aggiunta
 confirm.jpegload.multi=foto sono state aggiunte
-confirm.photo.connect=foto collegata
+confirm.media.connect=media collegata
 confirm.photo.disconnect=foto scollegata
-confirm.correlate.single=foto era correlata
-confirm.correlate.multi=foto erano correlate
+confirm.audio.disconnect=ripresa audio scollegata
+confirm.media.removed=rimosso
+confirm.correlatephotos.single=foto era correlata
+confirm.correlatephotos.multi=foto erano correlate
 confirm.createpoint=punto creato
 confirm.rotatephoto=foto ruotata
 confirm.running=Operazione in corso...
 confirm.lookupsrtm1=Trovato
 confirm.lookupsrtm2=valori di quota
 confirm.deletefieldvalues=Valori del campo cancellati
+confirm.audioload=Ripresa audio aggiunta
+confirm.correlateaudios.single=la ripresa audio era correlata
+confirm.correlateaudios.multi=le riprese audio erano correlate
 
 # Buttons
 button.ok=OK
@@ -491,6 +526,7 @@ filetype.kmz=File KMZ
 filetype.gpx=File GPX
 filetype.pov=File POV
 filetype.svg=File SVG
+filetype.audio=File MP3, OGG, WAV
 
 # Display components
 display.nodata=Nessun dato caricato
@@ -525,12 +561,17 @@ details.range.maxspeed=Velocit\u00e0 massima
 details.range.numsegments=Numero di segmenti
 details.range.pace=Passo
 details.range.gradient=Gradiente
-details.waypointsphotos.waypoints=Waypoint
-details.waypointsphotos.photos=Foto
+details.lists.waypoints=Waypoint
+details.lists.photos=Foto
+details.lists.audio=Ripresa audio
 details.photodetails=Dettagli foto
 details.nophoto=Nessuna foto selezionata
 details.photo.loading=Caricamento
-details.photo.connected=Collegata
+details.media.connected=Collegata
+details.audiodetails=Dettagli ripresa audio
+details.noaudio=Nessuna ripresa audio selezionata
+details.audio.file=Ripresa audio
+details.audio.playing=Riproduzione...
 map.overzoom=Mappa non disponibile a questo livello di zoom
 
 # Field names
@@ -573,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.it
+wikipedia.lang=it
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -583,9 +625,11 @@ cardinal.w=O
 # Undo operations
 undo.load=carica dati
 undo.loadphotos=carica foto
+undo.loadaudios=carica riprese audio
 undo.editpoint=edita punto
 undo.deletepoint=cancella punto
-undo.deletephoto=rimuovi foto
+undo.removephoto=rimuovi foto
+undo.removeaudio=rimuovi riprese audio
 undo.deleterange=cancella l'intervallo
 undo.compress=comprimi traccia
 undo.insert=inserisci punti
@@ -595,15 +639,16 @@ undo.addtimeoffset=aggiungi scarto temporale
 undo.addaltitudeoffset=aggiungi scarto altitudine
 undo.rearrangewaypoints=riorganizza waypoint
 undo.cutandmove=muovi selezione
-undo.connectphoto=collega foto
-undo.disconnectphoto=scollega foto
-undo.correlate=correla foto
+undo.connect=collega
+undo.disconnect=scollega
+undo.correlatephotos=correla foto
 undo.rearrangephotos=riorganizza foto
 undo.createpoint=crea punto
 undo.rotatephoto=ruota foto
 undo.convertnamestotimes=converti nomi in orari
 undo.lookupsrtm=cerca quote in SRTM
 undo.deletefieldvalues=cancellare i valori del campo
+undo.correlateaudios=correla riprese audio
 
 # Error messages
 error.save.dialogtitle=Errore nel salvataggio dati
@@ -625,9 +670,9 @@ error.load.othererror=Errore nella lettura del file:
 error.jpegload.dialogtitle=Errore nel caricamento delle foto
 error.jpegload.nofilesfound=File non trovato
 error.jpegload.nojpegsfound=File jpeg non trovato
-error.jpegload.noexiffound=Informazioni EXIF non trovate
 error.jpegload.nogpsfound=Informazioni GPS non trovate
 error.jpegload.exifreadfailed=Lettera dei dati EXIF fallita. I dati EXIF non possono\n essere letti senza una libreria interna o esterna.
+error.audioload.nofilesfound=Riprese audio non trovate
 error.gpsload.unknown=Errore sconosciuto
 error.undofailed.title=Impossibile annullare
 error.undofailed.text=Impossibile annullare l'operazione
@@ -645,3 +690,4 @@ error.lookupsrtm.nonefound=Valori di quota non trovati
 error.lookupsrtm.nonerequired=Tutti i punti hanno gi\u00e0 una quota, non c'\u00e8 niente da cercare
 error.gpsies.uploadnotok=Il server Gpsies ha riportato il messaggio
 error.gpsies.uploadfailed=Il caricamento \u00e8 fallito con l'errore
+error.playaudiofailed=Ripresa audio non riprodotta
index 0c0d5eb4243b1e1fe94a638d5852279e2b67e491..c6b165320aeca9f3afc4799fd13a89cb9692a302 100644 (file)
@@ -30,9 +30,9 @@ menu.range.start=\u958b\u59cb\u70b9\u3092\u7f6e\u304f
 menu.range.end=\u7d42\u4e86\u70b9\u3092\u7f6e\u304f
 menu.photo=\u5199\u771f
 menu.photo.saveexif=Exif\u306b\u4fdd\u5b58
-menu.photo.connect=\u70b9\u306b\u63a5\u7d9a
-menu.photo.disconnect=\u70b9\u304b\u3089\u63a5\u7d9a\u89e3\u9664
-menu.photo.delete=\u5199\u771f\u3092\u53d6\u308a\u9664\u304f
+function.connecttopoint=\u70b9\u306b\u63a5\u7d9a
+function.disconnectfrompoint=\u70b9\u304b\u3089\u63a5\u7d9a\u89e3\u9664
+function.removephoto=\u5199\u771f\u3092\u53d6\u308a\u9664\u304f
 menu.view=\u30d3\u30e5\u30fc
 menu.view.browser=\u5730\u56f3\u3092\u30d6\u30e9\u30a6\u30b6\u30fc\u3067\u898b\u308b
 menu.view.browser.google=Google \u30de\u30c3\u30d7
@@ -240,9 +240,9 @@ dialog.gpsies.activity.skating=\u30d5\u30a3\u30ae\u30e5\u30a2\u30b9\u30b1\u30fc\
 dialog.correlate.notimestamps=\u30c7\u30fc\u30bf\u30dd\u30a4\u30f3\u30c8\u306b\u306f\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7\u304c\u306a\u3044\u306e\u3067\u3001\u5199\u771f\u3092\u95a2\u9023\u4ed8\u3051\u3089\u308c\u308b\u7269\u304c\u3042\u308a\u307e\u305b\u3093\u3002
 dialog.correlate.nouncorrelatedphotos=\u95a2\u9023\u4ed8\u3051\u3089\u308c\u306a\u304b\u3063\u305f\u5199\u771f\u306f\u3042\u308a\u307e\u305b\u3093\u3002\n\u7d9a\u3051\u307e\u3059\u304b\uff1f
 dialog.correlate.photoselect.intro=\u6642\u9593\u504f\u4f4d\u3092\u4f5c\u308b\u305f\u3081\u306e\u5199\u771f\u3092\u4e00\u3064\u9078\u3093\u3067\u304f\u3060\u3055\u3044\u3002
-dialog.correlate.photoselect.photoname=\u5199\u771f\u540d
-dialog.correlate.photoselect.timediff=\u6642\u9593\u5dee
-dialog.correlate.photoselect.photolater=\u5199\u771f\u304c\u5f8c
+dialog.correlate.select.photoname=\u5199\u771f\u540d
+dialog.correlate.select.timediff=\u6642\u9593\u5dee
+dialog.correlate.select.photolater=\u5199\u771f\u304c\u5f8c
 dialog.correlate.options.tip=\u63d0\u793a\uff1a\u5c11\u306a\u304f\u3068\u3082\u4e00\u3064\u306e\u5199\u771f\u3092\u624b\u52d5\u3067\u95a2\u9023\u4ed8\u3051\u308b\u3068\u3001\u6642\u9593\u504f\u4f4d\u3092\u8a08\u7b97\u3059\u308b\u4e8b\u304c\u3067\u304d\u307e\u3059\u3002
 dialog.correlate.options.intro=\u81ea\u52d5\u95a2\u9023\u4ed8\u3051\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.correlate.options.offsetpanel=\u6642\u9593\u504f\u4f4d
@@ -251,7 +251,7 @@ dialog.correlate.options.offset.hours=\u6642\u9593
 dialog.correlate.options.offset.minutes=\u5206
 dialog.correlate.options.offset.seconds=\u79d2
 dialog.correlate.options.photolater=\u3053\u306e\u70b9\u3088\u308a\u5f8c\u308d\u306e\u5199\u771f
-dialog.correlate.options.pointlater=\u5199\u771f\u3088\u308a\u5f8c\u308d\u306e\u70b9
+dialog.correlate.options.pointlaterphoto=\u5199\u771f\u3088\u308a\u5f8c\u308d\u306e\u70b9
 dialog.correlate.options.limitspanel=\u95a2\u9023\u4ed8\u3051\u5236\u9650
 dialog.correlate.options.notimelimit=\u6642\u9593\u5236\u9650\u306a\u3057
 dialog.correlate.options.timelimit=\u6642\u9593\u5236\u9650
@@ -356,7 +356,6 @@ dialog.setlanguage.endmessage=\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u3001\u8a00\u
 
 # 3d window
 dialog.3d.title=Prune 3D \u8868\u793a
-dialog.3d.altitudecap=\u6700\u4f4e\u9ad8\u5ea6\u7bc4\u56f2
 dialog.3dlines.title=Prune \u683c\u5b50\u7dda
 dialog.3dlines.empty=\u683c\u5b50\u7dda\u304c\u8868\u793a\u3055\u308c\u307e\u305b\u3093
 dialog.3dlines.intro=\u3053\u308c\u3089\u304c 3D \u8868\u793a\u7528\u306e\u683c\u5b50\u7dda\u3067\u3059\u3002
@@ -382,10 +381,9 @@ confirm.undo.single=\u64cd\u4f5c\u306f\u30a2\u30f3\u30c9\u30a5\u3055\u308c\u305f
 confirm.undo.multi=\u64cd\u4f5c\u306f\u30a2\u30f3\u30c9\u30a5\u3055\u308c\u305f
 confirm.jpegload.single=\u5199\u771f\u304c\u52a0\u3048\u3089\u308c\u305f
 confirm.jpegload.multi=\u5199\u771f\u304c\u52a0\u3048\u3089\u308c\u305f
-confirm.photo.connect=\u5199\u771f\u304c\u63a5\u7d9a\u3055\u308c\u305f
 confirm.photo.disconnect=\u5199\u771f\u304c\u63a5\u7d9a\u3055\u308c\u305f
-confirm.correlate.single=\u5199\u771f\u304c\u95a2\u9023\u4ed8\u3051\u3089\u308c\u305f
-confirm.correlate.multi=\u5199\u771f\u304c\u95a2\u9023\u4ed8\u3051\u3089\u308c\u305f
+confirm.correlatephotos.single=\u5199\u771f\u304c\u95a2\u9023\u4ed8\u3051\u3089\u308c\u305f
+confirm.correlatephotos.multi=\u5199\u771f\u304c\u95a2\u9023\u4ed8\u3051\u3089\u308c\u305f
 confirm.createpoint=\u70b9\u304c\u4f5c\u3089\u308c\u305f
 confirm.rotatephoto=\u5199\u771f\u3092\u56de\u8ee2\u3057\u305f
 confirm.running=\u5b9f\u884c\u4e2d...
@@ -460,12 +458,12 @@ details.range.avemovingspeed=\u5e73\u5747\u79fb\u52d5
 details.range.numsegments=\u30bb\u30b0\u30e1\u30f3\u30c8\u6570
 details.range.pace=\u30da\u30fc\u30b9
 details.range.gradient=\u52fe\u914d
-details.waypointsphotos.waypoints=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8
-details.waypointsphotos.photos=\u5199\u771f
+details.lists.waypoints=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8
+details.lists.photos=\u5199\u771f
 details.photodetails=\u5199\u771f\u8a73\u7d30
 details.nophoto=\u5199\u771f\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093
 details.photo.loading=\u8aad\u307f\u8fbc\u307f\u4e2d
-details.photo.connected=\u63a5\u7d9a\u6e08
+details.media.connected=\u63a5\u7d9a\u6e08
 map.overzoom=\u3053\u306e\u30ba\u30fc\u30e0\u30ec\u30d9\u30eb\u3067\u306f\u5730\u56f3\u304c\u5165\u624b\u3067\u304d\u307e\u305b\u3093\u3002
 
 # Field names
@@ -508,6 +506,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.co.jp
+wikipedia.lang=ja
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -520,7 +519,7 @@ undo.load=\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u307f
 undo.loadphotos=\u5199\u771f\u306e\u8aad\u307f\u8fbc\u307f
 undo.editpoint=\u70b9\u306e\u7de8\u96c6
 undo.deletepoint=\u70b9\u306e\u524a\u9664
-undo.deletephoto=\u5199\u771f\u306e\u53d6\u308a\u9664\u304d
+undo.removephoto=\u5199\u771f\u306e\u53d6\u308a\u9664\u304d
 undo.deleterange=\u7bc4\u56f2\u306e\u524a\u9664
 undo.compress=\u30c8\u30e9\u30c3\u30af\u306e\u5727\u7e2e
 undo.insert=\u70b9\u306e\u633f\u5165
@@ -530,9 +529,9 @@ undo.addtimeoffset=\u6642\u9593\u504f\u4f4d\u3092\u52a0\u3048\u308b
 undo.addaltitudeoffset=\u9ad8\u5ea6\u504f\u4f4d\u3092\u52a0\u3048\u308b
 undo.rearrangewaypoints=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u3092\u633f\u5165
 undo.cutandmove=\u30bb\u30af\u30b7\u30e7\u30f3\u306e\u79fb\u52d5
-undo.connectphoto=\u5199\u771f\u306e\u63a5\u7d9a
-undo.disconnectphoto=\u5199\u771f\u306e\u63a5\u7d9a\u89e3\u9664
-undo.correlate=\u5199\u771f\u306e\u95a2\u9023\u4ed8\u3051
+undo.connect=\u5199\u771f\u306e\u63a5\u7d9a
+undo.disconnect=\u5199\u771f\u306e\u63a5\u7d9a\u89e3\u9664
+undo.correlatephotos=\u5199\u771f\u306e\u95a2\u9023\u4ed8\u3051
 undo.rearrangephotos=\u5199\u771f\u306e\u4e26\u3079\u66ff\u3048
 undo.createpoint=\u70b9\u306e\u4f5c\u6210
 undo.rotatephoto=\u5199\u771f\u306e\u56de\u8ee2
@@ -558,7 +557,6 @@ error.load.othererror=\u30d5\u30a1\u30a4\u30eb\u3092\u8aad\u307f\u8fbc\u307f\u30
 error.jpegload.dialogtitle=\u5199\u771f\u306e\u8aad\u307f\u8fbc\u307f\u30a8\u30e9\u30fc:
 error.jpegload.nofilesfound=\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
 error.jpegload.nojpegsfound=Jpeg\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
-error.jpegload.noexiffound=EXIF\u60c5\u5831\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
 error.jpegload.nogpsfound=GPS\u60c5\u5831\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
 error.gpsload.unknown=\u4e0d\u660e\u306a\u30a8\u30e9\u30fc
 error.undofailed.title=\u30a2\u30f3\u30c9\u30a5\u5931\u6557
diff --git a/tim/prune/lang/prune-texts_ko.properties b/tim/prune/lang/prune-texts_ko.properties
new file mode 100644 (file)
index 0000000..eb4ef92
--- /dev/null
@@ -0,0 +1,693 @@
+# Text entries for the Prune application
+# Korean entries thanks to HooAU
+
+# Menu entries
+menu.file=\ud30c\uc77c
+menu.file.addphotos=\uc0ac\uc9c4 \ucd94\uac00\ud558\uae30
+menu.file.save=\ud0dd\uc2a4\ud2b8\ub85c \uc800\uc7a5\ud558\uae30
+menu.file.exit=\uc885\ub8cc
+menu.track=\ud2b8\ub799
+menu.track.undo=\uc2e4\ud589\ucde8\uc18c
+menu.track.clearundo=\uc2e4\ud589\ucde8\uc18c \ubaa9\ub85d \uc0ad\uc81c
+menu.track.deletemarked=\ud45c\uc2dc\ub41c \uc9c0\uc810 \uc9c0\uc6b0\uae30
+menu.track.rearrange=\uacbd\uc720\uc9c0 \uc7ac\uc815\ub82c
+menu.track.rearrange.start=\uc2dc\uc791 \uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
+menu.track.rearrange.end=\ub05d \uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
+menu.track.rearrange.nearest=\uac00\uae4c\uc6b4 \ud2b8\ub799\uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
+menu.range=\uc5f0\uacb0\uc120
+menu.range.all=\ubaa8\ub450 \uc120\ud0dd
+menu.range.none=\uc120\ud0dd\ud558\uc9c0 \uc54a\uae30
+menu.range.start=\uc2dc\uc791 \uc9c0\uc810\uc73c\ub85c \uc9c0\uc815
+menu.range.end=\ub05d\uc9c0\uc810\uc73c\ub85c \uc9c0\uc815
+menu.range.deleterange=\uc5f0\uacb0\uc120 \uc0ad\uc81c
+menu.range.interpolate=\uc911\uac04\uc5d0 \ub123\uae30
+menu.range.average=\ud3c9\uade0\uc9c0\uc810\uc120\ud0dd
+menu.range.reverse=\ucc98\uc74c\uacfc \ub05d \ubc14\uafb8\uae30
+menu.range.mergetracksegments=\ud2b8\ub799 \ubd80\ubd84 \ubcd1\ud569
+menu.range.cutandmove=\uc790\ub974\uace0 \uc62e\uae30\uae30
+menu.point=\uc9c0\uc810
+menu.point.editpoint=\uc9c0\uc810 \uc218\uc815
+menu.point.deletepoint=\uc9c0\uc810 \uc0ad\uc81c
+menu.photo=\uc0ac\uc9c4
+menu.photo.saveexif=Exif\ub85c \uc800\uc7a5
+menu.audio=\uc18c\ub9ac
+menu.view=\ubcf4\uae30
+menu.view.showsidebars=\uc0ac\uc774\ub4dc\ubc14 \ubcf4\uae30
+menu.view.browser=\uc6f9\ube0c\ub77c\uc6b0\uc800\ub85c \uc9c0\ub3c4\ubcf4\uae30
+menu.view.browser.google=\uad6c\uae00 \uc9c0\ub3c4
+menu.view.browser.openstreetmap=\uc624\ud508\uc2a4\ud2b8\ub9ac\ud2b8\ub9f5
+menu.view.browser.mapquest=\ub9f5\ud018\uc2a4\ud2b8
+menu.view.browser.yahoo=\uc57c\ud6c4 \uc9c0\ub3c4
+menu.view.browser.bing=\ube59 \uc9c0\ub3c4
+menu.settings=\uc124\uc815
+menu.settings.onlinemode=\uc778\ud130\ub137\uc5d0\uc11c \uc9c0\ub3c4 \ubd88\ub7ec\uc624\uae30
+menu.help=\ub3c4\uc6c0\ub9d0
+# Popup menu for map
+menu.map.zoomin=\ud655\ub300
+menu.map.zoomout=\ucd95\uc18c
+menu.map.zoomfull=\uc804\uccb4\ud06c\uae30 \ubcf4\uae30
+menu.map.newpoint=\uc0c8 \uc9c0\uc810 \uc0dd\uc131
+menu.map.drawpoints=\uc0c8 \uc9c0\uc810\ub4e4 \uc0dd\uc131
+menu.map.connect=\ud2b8\ub809 \uc9c0\uc810 \uc5f0\uacb0\ud558\uae30
+menu.map.autopan=\uc790\ub3d9 \uc2dc\uc810 \ucd94\uc801
+menu.map.showmap=\uc9c0\ub3c4\ubcf4\uae30
+menu.map.showscalebar=\ucd95\uc801 \uc870\uc808\ubc14 \ubcf4\uae30
+
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=F
+altkey.menu.range=R
+altkey.menu.point=P
+altkey.menu.view=V
+altkey.menu.photo=O
+altkey.menu.audio=A
+altkey.menu.settings=S
+altkey.menu.help=H
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=O
+shortcut.menu.file.load=L
+shortcut.menu.file.save=S
+shortcut.menu.track.undo=Z
+shortcut.menu.edit.compress=C
+shortcut.menu.range.all=A
+shortcut.menu.help.help=H
+
+# Functions
+function.open=\ud30c\uc77c \uc5f4\uae30
+function.loadfromgps=GPS\uc5d0\uc11c \uc790\ub8cc \uac00\uc838\uc624\uae30
+function.sendtogps=GPS\ub85c \uc790\ub8cc \ubcf4\ub0b4\uae30
+function.exportkml=KML \ub0b4\ubcf4\ub0b4\uae30
+function.exportgpx=GPX \ub0b4\ubcf4\ub0b4\uae30
+function.exportpov=POV \ub0b4\ubcf4\ub0b4\uae30
+function.exportsvg=SVG \ub0b4\ubcf4\ub0b4\uae30
+function.editwaypointname=\uacbd\uc720\uc9c0 \uc774\ub984 \uc218\uc815
+function.compress=\uacbd\ub85c \uc555\ucd95\ud558\uae30
+function.addtimeoffset=\uc624\ud504\uc14b \uc2dc\uac04 \ucd94\uac00
+function.addaltitudeoffset=\uc624\ud504\uc14b \uace0\ub3c4 \ucd94\uac00
+function.convertnamestotimes=\uacbd\uc720\uc9c0 \uc774\ub984\uc744 \uc2dc\uac04\uc73c\ub85c \ubcc0\ud658
+function.deletefieldvalues=\ud544\ub4dc\uac12 \uc0ad\uc81c
+function.findwaypoint=\uacbd\uc720\uc9c0 \ucc3e\uae30
+function.pastecoordinates=\uc0c8 \uc88c\ud45c \ub123\uae30
+function.charts=\ucc28\ud2b8
+function.show3d=3\ucc28\uc6d0 \ubcf4\uae30
+function.distances=\uac70\ub9ac
+function.fullrangedetails=\uc5f0\uacb0\uc120 \uc0c1\uc138 \uc815\ubcf4 \ubcf4\uae30
+function.setmapbg=\ubc30\uacbd \uc9c0\ub3c4 \uc9c0\uc815
+function.setkmzimagesize=KMZ \uadf8\ub9bc \ud06c\uae30 \uc9c0\uc815
+function.setpaths=\uc678\ubd80\ud504\ub85c\uadf8\ub7a8 \uc9c0\uc815
+function.getgpsies=gpsies\uc5d0\uc11c \ud2b8\ub799\ubaa9\ub85d \uc5bb\uae30
+function.uploadgpsies=gpsies\ub85c \ud2b8\ub799 \uc62c\ub9ac\uae30
+function.lookupsrtm=SRTM\uc5d0\uc11c \uace0\ub3c4 \ucc3e\uae30
+function.getwikipedia=\uc704\ud0a4\ud53c\ub514\uc544\uc5d0\uc11c \uadfc\ucc98 \uc815\ubcf4 \ucc3e\uae30
+function.searchwikipedianames=\uc774\ub984\uc73c\ub85c \uc704\ud0a4\ud53c\ub514\uc544 \uac80\uc0c9
+function.downloadosm=OSM \ub370\uc774\ud0c0 \ub2e4\uc6b4\ub85c\ub4dc\ud558\uae30
+function.duplicatepoint=\uc9c0\uc810 \ubcf5\uc0ac\ud558\uae30
+function.setcolours=\uc0c9\uc0c1 \uc9c0\uc815
+function.setlinewidth=\uc120 \ub113\uc774 \uc9c0\uc815
+function.setlanguage=\uc5b8\uc5b4 \uc9c0\uc815
+function.connecttopoint=\uc9c0\uc810\uc73c\ub85c \uc5f0\uacb0
+function.disconnectfrompoint=\uc9c0\uc810\uc5d0\uc11c \uc5f0\uacb0 \ub04a\uae30
+function.removephoto=\ub9ac\uc2a4\ud2b8\uc5d0\uc11c \uc0ac\uc9c4 \uc9c0\uc6b0\uae30
+function.correlatephotos=\uc2dc\uac04\uc815\ubcf4\ub85c \uc0ac\uc9c4 \uc5f0\uacb0\ud558\uae30
+function.rearrangephotos=\uc0ac\uc9c4 \uc7ac\ubc30\uc5f4\ud558\uae30
+function.rotatephotoleft=\uc67c\ucabd\uc73c\ub85c \uc0ac\uc9c4 \ud68c\uc804
+function.rotatephotoright=\uc624\ub978\ucabd\uc73c\ub85c \uc0ac\uc9c4 \ud68c\uc804
+function.photopopup=\ud31d\uc5c5 \uc0ac\uc9c4 \ubcf4\uae30
+function.ignoreexifthumb=exif \uc378\ub124\uc77c \ubb34\uc2dc\ud558\uae30
+function.loadaudio=\uc18c\ub9ac\ud30c\uc77c \ucd94\uac00\ud558\uae30
+function.removeaudio=\ub9ac\uc2a4\ud2b8\uc5d0\uc11c \uc18c\ub9ac\ud30c\uc77c \uc9c0\uc6b0\uae30
+function.correlateaudios=\uc2dc\uac04\uc815\ubcf4\ub85c \uc18c\ub9ac\ud30c\uc77c \uc5f0\uacb0\ud558\uae30
+function.playaudio=\uc18c\ub9ac\ud30c\uc77c \uc7ac\uc0dd
+function.stopaudio=\uc18c\ub9ac\ud30c\uc77c \uba48\ucda4
+function.help=\ub3c4\uc6c0\ub9d0
+function.showkeys=\ub2e8\ucd95\ud0a4 \ubcf4\uae30
+function.about=Prune \uc815\ubcf4
+function.checkversion=\uc0c8\ubc84\uc804\uc774 \uc788\ub294\uc9c0 \uac80\uc0ac
+function.saveconfig=\uc124\uc815 \uc800\uc7a5\ud558\uae30
+function.diskcache=\ub514\uc2a4\ud06c\uc5d0 \ub9f5 \uc800\uc7a5
+
+# Dialogs
+dialog.exit.confirm.title=Prune \uc885\ub8cc
+dialog.exit.confirm.text=\ub370\uc774\ud0c0\uac00 \uc800\uc7a5\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uadf8\ub798\ub3c4 \ub098\uac00\uc2dc\uac8c\uc2b5\ub2c8\uae4c?
+dialog.openappend.title=\ub370\uc774\ud130 \ucd94\uac00\ud558\uae30
+dialog.openappend.text=\uc774\uc804\uc5d0 \ubd88\ub7ec\uc628 \ub370\uc774\ud0c0\ub85c \ucd94\uac00\ud558\uae30\uaca0\uc2b5\ub2c8\uae4c?
+dialog.deletepoint.title=\uc9c0\uc810 \uc0ad\uc81c
+dialog.deletepoint.deletephoto=\uc9c0\uc810\uc73c\ub85c \ucca8\ubd80\ub41c \uc0ac\uc9c4\uc744 \uc9c0\uc6b0\uae30\uaca0\uc2b5\ub2c8\uae4c?
+dialog.deletephoto.title=\uc0ac\uc9c4 \uc0ad\uc81c
+dialog.deletephoto.deletepoint=\uc0ac\uc9c4\uc5d0 \ucca8\ubd80\ub41c \uc9c0\uc810\uc744 \uc9c0\uc6b0\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.openoptions.title=\uc124\uc815
+dialog.openoptions.filesnippet=\ud30c\uc77c\uc5d0\uc11c \ucd94\ucd9c\ud558\uae30
+dialog.load.table.field=\ud544\ub4dc
+dialog.load.table.datatype=\ub370\uc774\ud0c0 \ud0c0\uc785
+dialog.load.table.description=\uc124\uba85
+dialog.delimiter.label=\ud544\ub4dc \uad6c\ubd84 \uae30\ud638
+dialog.delimiter.comma=\ucf64\ub9c8 ,
+dialog.delimiter.tab=\ud0ed
+dialog.delimiter.space=\ube48\uce78
+dialog.delimiter.semicolon=\uc138\ubbf8\ucf5c\ub860 ;
+dialog.delimiter.other=\uae30\ud0c0
+dialog.openoptions.deliminfo.records=\uac1c\uc758 \ub808\ucf54\ub4dc
+dialog.openoptions.deliminfo.fields=\uac1c\uc758 \ud544\ub4dc\uac00 \uc788\ub294
+dialog.openoptions.deliminfo.norecords=\ub808\ucf54\ub4dc\uac00 \uc5c6\uc74c
+dialog.openoptions.altitudeunits=\uace0\ub3c4 \ub2e8\uc704
+dialog.open.contentsdoubled=\uc774 \ud30c\uc77c\uc740 \uac01 \uc9c0\uc810\uc774 2\ubc88\uc529 \uae30\ub85d\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. /n\uac01 \uc9c0\uc810\uacfc \uac01 \uacbd\uc720\uc9c0\ub294 \ud55c\ubc88\uc529\ub9cc \uae30\ub85d\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4.
+dialog.selecttracks.intro=\ubd88\ub7ec\uc62c \uc9c0\uc810\uc774\ub098 \uc9c0\uc810\ub4e4\uc744 \uc120\ud0dd\ud558\uc138\uc694
+dialog.selecttracks.noname=\uc774\ub984 \uc5c6\uc74c
+dialog.jpegload.subdirectories=\ud558\uc704 \ud3f4\ub354\ub3c4 \ud3ec\ud568
+dialog.jpegload.loadjpegswithoutcoords=\uc88c\ud45c \uc815\ubcf4\uc5c6\ub294 \uc0ac\uc9c4\ub4e4\ub3c4 \ud3ec\ud568
+dialog.jpegload.loadjpegsoutsidearea=\uc9c0\uae08 \uc9c0\uc5ed\uc678\ubd80 \uc0ac\uc9c4\ub4e4\ub3c4 \ud3ec\ud568
+dialog.jpegload.progress.title=\uc0ac\uc9c4\uc744 \ubd88\ub7ec\uc624\uace0 \uc788\uc2b5\ub2c8\ub2e4.
+dialog.jpegload.progress=\uc0ac\uc9c4\uc774 \uac80\uc0c9\ub418\ub294 \ub3d9\uc548 \uae30\ub2e4\ub824 \uc8fc\uc138\uc694.
+dialog.gpsload.nogpsbabel=gpsbabel \ud504\ub85c\uadf8\ub7a8\uc744 \ucc3e\uc9c0 \ubabb\ud558\uc600\uc2b5\ub2c8\ub2e4. \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.gpsload.device=\uc7a5\uce58 \uc774\ub984
+dialog.gpsload.format=\ud615\ud0dc
+dialog.gpsload.getwaypoints=\uacbd\uc720\uc9c0 \ubd88\ub7ec\uc624\uae30
+dialog.gpsload.gettracks=\ud2b8\ub809 \ubd88\ub7ec\uc624\uae30
+dialog.gpsload.save=\ud30c\uc77c\ub85c \uc800\uc7a5
+dialog.gpssend.sendwaypoints=\uacbd\uc720\uc9c0 \ubcf4\ub0b4\uae30
+dialog.gpssend.sendtracks=\ud2b8\ub809 \ubcf4\ub0b4\uae30
+dialog.gpssend.trackname=\ud2b8\ub809 \uc774\ub984
+dialog.saveoptions.title=\ud30c\uc77c \uc800\uc7a5
+dialog.save.fieldstosave=\ud544\ub4dc\uba85\uc73c\ub85c \uc800\uc7a5
+dialog.save.table.field=\ud544\ub4dc
+dialog.save.table.hasdata=\uc790\ub8cc\uac00 \uc788\uc74c
+dialog.save.table.save=\uc800\uc7a5
+dialog.save.headerrow=\uccab\ubc88\uc9f8 \uc904\ub3c4 \uc800\uc7a5
+dialog.save.coordinateunits=\uc88c\ud45c \ub2e8\uc704
+dialog.save.altitudeunits=\uace0\ub3c4 \ub2e8\uc704
+dialog.save.timestampformat=\uc2dc\uac04 \ud45c\ud604\ud615\uc2dd
+dialog.save.overwrite.title=\ud30c\uc77c\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4.
+dialog.save.overwrite.text=\ud30c\uc77c\uc774 \uc774\ubbf8 \uc788\uc2b5\ub2c8\ub2e4. \uadf8\ub798\ub3c4 \uc774 \ud30c\uc77c\uc5d0 \ub36e\uc5b4\uc50c\uc6b0\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.save.notypesselected=\uc9c0\uc810\uc758 \ud615\uc2dd\uc774 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.
+dialog.exportkml.text=\uc81c\ubaa9\uc73c\ub85c \uc800\uc7a5
+dialog.exportkml.altitude=\uace0\ub3c4 \uace0\uc815(\ube44\ud589)
+dialog.exportkml.kmz=kmz \ud30c\uc77c \uc0dd\uc131\uc2dc \uc555\ucd95\ud558\uae30
+dialog.exportkml.exportimages=kmz\ub85c \uc791\uc740 \uc0ac\uc9c4 \ub0b4\ubcf4\ub0b4\uae30
+dialog.exportkml.trackcolour=\ud2b8\ub809 \uc0c9
+dialog.exportgpx.name=\uc774\ub984
+dialog.exportgpx.desc=\uc124\uba85
+dialog.exportgpx.includetimestamps=\uc2dc\uac04 \ud3ec\ud568\ud558\uae30
+dialog.exportgpx.copysource=xml \ud30c\uc77c \ubcf5\uc0ac\ud558\uae30
+dialog.exportpov.text=POV\ub85c \ub0b4\ubcf4\ub0bc \ubcc0\uc218\ub97c \uc801\uc5b4\uc8fc\uc138\uc694.
+dialog.exportpov.font=\uae00\uaf34
+dialog.exportpov.camerax=\uce74\uba54\ub77c\uc758 X \uc88c\ud45c
+dialog.exportpov.cameray=\uce74\uba54\ub77c\uc758 Y \uc88c\ud45c
+dialog.exportpov.cameraz=\uce74\uba54\ub77c\uc758 Z \uc88c\ud45c
+dialog.exportpov.modelstyle=\ubaa8\ub378 \uc2a4\ud0c0\uc77c
+dialog.exportpov.ballsandsticks=\ub9c9\ub300\uae30\uc640 \uacf5
+dialog.exportpov.tubesandwalls=\ubcbd\uacfc \ud29c\ube0c
+dialog.exportpov.warningtracksize=\uc774 \ud2b8\ub799\uc740 \uc9c0\uc810\uc774 \ub108\ubb34 \ub9ce\uc544 Java3D\uac00 \ud45c\ud604 \ubabb\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.exportsvg.text=SVG\ub85c \ub0b4\ubcf4\ub0bc \ud30c\ub77c\ubbf8\ud130\ub97c \uc120\ud0dd\ud558\uc138\uc694
+dialog.exportsvg.phi=\ubc29\uc704\uac01(\u03d5)
+dialog.exportsvg.theta=\uace0\ub3c4(\u03b8)
+dialog.exportsvg.gradients=\uc250\uc774\ub529\uc5d0 \uadf8\ub798\ub514\uc5b8\ud2b8 \uc0ac\uc6a9\ud558\uae30
+dialog.pointtype.desc=\uc9c0\uc810 \ud615\uc2dd \uc800\uc7a5\ud558\uae30
+dialog.pointtype.track=\ud2b8\ub799 \uc9c0\uc810
+dialog.pointtype.waypoint=\uacbd\uc720 \uc9c0\uc810
+dialog.pointtype.photo=\uc0ac\uc9c4 \uc9c0\uc810
+dialog.pointtype.audio=\uc18c\ub9ac \uc9c0\uc810
+dialog.pointtype.selection=\uc120\ud0dd \uc601\uc5ed\ub9cc
+dialog.confirmreversetrack.title=\ubc18\uc804\uc778\uc9c0 \ud655\uc778
+dialog.confirmreversetrack.text=\uc774 \ud2b8\ub799\uc740 \ubc18\uc804\ud6c4 \uc21c\uc11c\uac00 \ubc14\uafe8\uc744 \uc218\ub3c4 \uc788\ub294\uc2dc\uac04\uc815\ubcf4\ub97c \ud3ec\ud568\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uc774\uc601\uc5ed\uc744 \ubc18\uc804\uc2dc\ud0a4\uc2dc\ub824\ub098\uc694?
+dialog.confirmcutandmove.title=\uc790\ub974\uace0 \uc62e\uae30\uae30 \ud655\uc778
+dialog.confirmcutandmove.text=\uc774 \ud2b8\ub799\uc740 \uc774\ub3d9\ud6c4 \uc21c\uc11c\uac00 \ubc14\uafe7\uc744 \uc218\ub3c4 \uc788\ub294 \uc2dc\uac04\uc815\ubcf4\ub97c \ud3ec\ud568\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uc774 \uc601\uc5ed\uc744 \uc62e\uae30\uc2dc\ub824\ub098\uc694?
+dialog.interpolate.title=\uc0bd\uc785\ud55c \uc9c0\uc810
+dialog.interpolate.parameter.text=\uc120\ud0dd\ud55c \uc9c0\uc810 \uc0ac\uc774\uc5d0 \ub123\uc744 \uc9c0\uc810\uc758 \uc218
+dialog.undo.title=\uc791\uc5c5 \ub418\ub3cc\ub9ac\uae30
+dialog.undo.pretext=\ub418\ub3cc\ub9b4 \uc791\uc5c5\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694
+dialog.undo.none.title=\ub418\ub3cc\ub9b4 \uc218 \uc5c6\uc5b4\uc694
+dialog.undo.none.text=\ub418\ub3cc\ub9b4 \uc791\uc5c5\uc774 \uc5c6\uc5b4\uc694!
+dialog.clearundo.title=\ub418\ub3cc\ub9ac\uae30 \ubaa9\ub85d \uc9c0\uc6b0\uae30
+dialog.clearundo.text=\uc815\ub9d0\ub85c \ub418\ub3cc\ub9ac\uae30 \ubaa9\ub85d \uc9c0\uc6b0\uc2e4\uac74\uac00\uc694? /n \ubaa8\ub4e0 \ub418\ub3cc\ub9ac\uae30 \uc815\ubcf4\uac00 \uc5c6\uc5b4\uc9c4\ub2e4\uad6c\uc694!
+dialog.pointedit.title=\uc9c0\uc810 \uc218\uc815\ud558\uae30
+dialog.pointedit.text=\uc218\uc815\ud560 \ud544\ub4dc\ub97c \uc120\ud0dd\ud558\uc2dc\uace0, \uc218\uc815\ud558\uae30 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc138\uc694.
+dialog.pointedit.table.field=\ud544\ub4dc
+dialog.pointedit.table.value=\uac12
+dialog.pointedit.table.changed=\uc218\uc815\ub428
+dialog.pointedit.changevalue.text=\uc774 \ud544\ub4dc\uc5d0 \uc0c8 \uac12\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694
+dialog.pointedit.changevalue.title=\ud544\ub4dc \uc218\uc815
+dialog.pointnameedit.name=\uacbd\uc720\uc9c0 \uc774\ub984
+dialog.pointnameedit.uppercase=\ub300\ubb38\uc790\ub85c
+dialog.pointnameedit.lowercase=\uc18c\ubb38\uc790\ub85c
+dialog.pointnameedit.sentencecase=\uccab\uae00\uc790\ub9cc \ub300\ubb38\uc790\ub85c
+dialog.addtimeoffset.add=\uc2dc\uac04 \ucd94\uac00
+dialog.addtimeoffset.subtract=\uc2dc\uac04 \ube7c\uae30
+dialog.addtimeoffset.days=\uc77c
+dialog.addtimeoffset.hours=\uc2dc
+dialog.addtimeoffset.minutes=\ubd84
+dialog.addtimeoffset.notimestamps=\uc2dc\uac04 \uc815\ubcf4\ub97c \ud3ec\ud568\ud558\uace0 \uc788\uc9c0\uc54a\uc544\uc11c \uc2dc\uac04\uc624\ud504\uc14b\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\ub124\uc694.
+dialog.findwaypoint.intro=\uacbd\uc720\uc9c0 \uc774\ub984\uc744 \uc801\uc73c\uc138\uc694.
+dialog.findwaypoint.search=\ucc3e\uae30
+dialog.saveexif.title=Exif \uc800\uc7a5
+dialog.saveexif.intro=\uccb4\ud06c\ubc15\uc2a4\ub97c \uc774\uc6a9\ud574\uc11c \uc800\uc7a5\ud560 \uc0ac\uc9c4\uc744 \uc120\ud0dd\ud558\uc138\uc694.
+dialog.saveexif.nothingtosave=\uc88c\ud45c \ub370\uc774\ud0c0\uac00 \ubc14\ub00c\uc9c0 \uc54a\uc558\uc5b4\uc694. \uc800\uc7a5\ud560 \uac83\uc774 \uc544\ubb34\uac83\ub3c4 \uc5c6\ub124\uc694.
+dialog.saveexif.noexiftool=exiftool \ud504\ub85c\uadf8\ub7a8\uc744 \ucc3e\uc9c0 \ubabb\ud588\uc5b4\uc694. \uacc4\uc18d \ud558\uc2dc\uaca0\uc5b4\uc694?
+dialog.saveexif.table.photoname=\uc0ac\uc9c4 \uc774\ub984
+dialog.saveexif.table.status=\uc0c1\ud0dc
+dialog.saveexif.table.save=\uc800\uc7a5
+dialog.saveexif.photostatus.connected=\uc9c0\uc810\uacfc \uc5f0\uacb0\ub428
+dialog.saveexif.photostatus.disconnected=\uc9c0\uac80\uacfc\uc758 \uc5f0\uacb0 \ub04a\uae40
+dialog.saveexif.photostatus.modified=\uc218\uc815\ub428
+dialog.saveexif.overwrite=\ud30c\uc77c \ub36e\uc5b4\uc4f0\uae30
+dialog.saveexif.force=\uc624\ub958\ubb34\uc2dc\ud558\uae30
+dialog.charts.xaxis=x \ucd95
+dialog.charts.yaxis=y \ucd95
+dialog.charts.output=\ucd9c\ub825
+dialog.charts.screen=\ud654\uba74\uc73c\ub85c \ucd9c\ub825
+dialog.charts.svg=SVG\ud30c\uc77c\ub85c \ucd9c\ub825
+dialog.charts.svgwidth=SVG \ud3ed
+dialog.charts.svgheight=SVG \ub192\uc774
+dialog.charts.needaltitudeortimes=\ucc28\ud2b8\ub9cc\ub4e4\ub54c \ud2b8\ub809\uc5d0\ub294 \uace0\ub3c4\uc640 \uc2dc\uac04 \uc815\ubcf4\uac00 \ud544\uc694\ud574\uc694.
+dialog.charts.gnuplotnotfound=\uc54c\ub824\uc900\uacbd\ub85c\uc5d0\uc11c gnuplot\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc5b4\uc694.
+dialog.distances.intro=\uc9c0\uc810\uc0ac\uc774\uc758 \uc9c1\uc120 \uac70\ub9ac
+dialog.distances.column.from=\uc9c0\uac80\uc5d0\uc11c
+dialog.distances.column.to=\uc9c0\uc810\uae4c\uc9c0
+dialog.distances.currentpoint=\uc9c0\uae08 \uc9c0\uc810
+dialog.distances.toofewpoints=\uacbd\uc720\uc9c0 \uc0ac\uc774\uc758 \uac70\ub9ac\ub97c \uc54c\ub824\uba74 \uacbd\uc720\uc9c0\uac00 2\uac1c\uc774\uc0c1 \ud544\uc694\ud574\uc694.
+dialog.fullrangedetails.intro=\uc120\ud0dd\ub41c \ubc94\uc704\uc758 \uc790\uc138\ud55c \uc815\ubcf4
+dialog.setmapbg.intro=\ub9f5 \uc18c\uc2a4 \ud558\ub098\ub97c \uace0\ub974\uac70\ub098 \uc0c8\ub85c \ucd94\uac00\ud558\uc138\uc694.
+dialog.addmapsource.title=\uc0c8 \ub9f5 \uc18c\uc2a4 \ucd94\uac00\ud558\uae30
+dialog.addmapsource.sourcename=\uc18c\uc2a4 \uc774\ub984
+dialog.addmapsource.layer1url=\uccab \ub808\uc774\uc5b4\uc758 URL
+dialog.addmapsource.layer2url=\ub450\ubc88\uc9f8 \ub808\uc774\uc5b4\uc758 URL
+dialog.addmapsource.maxzoom=\ucd5c\uace0 \ud655\ub300
+dialog.addmapsource.cloudstyle=\uc2a4\ud0c0\uc77c \uc218
+dialog.addmapsource.noname=\uc774\ub984 \uc5c6\uc74c
+dialog.gpsies.column.name=\ud2b8\ub809 \uc774\ub984
+dialog.gpsies.column.length=\uae38\uc774
+dialog.gpsies.description=\uc124\uba85
+dialog.gpsies.nodescription=\uc124\uba85 \uc5c6\uc74c
+dialog.gpsies.nonefound=\ud2b8\ub799\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc74c
+dialog.gpsies.username=Gpsies username
+dialog.gpsies.password=Gpsies \ube44\ubc00\ubc88\ud638
+dialog.gpsies.keepprivate=\ud2b8\ub799 \uac1c\uc778\uc815\ubcf4\ubcf4\ud638
+dialog.gpsies.confirmopenpage=\uc5c5\ub85c\ub4dc\ud55c \ud2b8\ub799\uc744 \uc6f9\ud398\uc774\uc9c0\uc5d0\uc11c \ubcf4\uc2dc\uaca0\uc5b4\uc694?
+dialog.gpsies.activities=\ud65c\ub3d9 \ud615\ud0dc
+dialog.gpsies.activity.trekking=\ub3c4\ubcf4\uc5ec\ud589
+dialog.gpsies.activity.walking=\uac77\uae30
+dialog.gpsies.activity.jogging=\ub2ec\ub9ac\uae30
+dialog.gpsies.activity.biking=\uc790\uc804\uac70\ud0c0\uae30
+dialog.gpsies.activity.motorbiking=\uc624\ud1a0\ubc14\uc774\ud0c0\uae30
+dialog.gpsies.activity.snowshoe=\ub208\uae38\uac77\uae30
+dialog.gpsies.activity.sailing=\ubc30\ud0c0\uae30
+dialog.gpsies.activity.skating=\uc2a4\ucf00\uc774\ud2b8\ud0c0\uae30
+dialog.wikipedia.column.name=\uac8c\uc2dc\ubb3c \uc774\ub984
+dialog.wikipedia.column.distance=\uac70\ub9ac
+dialog.correlate.notimestamps=\uc9c0\uc810 \ub370\uc774\ud130\uc5d0 \uc2dc\uac04\uc815\ubcf4\uac00 \uc5c6\uc5b4\uc11c \uc0ac\uc9c4\uc744 \uc5f0\uacb0 \ud560 \uc218 \uc5c6\uc5b4\uc694.
+dialog.correlate.nouncorrelatedphotos=\uc5f0\uacb0\uc774 \uc548\ub41c \uc0ac\uc9c4\uc774 \uc5c6\ub124\uc694. /n \uacc4\uc18d \ud558\uc2e4\uac70\uc8e0?
+dialog.correlate.photoselect.intro=\ub300\ud45c\uc0ac\uc9c4\uc73c\ub85c \uc0ac\uc6a9\ud560 \uc0ac\uc9c4\uc744 \ud558\ub098 \uc120\ud0dd\ud558\uc138\uc694.
+dialog.correlate.select.photoname=\uc0ac\uc9c4\uc774\ub984
+dialog.correlate.select.timediff=\uc0ac\uae34 \ucc28\uc774
+dialog.correlate.select.photolater=\uc0ac\uc9c4 \uc2dc\uac04\uc774 \uc9c0\uc810\uc2dc\uac04\ubcf4\ub2e4 \ub290\ub9bc
+dialog.correlate.options.tip=\ub3c4\uc6c0: \ucd5c\uc18c\ud55c \ud55c\uac1c\uc758 \uc544\uc774\ud0ec(\uc0ac\uc9c4,\uc18c\ub9ac)\uc744 \uc9c1\uc811 \uc5f0\uacb0\ud558\uba74, \ud0c0\uc784 \uc624\ud504\uc14b\uc744 \uacc4\uc0b0\ud560 \uc218 \uc788\uc5b4\uc694.
+dialog.correlate.options.intro=\uc790\ub3d9 \uc5f0\uacb0\uc744 \uc704\ud574 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.
+dialog.correlate.options.offsetpanel=\uc2dc\uac04 \uc624\ud504\uc14b
+dialog.correlate.options.offset=\uc624\ud504\uc14b
+dialog.correlate.options.offset.hours=\uc2dc\uac04
+dialog.correlate.options.offset.minutes=\ubd84
+dialog.correlate.options.offset.seconds=\ucd08
+dialog.correlate.options.photolater=\uc9c0\uc810\uc2dc\uac04\ubcf4\ub2e4 \uc0ac\uc9c4\uc2dc\uac04\uc774 \ub4a4\uc784
+dialog.correlate.options.pointlaterphoto=\uc0ac\uc9c4\uc2dc\uac04\ubcf4\ub2e4 \uc9c0\uc810\uc2dc\uac04\uc774 \ub4a4\uc784
+dialog.correlate.options.audiolater=\uc9c0\uc810\uc2dc\uac04\ubcf4\ub2e4 \uc18c\ub9ac\uc2dc\uac04\uc774 \ub4a4\uc784
+dialog.correlate.options.pointlateraudio=\uc18c\ub9ac\uc2dc\uac04\ubcf4\ub2e4 \uc9c0\uc810\uc2dc\uac04\uc774 \ub4a4\uc784
+dialog.correlate.options.limitspanel=\uc5f0\uacb0 \uc81c\ud55c
+dialog.correlate.options.notimelimit=\uc2dc\uac04 \uc81c\ud55c \uc5c6\uc74c
+dialog.correlate.options.timelimit=\uc2dc\uac04 \uc81c\ud55c
+dialog.correlate.options.nodistancelimit=\uac70\ub9ac\uc81c\ud55c \uc5c6\uc74c
+dialog.correlate.options.distancelimit=\uac70\ub9ac\uc81c\ud55c
+dialog.correlate.options.correlate=\uc5f0\uacb0
+dialog.correlate.alloutsiderange=\ubaa8\ub4e0 \uc0ac\uc9c4\ub4e4\uc774 \ud2b8\ub809\uc758 \uc2dc\uac04 \ubc94\uc704\uc548\uc5d0 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4./n\uc624\ud504\uc14b\uc744 \uc218\uc815\ud574\ubcf4\uc2dc\uac70\ub098 \ucd5c\uc18c\ud55c \ud55c \uc7a5\uc758 \uc0ac\uc9c4\uc744 \uc9c1\uc811 \uc5f0\uacb0\ud574\ubcf4\uc138\uc694.
+dialog.correlate.filetimes=\ud30c\uc77c\uc758 \uc2dc\uac04\uc774 \ub098\ud0c0\ub0b4\ub294 :
+dialog.correlate.filetimes2=\uc18c\ub9ac\ud30c\uc77c\uc758
+dialog.correlate.correltimes=\uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc138\uc694
+dialog.correlate.timestamp.beginning=\uc2dc\uc791
+dialog.correlate.timestamp.middle=\uc911\uac04
+dialog.correlate.timestamp.end=\ub05d
+dialog.correlate.audioselect.intro=\ud0c0\uc784\uc624\ud504\uc14b\uc73c\ub85c \uc0ac\uc6a9\ud560 \uc5f0\uacb0\ub41c \uc18c\ub9ac\ub4e4\uc911 \ud558\ub098\ub97c \uc120\ud0dd\ud558\uc138\uc694
+dialog.correlate.select.audioname=\uc18c\ub9ac \uc774\ub984
+dialog.correlate.select.audiolater=\uc18c\ub9ac \ud6c4\uc5d0
+dialog.rearrangephotos.desc=\uc0ac\uc9c4 \uc9c0\uc810\ub4e4\uc744 \uc5b4\ub5bb\uac8c \uc815\ub82c\ud560 \uac74\uc9c0 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.
+dialog.rearrangephotos.tostart=\uc2dc\uc791\uc73c\ub85c
+dialog.rearrangephotos.toend=\ub05d\uc73c\ub85c
+dialog.rearrangephotos.nosort=\uc815\ub82c\ud558\uc9c0 \uc54a\uae30
+dialog.rearrangephotos.sortbyfilename=\ud30c\uc77c \uc774\ub984\uc73c\ub85c \uc815\ub82c
+dialog.rearrangephotos.sortbytime=\uc2dc\uac04\uc73c\ub85c \uc815\ub82c
+dialog.compress.nonefound=\uc9c0\uc810 \ub370\uc774\ud130\uac00 \uc81c\uac70\ub420 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.
+dialog.compress.closepoints.title=\uc8fc\ubcc0 \ud3ec\uc778\ud2b8 \uc81c\uac70
+dialog.compress.closepoints.paramdesc=\ud655\uc7a5 \uacc4\uc218
+dialog.compress.wackypoints.title=\uc5c9\ub6b1\ud558\uac70\ub098 \uc0ac\uc6a9\ud558\uc9c0 \uc54a\ub294 \uc9c0\uc810 \uc81c\uac70
+dialog.compress.wackypoints.paramdesc=\uac70\ub9ac \uacc4\uc218
+dialog.compress.singletons.title=\ud558\ub098\uc529 \uc81c\uac70
+dialog.compress.singletons.paramdesc=\uac70\ub9ac \uacc4\uc218
+dialog.compress.duplicates.title=\ubcf5\uc0ac\ub41c\uac70 \uc81c\uac70
+dialog.compress.summarylabel=\uc0ad\uc81c\ud560 \uc9c0\uc810
+dialog.pastecoordinates.desc=\uc790\ud45c\ub97c \ub123\uc73c\uc138\uc694
+dialog.pastecoordinates.coords=\uc88c\ud45c
+dialog.pastecoordinates.nothingfound=\uc88c\ud45c\ub97c \ud655\uc778\ud558\uc2dc\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \ubcf4\uc138\uc694.
+dialog.help.help=http://activityworkshop.net/software/prun
+dialog.about.version=\ubc84\uc804
+dialog.about.build=\ube4c\ub4dc
+dialog.about.summarytext1=Prune\uc740 GPS\uc218\uc2e0\uae30\uc5d0\uc11c \uc704\uce58 \uc815\ubcf4\ub97c \ubc1b\uace0, \ud654\uba74\uc5d0 \ubcf4\uc5ec\uc8fc\uace0, \uc218\uc815\ud558\uac8c \ud574\uc8fc\ub294 \ud504\ub85c\uadf8\ub7a8\uc785\ub2c8\ub2e4.
+dialog.about.summarytext2=\uc774 \ud504\ub85c\uadf8\ub7a8\uc740 \uc790\uc720\ub86d\uac8c, \uac1c\ubc29\uc801\uc73c\ub85c, \uadf8\ub9ac\uace0 \uc804\uc138\uacc4\uc801\uc73c\ub85c \uc0ac\uc6a9\ud558\uace0 \uac1c\uc120\ud558\uae30 \uc704\ud574 Gnu GPL \uc5d0 \ub530\ub77c\uc11c \ubc30\ud3ec\ub429\ub2c8\ub2e4. <br> \uc774 \ud504\ub85c\uadf8\ub7a8\uc5d0 \ud3ec\ud568\ub41c <code>license.txt</code> \ud30c\uc77c\uc758 \uc870\uac74\uc5d0 \ub530\ub77c \ubcf5\uc81c, \uc7ac\ubc30\ud3ec, \uc218\uc815\uc774 \ud5c8\uac00\ub418\uace0, \uc7a5\ub824\ub429\ub2c8\ub2e4.
+dialog.about.summarytext3=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.<code style="font-weight:bold">http://activityworkshop.net/</code></span>
+dialog.about.languages=\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc5b8\uc5b4\ub4e4
+dialog.about.translatedby=\ud55c\uad6d\uc5b4 Hooau
+dialog.about.systeminfo=\uc2dc\uc2a4\ud15c \uc815\ubcf4
+dialog.about.systeminfo.os=\uc6b4\uc601\uccb4\uc81c
+dialog.about.systeminfo.java=\uc790\ubc14 \ub7f0\ud0c0\uc784
+dialog.about.systeminfo.java3d=Java3D\uac00 \uc124\uce58\ub418\uc5c8\uc74c
+dialog.about.systeminfo.povray=Povray\uac00 \uc124\uce58\ub418\uc5c8\uc74c
+dialog.about.systeminfo.exiftool=Exiftool\uc774 \uc124\uce58\ub418\uc5c8\uc74c
+dialog.about.systeminfo.gpsbabel=Gpsbabel\uc774 \uc124\uce58\ub418\uc5c8\uc74c
+dialog.about.systeminfo.gnuplot=Gnuplot\uc774 \uc124\uce58\ub418\uc5c8\uc74c
+dialog.about.systeminfo.exiflib=Exif \ub77c\uc774\ube0c\ub7ec\ub9ac
+dialog.about.systeminfo.exiflib.internal=\ub0b4\uc7a5
+dialog.about.systeminfo.exiflib.internal.failed=\ub0b4\uc7a5(\ucc3e\uc9c0\ubabb\ud568)
+dialog.about.systeminfo.exiflib.external=\uc678\uc7a5
+dialog.about.systeminfo.exiflib.external.failed=\uc678\uc7a5(\ucc3e\uc9c0\ubabb\ud568)
+dialog.about.yes=\uc608
+dialog.about.no=\uc544\ub2c8\uc624
+dialog.about.credits=\uc5d0\uac8c \uac10\uc0ac\ub97c
+dialog.about.credits.code=Prune \ucf54\ub4dc\ub97c \uc791\uc131\ud574\uc900
+dialog.about.credits.exifcode=Exif \ucf54\ub4dc\ub97c \uc791\uc131\ud574\uc900
+dialog.about.credits.icons=\uc77c\ubd80 \uc544\uc774\ucf58\uc744 \uac00\uc838\uc628
+dialog.about.credits.translators=\ubc88\uc5ed\uc790\ub4e4
+dialog.about.credits.translations=\ubc88\uc5ed\uc5d0 \ub3c4\uc6c0\uc744 \uc900
+dialog.about.credits.devtools=\uac1c\ubc1c \ub3c4\uad6c\ub4e4
+dialog.about.credits.othertools=\ub2e4\ub978 \ub3c4\uad6c\ub4e4
+dialog.about.credits.thanks=\uac10\uc0ac\ud569\ub2c8\ub2e4.
+dialog.about.readme=\uc77d\uc5b4\uc8fc\uc138\uc694
+dialog.checkversion.error=\ubc84\uc804\uc774 \ud655\uc778\ub418\uc9c0 \uc54a\uc558\uc5b4\uc694./n \uc778\ud130\ub137 \uc5f0\uacb0\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.
+dialog.checkversion.uptodate=\ub2f9\uc2e0\uc740 Prune\uc758 \ucd5c\uc2e0 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uacc4\uc2ed\ub2c8\ub2e4.
+dialog.checkversion.newversion1=Prune\uc758 \uc0c8 \ubc84\uc804\uc744 \uc9c0\uae08 \uc0ac\uc6a9\ud560 \uc218 \uc788\uaca0\ub124\uc694. \ucd5c\uc2e0\ubc84\uc804\uc740
+dialog.checkversion.newversion2=\ubc84\uc804\uc785\ub2c8\ub2e4.
+dialog.checkversion.releasedate1=\uc0c8 \ubc84\uc804\uc740
+dialog.checkversion.releasedate2=\uc5d0 \ubc30\ud3ec\ub418\uc5c8\uc2b5\ub2c8\ub2e4.
+dialog.checkversion.download=\uc0c8 \ubc84\uc804\uc744 \ub2e4\uc6b4\ubc1b\uace0 \uc2f6\uc73c\uc138\uc694? \uadf8\ub7fc \uc544\ub798 URL\ub85c \uc640\uc8fc\uc138\uc694. /n http://activityworkshop.net/software/prune/download.html.
+dialog.keys.intro=\ub9c8\uc6b0\uc2a4\ub97c \uc0ac\uc6a9\ud558\uc9c0 \ub9c8\uc2dc\uace0 \uc544\ub798 \ub2e8\ucd95\ud0a4\ub97c \uc0ac\uc6a9\ud574\ubcf4\uc138\uc694.
+dialog.keys.keylist=<table><tr><td>\ud654\uc0b4\ud45c \ud0a4</td><td>\uc88c,\uc6b0,\uc544\ub798,\uc704\ub85c \uc9c0\ub3c4 \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc67c\ucabd, \uc624\ub978\ucabd \ud654\uc0b4\ud45c</td><td>\uc9c0\uc810 \uc120\ud0dd \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc704, \uc544\ub798 \ud654\uc0b4\ud45c</td><td>\uc9c0\ub3c4 \ud655\ub300 \ucd95\uc18c</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\uc774\uc804 \uc774\ud6c4 \ubd80\ubd84 \uc120\ud0dd</td></tr><tr><td>Ctrl + Home, End</td><td>\ucc98\uc74c \uc9c0\uc810, \ub9c8\uc9c0\ub9c9 \uc9c0\uc810 \uc120\ud0dd</td></tr><tr><td>Del</td><td>\ud604\uc7ac \uc9c0\uc810 \uc0ad\uc81c</td></tr></table>
+dialog.keys.normalmodifier=Ctrl
+dialog.keys.macmodifier=Command
+dialog.saveconfig.desc=\uc544\ub798 \uc124\uc815\ub4e4\uc740 \uc124\uc815\ud30c\uc77c\uc5d0 \uc800\uc7a5\ud560 \uc218 \uc788\uc5b4\uc694:
+dialog.saveconfig.prune.trackdirectory=\ud2b8\ub809 \ud3f4\ub354
+dialog.saveconfig.prune.photodirectory=\uc0ac\uc9c4 \ud3f4\ub354
+dialog.saveconfig.prune.languagecode=\uc5b8\uc5b4\ucf54\ub4dc (KO)
+dialog.saveconfig.prune.languagefile=\uc5b8\uc5b4\ud30c\uc77c
+dialog.saveconfig.prune.gpsdevice=GPS\uc7a5\uce58
+dialog.saveconfig.prune.gpsformat=GPS \ud615\uc2dd
+dialog.saveconfig.prune.povrayfont=Povray \uae00\uaf34
+dialog.saveconfig.prune.metricunits=\ubbf8\ud130\ubc95 \ub2e8\uc704\ub97c \uc0ac\uc6a9\ud558\uc2dc\ub098\uc694?
+dialog.saveconfig.prune.gnuplotpath=gnjuplot \uacbd\ub85c
+dialog.saveconfig.prune.gpsbabelpath=gpsbabel \uacbd\ub85c
+dialog.saveconfig.prune.exiftoolpath=exiftool \uacbd\ub85c
+dialog.saveconfig.prune.mapsource=\uc120\ud0dd\ub41c \uc9c0\ub3c4 \uc704\uce58
+dialog.saveconfig.prune.mapsourcelist=\uc9c0\ub3c4 \uc18c\uc2a4
+dialog.saveconfig.prune.diskcache=\uc9c0\ub3c4 \uce90\uc2dc
+dialog.saveconfig.prune.kmzimagewidth=KMZ \uc774\ubbf8\uc9c0 \ub113\uc774
+dialog.saveconfig.prune.kmzimageheight=KMZ \uc774\ubbf8\uc9c0 \ub192\uc774
+dialog.saveconfig.prune.colourscheme=\uc0c9 \uad6c\uc131
+dialog.saveconfig.prune.linewidth=\ud2b8\ub799\uc120 \ub450\uaed8
+dialog.saveconfig.prune.kmltrackcolour=KML \ud2b8\ub799 \uc0c9
+dialog.setpaths.intro=\uc678\ubd80 \ud504\ub85c\uadf8\ub7a8\uc758 \uacbd\ub85c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc5b4\uc694.
+dialog.setpaths.found=\uacbd\ub85c\ub97c \ucc3e\uc73c\uc168\ub098\uc694?
+dialog.addaltitude.noaltitudes=\uc120\ud0dd\ub41c \ubc94\uc704\uc5d0 \uace0\ub3c4\uac00 \ud3ec\ud568\ub418\uc5b4\uc788\uc9c0 \uc54a\ub124\uc694.
+dialog.addaltitude.desc=\ucd94\uac00\ud560 \uace0\ub3c4 \uc624\ud504\uc14b
+dialog.lookupsrtm.overwritezeros=\uace0\ub3c4 \uac12\uc744 0\uc73c\ub85c \ub36e\uc5b4\uc4f0\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.setcolours.intro=\ubc14\uafc0 \uc0c9\uc73c\ub85c \uc0c9 \ud328\uce58\uc5d0\uc11c \ud074\ub9ad\ud558\uc138\uc694.
+dialog.setcolours.background=\ubc30\uacbd
+dialog.setcolours.borders=\ud14c\ub450\ub9ac
+dialog.setcolours.lines=\uc120
+dialog.setcolours.primary=1\ucc28
+dialog.setcolours.secondary=2\ucc28
+dialog.setcolours.point=\uc9c0\uc810
+dialog.setcolours.selection=\uc120\ud0dd
+dialog.setcolours.text=\uae00\uc790
+dialog.colourchooser.title=\uc0c9\uc0c1 \uc120\ud0dd
+dialog.colourchooser.red=\ube68\uac15
+dialog.colourchooser.green=\ub179\uc0c9
+dialog.colourchooser.blue=\ud30c\ub791
+dialog.setlanguage.firstintro=\ud3ec\ud568\ub41c \uc5b8\uc5b4\ub4e4 \uc911 \ud558\ub098\ub97c \uc120\ud0dd\ud558\uc2e4 \uc218 \uc788\uc5b4\uc694,<p> \ud639\uc740 \ub300\uc2e0 \uc0ac\uc6a9\ud560 \ud14d\uc2a4\ud2b8 \ud30c\uc77c\uc744 \uc120\ud0dd\ud558\uc138\uc694.
+dialog.setlanguage.secondintro=\uc124\uc815\uc744 \uc800\uc7a5\ud558\uace0, <p> \uc5b8\uc5b4\ub97c \ubc14\uafb8\uae30\uc704\ud574\uc11c\ub294 Prune\uc744 \uc7ac\uc2dc\uc791\ud558\uc154\uc57c \ud574\uc694.
+dialog.setlanguage.language=\uc5b8\uc5b4
+dialog.setlanguage.languagefile=\uc5b8\uc5b4 \ud30c\uc77c
+dialog.setlanguage.endmessage=\ubcc0\uacbd\ub41c \uc5b8\uc5b4\ub97c \uc801\uc6a9\ud558\uc2e4\ub824\uba74/n\uc124\uc815\uc744 \uc800\uc7a5\ud558\uace0 Prune\uc744 \uc7ac\uc2dc\uc791\ud558\uc138\uc694.
+dialog.diskcache.save=\ub514\uc2a4\ud06c\ub85c \uc9c0\ub3c4 \uc774\ubbf8\uc9c0\ub97c \uc800\uc7a5
+dialog.diskcache.dir=\uce90\uc2dc \ub514\ub809\ud1a0\ub9ac
+dialog.diskcache.createdir=\ub514\ub809\ud1a0\ub9ac \ub9cc\ub4e4\uae30
+dialog.diskcache.nocreate=\uce90\uc2dc \ub514\ub809\ud1a0\ub9ac\uac00 \ub9cc\ub4e4\uc5b4\uc9c0\uc9c0 \uc54a\uc558\uc5b4\uc694.
+dialog.deletefieldvalues.intro=\ud604\uc7ac \ubc94\uc704\uc5d0\uc11c \uc0ad\uc81c\ud560 \ud544\ub4dc\ub97c \uc120\ud0dd
+dialog.setlinewidth.text=\ud2b8\ub799\uc744 \uadf8\ub9b4 \uc120\uc758 \ub450\uaed8\ub97c \uc801\uc73c\uc138\uc694(1-4)
+dialog.downloadosm.desc=\ud2b9\uc815\uc9c0\uc5ed\uc758 raw OSM \ub370\uc774\ud130\ub97c \ub2e4\uc6b4\ub85c\ub4dc \ud558\uc2dc\uaca0\uc5b4\uc694?
+dialog.searchwikipedianames.search=\ucc3e\uae30
+
+# 3d window
+dialog.3d.title=Prune 3D \ubcf4\uae30
+dialog.3d.altitudefactor=\uace0\ub3c4 \uacfc\uc7a5 \uacc4\uc218
+dialog.3dlines.title=Prune \uaca9\uc790\uc120
+dialog.3dlines.empty=\uaca9\uc790\uc120 \uc5c6\uc774 \ud45c\uc2dc\ud558\uae30
+dialog.3dlines.intro=3D \ubcf4\uae30\ub97c \uc704\ud55c \uaca9\uc790\uc120\uc785\ub2c8\ub2e4.
+
+# Confirm messages
+confirm.loadfile=\ud30c\uc77c\uc5d0\uc11c \uc790\ub8cc\ub97c \ubd88\ub7ec\uc654\uc5b4\uc694.
+confirm.save.ok1=
+confirm.save.ok2=\uc9c0\uc810\uc774 \uc798 \uc800\uc7a5\ub418\uc5c8\uc5b4\uc694.
+confirm.deletepoint.single=\uc9c0\uc810\uc774 \uc81c\uac70\ub418\uc5c8\uc5b4\uc694.
+confirm.deletepoint.multi=\uc9c0\uc810\ub4e4\uc774 \uc81c\uc5b4\ub418\uc5c8\uc5b4\uc694.
+confirm.point.edit=\uc9c0\uc810\uc774 \uc218\uc815\ub418\uc5c8\uc5b4\uc694.
+confirm.mergetracksegments=\ud2b8\ub799 \ubd80\ubd84\ub4e4\uc774 \ubcd1\ud569\ub418\uc5c8\uc5b4\uc694.
+confirm.reverserange=\ubc94\uc704\uac00 \ubc18\uc804\ub418\uc5c8\uc5b4\uc694.
+confirm.addtimeoffset=\ud0c0\uc784 \uc624\ud504\uc14b\uc774 \ucd94\uac00\ub418\uc5c8\uc5b4\uc694.
+confirm.addaltitudeoffset=\uace0\ub3c4 \uc624\ud504\uc14b\uc774 \ucd94\uac00\ub418\uc5c8\uc5b4\uc694.
+confirm.rearrangewaypoints=\uacbd\uc720\uc9c0\uac00 \uc7ac\uc815\ub82c\ub418\uc5c8\uc5b4\uc694
+confirm.rearrangephotos=\uc0ac\uc9c4\ub4e4\uc774 \uc7ac\uc815\ub82c\ub418\uc5c8\uc5b4\uc694.
+confirm.cutandmove=\uc62e\uaca8\uc84c\uc5b4\uc694.
+confirm.convertnamestotimes=\uacbd\uc720\uc9c0 \uc774\ub984\ub4e4\uc774 \ubcc0\ud658\ub418\uc5c8\uc5b4\uc694.
+confirm.saveexif.ok1=\uc800\uc7a5\ub428
+confirm.saveexif.ok2=\uc0ac\uc9c4 \ud30c\uc77c\ub4e4
+confirm.undo.single=\uac1c \uc2e4\ud589\ucde8\uc18c \ub3d9\uc791
+confirm.undo.multi=\uac1c \uc2e4\ud589\ucde8\uc18c \ub3d9\uc791
+confirm.jpegload.single=\uc0ac\uc9c4\uc774 \ucd94\uac00\ub428
+confirm.jpegload.multi=\uc0ac\uc9c4\ub4e4\uc774 \ucd94\uac00\ub428
+confirm.media.connect=\ubbf8\ub514\uc544 \ud30c\uc77c \uc5f0\uacb0\ub428
+confirm.photo.disconnect=\uc0ac\uc9c4 \uc5f0\uacb0 \ub04a\uae40
+confirm.audio.disconnect=\uc18c\ub9ac \uc5f0\uacb0 \ub04a\uae40
+confirm.media.removed=\uc0ad\uc81c\ub428
+confirm.correlatephotos.single=\uc0ac\uc9c4 \uc5f0\uacb0\ub428
+confirm.correlatephotos.multi=\uc0ac\uc9c4\ub4e4 \uc5f0\uacb0\ub428
+confirm.createpoint=\uc9c0\uc810 \uc0dd\uc131\ub428
+confirm.rotatephoto=\uc0ac\uc9c4 \ub3cc\ub824\uc9d0
+confirm.running=\uc2e4\ud589\uc911
+confirm.lookupsrtm1=
+confirm.lookupsrtm2=\uace0\ub3c4\uac12 \ubc1c\uacac
+confirm.deletefieldvalues=\ud544\ub4dc\uac12 \uc9c0\uc6cc\uc9d0
+confirm.audioload=\uc18c\ub9ac\ud30c\uc77c \ucd94\uac00\ub428
+confirm.correlateaudios.single=\uc18c\ub9ac \uc5f0\uacb0\ub428
+confirm.correlateaudios.multi=\uc18c\ub9ac\ub4e4 \uc5f0\uacb0\ub428
+
+# Buttons
+button.ok=\ud655\uc778
+button.back=\ub4a4\ub85c
+button.next=\ub2e4\uc74c
+button.finish=\ub9c8\uce68
+button.cancel=\ucde8\uc18c
+button.overwrite=\ub36e\uc5b4\uc4f0\uae30
+button.moveup=\uc704\ub85c
+button.movedown=\uc544\ub798\ub85c
+button.showlines=\uc120 \ubcf4\uae30
+button.edit=\uc218\uc815
+button.exit=\ub098\uac00\uae30
+button.close=\ub2eb\uae30
+button.continue=\uacc4\uc18d
+button.yes=\uc608
+button.no=\uc544\ub2c8\uc624
+button.yestoall=\uc804\ubd80 \uc608
+button.notoall=\uc804\ubd80 \uc544\ub2c8\uc624
+button.select=\uc120\ud0dd
+button.selectall=\uc804\ubd80\uc120\ud0dd
+button.selectnone=\uc544\ubb34\uac83\ub3c4 \uc120\ud0dd\ud558\uc9c0\uc54a\uae30
+button.preview=\ubbf8\ub9ac\ubcf4\uae30
+button.load=\ubd88\ub7ec\uc624\uae30
+button.upload=\uc62c\ub9ac\uae30
+button.guessfields=\ucd94\uce21 \ud544\ub4dc
+button.showwebpage=\uc6f9\ud398\uc774\uc9c0 \ubcf4\uae30
+button.check=\uccb4\ud06c
+button.resettodefaults=\uae30\ubcf8\uc124\uc815\uc73c\ub85c
+button.browse=\ucc3e\uae30
+button.addnew=\uc0c8\ub85c \ucd94\uac00
+button.delete=\uc9c0\uc6b0\uae30
+
+# File types
+filetype.txt=TXT \ud30c\uc77c
+filetype.jpeg=JPG \ud30c\uc77c
+filetype.kmlkmz=KML, KMZ \ud30c\uc77c
+filetype.kml=KML \ud30c\uc77c
+filetype.kmz=KMZ \ud30c\uc77c
+filetype.gpx=GPX \ud30c\uc77c
+filetype.pov=POV \ud30c\uc77c
+filetype.svg=SVG \ud30c\uc77c
+filetype.audio=MP3, OGG, WAV \ud30c\uc77c
+
+# Display components
+display.nodata=\ubd88\ub7ec\uc9c4 \ub370\uc774\ud0c0 \uc5c6\uc74c
+display.noaltitudes=\ud2b8\ub799\ub370\uc774\ud0c0\uc5d0 \uace0\ub3c4\uac00 \ud3ec\ud568\uc548\ub418\uc788\uc5b4\uc694.
+display.notimestamps=\ud2b8\ub799\ub370\uc774\ud0c0\uc5d0 \uc2dc\uac04\uc774 \ud3ec\ud568\uc548\ub418\uc5b4\uc788\uc5b4\uc694.
+details.trackdetails=\ud2b8\ub809 \uc0c1\uc138\uc815\ubcf4
+details.notrack=\ubd88\ub7ec\uc628 \ud2b8\ub799\uc774 \uc5c6\uc5b4\uc694
+details.track.points=\uc9c0\uc810
+details.track.file=\ud30c\uc77c
+details.track.numfiles=\ud30c\uc77c\uc218
+details.pointdetails=\uc9c0\uc810 \uc0c1\uc138\uc815\ubcf4
+details.index.selected=\uac1c \uc120\ud0dd\ub428
+details.index.of=\uac1c \uc911\uc5d0
+details.nopointselection=\uc120\ud0dd\ub41c \uc9c0\uc810 \uc5c6\uc74c
+details.photofile=\uc0ac\uc9c4 \ud30c\uc77c
+details.norangeselection=\ubc94\uc704\uac00 \uc120\ud0dd\ub418\uc9c0 \uc54a\uc558\uc5b4\uc694.
+details.rangedetails=\ubc94\uc704 \uc0c1\uc138\uc815\ubcf4
+details.range.selected=\uc120\ud0dd\ub428
+details.range.to=\ud604\uc7ac \ubc94\uc704 \uc9c0\uc810\uc5d0\uc11c
+details.altitude.to=\uc120\ud0dd\ub41c \ubc94\uc704\uc5d0\uc11c \uc774\uc9c0\uc810\uc740
+details.range.climb=\uc624\ub984
+details.range.descent=\ud558\uac15
+details.coordformat=\uc88c\ud45c\ud615\uc2dd
+details.distanceunits=\uac70\ub9ac \ub2e8\uc704
+display.range.time.secs=\ucd08
+display.range.time.mins=\ubd84
+display.range.time.hours=\uc2dc\uac04
+display.range.time.days=\uc77c
+details.range.avespeed=\ud3c9\uade0 \uc18d\ub3c4
+details.range.avemovingspeed=\ud3c9\uade0 \uc774\ub3d9
+details.range.maxspeed=\ucd5c\uace0 \uc18d\ub3c4
+details.range.numsegments=\ubd80\ubd84\ub4e4\uc758 \uc218
+details.range.pace=\ud398\uc774\uc2a4(1km\ub098 1mile\uc774\ub3d9 \uc2dc\uac04)
+details.range.gradient=\uacbd\uc0ac
+details.lists.waypoints=\uacbd\uc720\uc9c0
+details.lists.photos=\uc0ac\uc9c4\ub4e4
+details.lists.audio=\uc18c\ub9ac
+details.photodetails=\uc0ac\uc9c4 \uc0c1\uc138\uc815\ubcf4
+details.nophoto=\uc0ac\uc9c4\uc774 \uc120\ud0dd\ub418\uc9c0 \uc54a\uc74c
+details.photo.loading=\ubd88\ub7ec\uc624\uae30
+details.media.connected=\uc5f0\uacb0\ub428
+details.audiodetails=\uc18c\ub9ac \uc0c1\uc138\uc815\ubcf4
+details.noaudio=\uc18c\ub9ac\ud30c\uc77c\uc774 \uc120\ud0dd\ub418\uc9c0 \uc54a\uc74c
+details.audio.file=\uc18c\ub9ac\ud30c\uc77c
+details.audio.playing=\uc7ac\uc0dd\uc911...
+map.overzoom=\uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 \uc9c0\ub3c4\ub97c \ud45c\uc2dc\ud560 \uc218 \uc5c6\uc5b4\uc694.
+
+# Field names
+fieldname.latitude=\uc704\ub3c4
+fieldname.longitude=\uacbd\ub3c4
+fieldname.altitude=\uace0\ub3c4
+fieldname.timestamp=\uc2dc\uac04
+fieldname.time=\uc2dc\uac04
+fieldname.waypointname=\uc774\ub984
+fieldname.waypointtype=\ud615\uc2dd
+fieldname.newsegment=\ubd80\ubd84
+fieldname.custom=\ucee4\uc2a4\ud140
+fieldname.prefix=\ud544\ub4dc
+fieldname.distance=\uac70\ub9ac
+fieldname.movingdistance=\uc6c0\uc9c0\uc778\uac70\ub9ac
+fieldname.duration=\uae30\uac04
+fieldname.speed=\uc18d\ub3c4
+fieldname.verticalspeed=\uc218\uc9c1\uc18d\ub3c4
+
+# Measurement units
+units.original=\uc6d0\ubcf8
+units.default=\uae30\ubcf8
+units.metres=\ubbf8\ud130
+units.metres.short=\ubbf8\ud130
+units.feet=\ud53c\ud2b8
+units.feet.short=\ud53c\ud2b8
+units.kilometres=\ud0ac\ub85c\ubbf8\ud130
+units.kilometres.short=\ud0ac\ub85c\ubbf8\ud130
+units.kmh=\ud0ac\ub85c\ubbf8\ud130-\uc2dc\uc18d
+units.miles=\ub9c8\uc77c
+units.miles.short=\ub9c8\uc77c
+units.mph=\ub9c8\uc77c-\uc2dc\uc18d
+units.metrespersec=m/s
+units.feetpersec=ft/s
+units.hours=\uc2dc\uac04
+units.degminsec=\ub3c4-\ubd84-\ucd08
+units.degmin=\ub3c4-\ubd84
+units.deg=\ub3c4
+units.iso8601=ISO 8601
+
+# External urls
+url.googlemaps=maps.google.co.kr
+wikipedia.lang=ko
+
+# Cardinals for 3d plots
+cardinal.n=N
+cardinal.s=S
+cardinal.e=E
+cardinal.w=W
+
+# Undo operations
+undo.load=\uc790\ub8cc \ubd88\ub7ec\uc624\uae30
+undo.loadphotos=\uc0ac\uc9c4 \ubd88\ub7ec\uc624\uae30
+undo.loadaudios=\uc18c\ub9ac\ud30c\uc77c \ubd88\ub7ec\uc624\uae30
+undo.editpoint=\uc9c0\uc810 \uc218\uc815
+undo.deletepoint=\uc9c0\uc810 \uc0ad\uc81c
+undo.removephoto=\uc0ac\uc9c4 \uc81c\uac70
+undo.removeaudio=\uc18c\ub9ac\ud30c\uc77c \uc81c\uac70
+undo.deleterange=\ubc94\uc704 \uc0ad\uc81c
+undo.compress=\ud2b8\ub799 \uc555\ucd95
+undo.insert=\uc9c0\uc810\ub4e4 \uc0bd\uc785
+undo.reverse=\ubc94\uc704 \ubc18\uc804
+undo.mergetracksegments=\ud2b8\ub799 \ubd80\ubd84 \ubcd1\ud569
+undo.addtimeoffset=\ud0c0\uc784 \uc624\ud504\uc14b \ucd94\uac00
+undo.addaltitudeoffset=\uace0\ub3c4 \uc624\ud504\uc14b \ucd94\uac00
+undo.rearrangewaypoints=\uacbd\uc720\uc9c0 \uc7ac\uc815\ub82c
+undo.cutandmove=\ubd80\ubd84 \uc774\ub3d9
+undo.connect=\uc5f0\uacb0
+undo.disconnect=\uc5f0\uacb0 \ub04a\uae30
+undo.correlatephotos=\uc0ac\uc9c4 \uc5f0\uacb0
+undo.rearrangephotos=\uc0ac\uc9c4 \uc7ac\uc815\ub82c
+undo.createpoint=\uc9c0\uc810 \uc0dd\uc131
+undo.rotatephoto=\uc0ac\uc9c4 \ud68c\uc804
+undo.convertnamestotimes=\uc774\ub984\uc744 \uc2dc\uac04\uc73c\ub85c \ubcc0\ud658
+undo.lookupsrtm=SRTM\uc5d0\uc11c \uace0\ub3c4 \ucc3e\uae30
+undo.deletefieldvalues=\ud544\ub4dc \uac11 \uc218\uc815
+undo.correlateaudios=\uc18c\ub9ac \uc5f0\uacb0
+
+# Error messages
+error.save.dialogtitle=\uc790\ub8cc \uc800\uc7a5\uc911 \uc624\ub958
+error.save.nodata=\uc800\uc7a5\ud560 \uc790\ub8cc\uac00 \uc5c6\uc5b4\uc694
+error.save.failed=\ud30c\uc77c\uc5d0 \uc790\ub8cc\uc800\uc7a5 \uc2e4\ud328
+error.saveexif.filenotfound=\uc0ac\uc9c4\ud30c\uc77c \ucc3e\uae30 \uc2e4\ud328
+error.saveexif.cannotoverwrite1=\uc0ac\uc9c4\ud30c\uc77c
+error.saveexif.cannotoverwrite2=\uc774 \uc77d\uae30\uc804\uc6a9\uc774\ub124\uc694, \ub36e\uc5b4\uc4f8\uc218 \uc5c6\uc5b4\uc694 \ubcf5\uc0ac\ud574\uc11c \uc4f8\uae4c\uc694?
+error.saveexif.failed1=
+error.saveexif.failed2=\uc774\ubbf8\uc9c0 \uc800\uc7a5 \uc2e4\ud328
+error.saveexif.forced1=
+error.saveexif.forced2=\uc774\ubbf8\uc9c0\uac00 \uac15\uc81c\ub97c \uc694\uad6c\ud569\ub2c8\ub2e4.
+error.load.dialogtitle=\uc790\ub8cc \ubd88\ub7ec\uc624\ub2e4\uac00 \uc5d0\ub7ec
+error.load.noread=\ud30c\uc77c\uc744 \uc77d\uc744 \uc218 \uc5c6\ub124\uc694.
+error.load.nopoints=\ud30c\uc77c\uc5d0 \uc88c\ud45c \uc815\ubcf4\uac00 \uc5c6\uc5b4\uc694.
+error.load.unknownxml=\uc54c\uc218\uc5c6\ub294 xml \ud3ec\uba67\uc774\uc5d0\uc694.(kml\uc774\ub098 gpx\uac00 \uc544\ub2cc\uac70 \uac19\uc544\uc694)
+error.load.noxmlinzip=zip\ud30c\uc77c\uc548\uc5d0 xml\ud30c\uc77c\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc5b4\uc694.
+error.load.othererror=\ud30c\uc77c \uc77d\ub294 \uc911 \uc5d0\ub7ec :
+error.jpegload.dialogtitle=\uc0ac\uc9c4 \ubd88\ub7ec\uc624\ub294 \uc911 \uc5d0\ub7ec
+error.jpegload.nofilesfound=\ucc3e\uc740 \ud30c\uc77c \uc5c6\uc74c.
+error.jpegload.nojpegsfound=\ucc3e\uc740 jpeg\ud30c\uc77c \uc5c6\uc74c.
+error.jpegload.nogpsfound=GPS \uc815\ubcf4\ub97c \ucc3e\uc9c0 \ubabb\ud568.
+error.jpegload.exifreadfailed=EXIF\uc815\ubcf4 \uc77d\uae30 \uc2e4\ud328./n\ub0b4\uc7a5\uc774\ub098 \uc678\uc7a5 \ub77c\uc774\ube0c\ub7ec\uc774\uac00 \uc5c6\uc73c\uba74/nEXIF\uc815\ubcf4\ub97c \uc77d\uc744 \uc218 \uc5c6\uc5b4\uc694.
+error.audioload.nofilesfound=\ucc3e\uc740 \uc18c\ub9ac\ud30c\uc77c \uc5c6\uc74c.
+error.gpsload.unknown=\uc54c\ub824\uc9c0\uc9c0 \uc54a\uc740 \uc624\ub958.
+error.undofailed.title=\ub418\ub3cc\ub9ac\uae30 \uc2e4\ud328.
+error.undofailed.text=\ub418\ub3cc\ub9ac\uae30 \ub3d9\uc791\uc774 \uc2e4\ud328\ud558\uc600\uc5b4\uc694.
+error.function.noop.title=\uae30\ub2a5\uc774 \uc544\ubb34 \ud6a8\uacfc \uc5c6\ub124\uc694.
+error.rearrange.noop=\uc7ac\uc815\ub82c\ud55c \uc9c0\uc810\ub4e4\uc774 \ud6a8\uacfc \uc5c6\uc5b4\uc694.(\uacbd\uc720\uc9c0\uac00 \uc544\ub2cc\uac70 \uac19\uc544\uc694.)
+error.function.notavailable.title=\uae30\ub2a5\uc744 \uc791\ub3d9\ud560 \uc218 \uc5c6\uc74c.
+error.function.nojava3d=\uc774 \uae30\ub2a5\uc740 Java3D \ub77c\uc774\ube0c\ub7ec\ub9ac\uac00 \ud544\uc694\ud574\uc694./n Sun.com\uc5d0\uc11c \uad6c\ud560 \uc218 \uc788\uc5b4\uc694.
+error.3d=3D \ud45c\uc2dc\ud558\ub2e4\uac00 \uc5d0\ub7ec\ubc1c\uc0dd
+error.readme.notfound=Readme \ud30c\uc77c\uc744 \ubabb \ucc3e\uc74c
+error.osmimage.dialogtitle=\uc9c0\ub3c4 \uc774\ubbf8\uc9c0 \ubd88\ub7ec\uc624\uae30\uc911 \uc624\ub958
+error.osmimage.failed=\uc9c0\ub3c4 \uc774\ubbf8\uc9c0 \ubd88\ub7ec\uc624\uae30 \uc2e4\ud328./n\uc778\ud130\ub137 \uc5f0\uacb0\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.
+error.language.wrongfile=\uc120\ud0dd\ud558\uc2e0 \ud30c\uc77c\uc774 Prune\uc744 \uc704\ud55c \uc5b8\uc5b4\ud30c\uc77c\uc774 \uc544\ub2cc\uac70 \uac19\uc544\uc694.
+error.convertnamestotimes.nonames=\uc774\ub984\uc744 \uc2dc\uac04\uc73c\ub85c \ubcc0\ud658 \ud560 \uc218 \uc5c6\uc5b4\uc694./n(\uacbd\uc720\uc9c0\uc774\ub984\uc744 \ucc3e\uc9c0 \ubabb\ud588\uac70\ub098 \ubcc0\ud658\ud560 \uc218 \uc5c6\ub294 \uacbd\uc6b0\uc5d0\uc694.)
+error.lookupsrtm.nonefound=\uc774 \uc9c0\uc810\ub4e4\uc5d0\uc11c \uace0\ub3c4\uac12\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc5b4\uc694./n\uc774 \uc601\uc5ed\uc5d0\uc11c \ud0c0\uc77c\uc774 \uc5c6\uac70\ub098, \uc790\ub8cc\uac00 \uc54c\uc218 \uc5c6\ub294 \uac83\ub4e4\uc744 \ud3ec\ud568\ud558\uace0 \uc788\ub294 \uacbd\uc720.
+error.lookupsrtm.nonerequired=\ubaa8\ub4e0 \uc9c0\uc810\uc774 \uace0\ub3c4 \uc815\ubcf4\uac00 \uc788\uc5b4\uc11c, \ucc3e\uc744\uac8c \uc544\ubb34\uac83\ub3c4 \uc5c6\ub124\uc694.
+error.gpsies.uploadnotok=gpsies \uc11c\ubc84\uac00 \uba54\uc138\uc9c0\ub97c \ub2e4\uc74c\uacfc \uac19\uc740 \ub3cc\ub824\uc90d\ub2c8\ub2e4
+error.gpsies.uploadfailed=\ub2e4\uc74c\uacfc \uac19\uc740 \uc774\uc720\ub85c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4
+error.playaudiofailed=\uc18c\ub9ac\ud30c\uc77c \uc7ac\uc0dd \uc2e4\ud328
index f7b7578ccd2401b2d1f5fa53bddc0216d238d825..162b160f1c539255f5f8e6b0b9d5ed63af7f1528 100644 (file)
@@ -30,9 +30,7 @@ menu.point.editpoint=Wijzig punt
 menu.point.deletepoint=Verwijder punt
 menu.photo=Foto
 menu.photo.saveexif=Opslaan naar Exif
-menu.photo.connect=Aan punt vastzetten
-menu.photo.disconnect=Van punt losmaken
-menu.photo.delete=Verwijder foto
+menu.audio=Audio
 menu.view=Bekijken
 menu.view.showsidebars=Toon panelen
 menu.view.browser=Kaart in browser
@@ -49,6 +47,7 @@ menu.map.zoomin=Zoom +
 menu.map.zoomout=Zoom -
 menu.map.zoomfull=Zoom alles
 menu.map.newpoint=Maak nieuw punt
+menu.map.drawpoints=Maak een serie punten
 menu.map.connect=Verbind route punten
 menu.map.autopan=Autopan
 menu.map.showmap=Toon kaart
@@ -61,6 +60,7 @@ altkey.menu.range=E
 altkey.menu.point=P
 altkey.menu.view=B
 altkey.menu.photo=F
+altkey.menu.audio=A
 altkey.menu.settings=I
 altkey.menu.help=H
 
@@ -99,14 +99,27 @@ function.setpaths=Instellen programmapaden
 function.getgpsies=Routes van Gpsies ophalen
 function.uploadgpsies=Upload routes naar Gpsies
 function.lookupsrtm=Hoogtes van SRTM ophalen
+function.getwikipedia=Wikipedia artikelen uit de buurt ophalen
+function.searchwikipedianames=Wikipedia zoeken op naam
+function.downloadosm=Downloaden OSM data voor gebied
 function.duplicatepoint=Dupliceer punt
 function.setcolours=Instellen kleuren
+function.setlinewidth=Instellen lijndikte
 function.setlanguage=Instellen taal
+function.connecttopoint=Aan punt vastzetten
+function.disconnectfrompoint=Van punt losmaken
+function.removephoto=Verwijder foto
 function.correlatephotos=Correleer foto's
 function.rearrangephotos=Foto's herschikken
 function.rotatephotoleft=Roteer foto linksom
 function.rotatephotoright=Roteer foto rechtsom
+function.photopopup=Toon foto in pop-up
 function.ignoreexifthumb=Negeer exif thumbnail
+function.loadaudio=Toevoegen audiobestanden
+function.removeaudio=Verwijder audiobestand
+function.correlateaudios=Correleeer audiobestanden
+function.playaudio=Afspelen audiobestand
+function.stopaudio=Stop audiobestand
 function.help=Help
 function.showkeys=Toon sneltoetsen
 function.about=Over Prune
@@ -138,7 +151,7 @@ dialog.openoptions.deliminfo.records=bestanden, met
 dialog.openoptions.deliminfo.fields=velden
 dialog.openoptions.deliminfo.norecords=Geen bestanden
 dialog.openoptions.altitudeunits=Hoogte eenheden
-dialog.open.contentsdoubled=Dit bestand bevat twee kopie\u00ebn van ieder punt,\neenkeer als als waypoint en eenkeer als punt
+dialog.open.contentsdoubled=Dit bestand bevat twee kopie\u00ebn van ieder punt,\neen keer als waypoint en een keer als punt
 dialog.selecttracks.intro=Selecteer route of routes om te laden
 dialog.selecttracks.noname=Onbenoemd
 dialog.jpegload.subdirectories=Submappen meenemen
@@ -183,7 +196,7 @@ dialog.exportpov.cameray=Camera Y
 dialog.exportpov.cameraz=Camera Z
 dialog.exportpov.modelstyle=Model stijl
 dialog.exportpov.ballsandsticks=Balletjes en stokjes
-dialog.exportpov.tubesandwalls=Tubes en muren
+dialog.exportpov.tubesandwalls=Buizen en muren
 dialog.exportpov.warningtracksize=Deze route heeft een groot aantal punten. Java3D kan deze mogelijk niet tonen.\nWeet u zeker dat u door wilt gaan?
 dialog.exportsvg.text=Selecteer de camera hoeken voor SVG export
 dialog.exportsvg.phi=Azimut hoek \u03d5
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Sla de volgende punttypen op:
 dialog.pointtype.track=Routepunten
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Fotopunten
+dialog.pointtype.audio=Audiopunten
 dialog.pointtype.selection=Alleen selectie
 dialog.confirmreversetrack.title=Bevestig omkering
 dialog.confirmreversetrack.text=Deze route bevat tijd-informatie die niet meer klopt na een omkering.\nWeet u zeker dat u deze sectie wilt omkeren?
@@ -274,16 +288,18 @@ dialog.gpsies.activity.trekking=Trekking
 dialog.gpsies.activity.walking=Lopen
 dialog.gpsies.activity.jogging=Hardlopen
 dialog.gpsies.activity.biking=Fietsen
-dialog.gpsies.activity.motorbiking=Motor
-dialog.gpsies.activity.snowshoe=Sneeuwschoen
+dialog.gpsies.activity.motorbiking=Motorrijden
+dialog.gpsies.activity.snowshoe=Sneeuwschoen-lopen
 dialog.gpsies.activity.sailing=Zeilen
 dialog.gpsies.activity.skating=Skating
+dialog.wikipedia.column.name=Artikelnaam
+dialog.wikipedia.column.distance=Afstand
 dialog.correlate.notimestamps=Er zit geen tijdinformatie in de punten, dus kunnen ze niet aan foto's gekoppeld worden.
 dialog.correlate.nouncorrelatedphotos=Er zijn geen ongekoppelde foto's.\nWeet u zeker dat u wilt doorgaan?
 dialog.correlate.photoselect.intro=Selecteer \u00e9\u00e9n van deze gekoppelde foto's om het tijdsverschil te gebruiken
-dialog.correlate.photoselect.photoname=Fotonaam
-dialog.correlate.photoselect.timediff=Tijdsverschil
-dialog.correlate.photoselect.photolater=Foto later
+dialog.correlate.select.photoname=Fotonaam
+dialog.correlate.select.timediff=Tijdsverschil
+dialog.correlate.select.photolater=Foto later
 dialog.correlate.options.tip=Tip: Door handmatig een foto te koppelen kan het tijdsverschil voor u berekend worden.
 dialog.correlate.options.intro=Selecteer de opties voor automatisch koppelen
 dialog.correlate.options.offsetpanel=Tijdverschil
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=uren,
 dialog.correlate.options.offset.minutes=minuten en
 dialog.correlate.options.offset.seconds=seconden
 dialog.correlate.options.photolater=Foto later dan punt
-dialog.correlate.options.pointlater=Punt later dan foto
+dialog.correlate.options.pointlaterphoto=Punt later dan foto
+dialog.correlate.options.audiolater=Audio later dan punt
+dialog.correlate.options.pointlateraudio=Punt later dan audio
 dialog.correlate.options.limitspanel=Koppelingslimieten
 dialog.correlate.options.notimelimit=Geen tijdslimiet
 dialog.correlate.options.timelimit=Tijdslimiet
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Geen afstandlimiet
 dialog.correlate.options.distancelimit=Afstandlimeit
 dialog.correlate.options.correlate=Koppelen
 dialog.correlate.alloutsiderange=Alle foto's zijn buiten het bereik van de route en kunnen dus niet gekoppeld worden.\nPas het tijdsverschil aan, of maak minimaal \u00e9\u00e9n koppeling handmatig.
+dialog.correlate.filetimes=Tijdstip bestand geeft aan:
+dialog.correlate.filetimes2=van audiobestand
+dialog.correlate.correltimes=Voor correleren, gebruik:
+dialog.correlate.timestamp.beginning=Begin
+dialog.correlate.timestamp.middle=Midden
+dialog.correlate.timestamp.end=Einde
+dialog.correlate.audioselect.intro=Gebruk \u00e9\u00e9n van deze gecorreleerde audiobestanden als tijdsveschil
+dialog.correlate.select.audioname=Naam audiobestsnd
+dialog.correlate.select.audiolater=Audio later
 dialog.rearrangephotos.desc=Selecteer doel en sorteervolgorde van de foto punten
 dialog.rearrangephotos.tostart=Naar begin
 dialog.rearrangephotos.toend=Naar einde
@@ -380,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Kaartcache
 dialog.saveconfig.prune.kmzimagewidth=KMZ afbeelding breedte
 dialog.saveconfig.prune.kmzimageheight=KMZ afbeelding hoogte
 dialog.saveconfig.prune.colourscheme=Kleurenschema
+dialog.saveconfig.prune.linewidth=Lijndikte
 dialog.saveconfig.prune.kmltrackcolour=KML routekleur
 dialog.setpaths.intro=Indien nodig kan je de paden naar externe applicaties kiezen:
 dialog.setpaths.found=Pad gevonden?
@@ -409,10 +437,12 @@ dialog.diskcache.dir=Cache map
 dialog.diskcache.createdir=Cre\u00eber map
 dialog.diskcache.nocreate=Cache map niet aangemaakt
 dialog.deletefieldvalues.intro=Selecteer het te verwijderen veld voor de huidige reeks
+dialog.setlinewidth.text=Geef lijndikte voor routes (1-4)
+dialog.downloadosm.desc=Bevestig het downloaden van ruwe OSM data voor dit gebied:
+dialog.searchwikipedianames.search=Zoeken naar:
 
 # 3d window
 dialog.3d.title=Prune in 3D
-dialog.3d.altitudecap=Minimale hoogte
 dialog.3d.altitudefactor=Hoogte overdrijvingsfactor
 dialog.3dlines.title=Prune raster
 dialog.3dlines.empty=Geen raster om af te beelden
@@ -439,16 +469,21 @@ confirm.undo.single=Actie geannuleerd
 confirm.undo.multi=Acties geannuleerd
 confirm.jpegload.single=foto was toegevoegd
 confirm.jpegload.multi=foto's waren toegevoegd
-confirm.photo.connect=foto's gekoppeld
+confirm.media.connect=Media gekoppeld
 confirm.photo.disconnect=foto's ontkoppeld
-confirm.correlate.single=Foto was gecorreleerd
-confirm.correlate.multi=Foto's waren gecorreleerd
+confirm.audio.disconnect=audio ontkoppeld
+confirm.media.removed=verwijderd
+confirm.correlatephotos.single=Foto was gecorreleerd
+confirm.correlatephotos.multi=Foto's waren gecorreleerd
 confirm.createpoint=punt aangemaakt
-confirm.rotatephoto=foto rgeroteerd
+confirm.rotatephoto=foto geroteerd
 confirm.running=Bezig...
 confirm.lookupsrtm1=Gevonden
 confirm.lookupsrtm2=hoote waarden
 confirm.deletefieldvalues=Veldwaarden gewist
+confirm.audioload=Audiobestanden toegevoegd
+confirm.correlateaudios.single=audiobestand gecorreleerd
+confirm.correlateaudios.multi=audiobestanden gecorreleerd
 
 # Buttons
 button.ok=OK
@@ -473,6 +508,7 @@ button.selectall=Selecteer alles
 button.selectnone=Selecteer niets
 button.preview=Voorbeeld
 button.load=Laden
+button.upload=Upload
 button.guessfields=Raad velden
 button.showwebpage=Toon webpagina
 button.check=Controleren
@@ -490,6 +526,7 @@ filetype.kmz=KMZ bestand
 filetype.gpx=GPX bestand
 filetype.pov=POV bestand
 filetype.svg=SVG bestand
+filetype.audio=MP3, OGG, WAV bestanden
 
 # Display components
 display.nodata=Geen gegevens geladen
@@ -524,12 +561,17 @@ details.range.maxspeed=Max snelheid
 details.range.numsegments=Aantal segmenten
 details.range.pace=Tempo
 details.range.gradient=Helling
-details.waypointsphotos.waypoints=Waypoints
-details.waypointsphotos.photos=Foto
+details.lists.waypoints=Waypoints
+details.lists.photos=Foto
+details.lists.audio=Audio
 details.photodetails=Foto details
 details.nophoto=Geen foto geselecteerd
 details.photo.loading=Bezig met laden
-details.photo.connected=Geconnecteerd
+details.media.connected=Geconnecteerd
+details.audiodetails=Audio details
+details.noaudio=Geen audiobestand geselecteerd
+details.audio.file=Audiobestand
+details.audio.playing=Afspelen...
 map.overzoom=Geen kaarten beschikbaar op dit zoom-niveau
 
 # Field names
@@ -572,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.nl
+wikipedia.lang=nl
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -582,27 +625,30 @@ cardinal.w=W
 # Undo operations
 undo.load=gegevens laden
 undo.loadphotos=foto's laden
+undo.loadaudios=audiobestanden laden
 undo.editpoint=wijzigen punt
 undo.deletepoint=verwijderen punt
-undo.deletephoto=verwijderen foto
+undo.removephoto=verwijderen foto
+undo.removeaudio=verwijderen audiobestand
 undo.deleterange=verwijderen reeks
 undo.compress=comprimeren route
 undo.insert=punten invoegen
 undo.reverse=reeks omkeren
-undo.mergetracksegments=Samenvoegen route segmenten
-undo.addtimeoffset=Tijdsverschil toevoegen
-undo.addaltitudeoffset=Hoogteverschil toevoegen
-undo.rearrangewaypoints=Herschikken waypoint
+undo.mergetracksegments=samenvoegen route segmenten
+undo.addtimeoffset=tijdsverschil toevoegen
+undo.addaltitudeoffset=hoogteverschil toevoegen
+undo.rearrangewaypoints=herschikken waypoint
 undo.cutandmove=Verschuif sectie
-undo.connectphoto=Koppel foto
-undo.disconnectphoto=Loskoppelen foto
-undo.correlate=Correleer foto
-undo.rearrangephotos=Herschikken foto's
-undo.createpoint=Cre\u00eber punt
-undo.rotatephoto=Roteer foto
-undo.convertnamestotimes=Converteer namen naar tijden
-undo.lookupsrtm=Opzoeken hoogtes in SRTM
-undo.deletefieldvalues=Verwijder veldwaarden
+undo.connect=koppel
+undo.disconnect=loskoppelen
+undo.correlatephotos=correleer foto
+undo.rearrangephotos=herschikken foto's
+undo.createpoint=cre\u00eber punt
+undo.rotatephoto=roteer foto
+undo.convertnamestotimes=converteer namen naar tijden
+undo.lookupsrtm=opzoeken hoogtes in SRTM
+undo.deletefieldvalues=verwijder veldwaarden
+undo.correlateaudios=correleer audiobestanden
 
 # Error messages
 error.save.dialogtitle=Fout bij opslaan gegevens
@@ -624,9 +670,9 @@ error.load.othererror=Fout bij lezen bestand:
 error.jpegload.dialogtitle=Fout bij inlezen foto's
 error.jpegload.nofilesfound=Bestanden niet gevonden
 error.jpegload.nojpegsfound=Geen jpeg-bestanden gevonden
-error.jpegload.noexiffound=Geen EXIF informatie gevonden
 error.jpegload.nogpsfound=Geen GPS informatie gevonden
 error.jpegload.exifreadfailed=Kon geen EXIF informatie inlezen. EXIF informatie kan niet worden gelezen\n zonder interne of externe bibliotheek.
+error.audioload.nofilesfound=Geen audiobestanden gevonden
 error.gpsload.unknown=Onbekende fout
 error.undofailed.title=Terugdraaien mislukt
 error.undofailed.text=Kon actie niet terugdraaien
@@ -644,3 +690,4 @@ error.lookupsrtm.nonefound=Geen hoogtewaarden beschikbaar voor deze punten
 error.lookupsrtm.nonerequired=Alle punten hebben reeds hoogte, er hoeft niets te worden opgezocht.
 error.gpsies.uploadnotok=Gpsies server antwoordde met
 error.gpsies.uploadfailed=De upload is mislukt. Fout
+error.playaudiofailed=Kon audiobestand niet afspelen
index 46ae39b607997526ed220a6efe18b4dab41f7231..378c08db651f17b5810b42d9fa17c5d070b6f68d 100644 (file)
@@ -30,9 +30,7 @@ menu.point.editpoint=Edytuj punkt
 menu.point.deletepoint=Usu\u0144 punkt
 menu.photo=Zdj\u0119cie
 menu.photo.saveexif=Zapisz Exif
-menu.photo.connect=Przy\u0142\u0105cz do punktu
-menu.photo.disconnect=Od\u0142\u0105cz od punktu
-menu.photo.delete=Usu\u0144 zdj\u0119cie
+menu.audio=Audio
 menu.view=Widok
 menu.view.showsidebars=Poka\u017c boczne panele
 menu.view.browser=Mapa w przegl\u0105darce
@@ -49,6 +47,7 @@ menu.map.zoomin=Powi\u0119ksz
 menu.map.zoomout=Pomniejsz
 menu.map.zoomfull=Dostosuj powi\u0119kszenie
 menu.map.newpoint=Stw\u00f3rz nowy punkt
+menu.map.drawpoints=Stw\u00f3rz seri\u0119 punkt\u00f3w
 menu.map.connect=Po\u0142\u0105cz punkty \u015bcie\u017cki
 menu.map.autopan=Przesuwanie mapy
 menu.map.showmap=Poka\u017c map\u0119
@@ -61,6 +60,7 @@ altkey.menu.range=Z
 altkey.menu.point=U
 altkey.menu.view=W
 altkey.menu.photo=Z
+altkey.menu.audio=A
 altkey.menu.settings=T
 altkey.menu.help=M
 
@@ -99,14 +99,27 @@ function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
 function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
 function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
 function.lookupsrtm=Pobierz wysoko\u015bci z SRTM
+function.getwikipedia=Szukaj w Wikipedii o okolicy
+function.searchwikipedianames=Szukaj nazwy w Wikipedii
+function.downloadosm=Za\u0142aduj dane obszaru z OSM
 function.duplicatepoint=Duplikuj plik
 function.setcolours=Ustaw kolory
+function.setlinewidth=Ustaw szeroko\u015b\u0107 linii
 function.setlanguage=Zmie\u0144 j\u0119zyk
+function.connecttopoint=Przy\u0142\u0105cz do punktu
+function.disconnectfrompoint=Od\u0142\u0105cz od punktu
+function.removephoto=Usu\u0144 zdj\u0119cie
 function.correlatephotos=Powi\u0105\u017c zdj\u0119cia
 function.rearrangephotos=Zmie\u0144 kolejno\u015b\u0107 zdj\u0119\u0107
 function.rotatephotoleft=Obr\u00f3\u0107 zdj\u0119cie w lewo
 function.rotatephotoright=Obr\u00f3\u0107 zdj\u0119cie wprawo
+function.photopopup=Poka\u017c zdj\u0119cie
 function.ignoreexifthumb=Ignoruj miniaturk\u0119 z exif
+function.loadaudio=Dodaj pliki audio
+function.removeaudio=Usu\u0144 pliki audio
+function.correlateaudios=Powi\u0105\u017c audio
+function.playaudio=Odtw\u00f3rz plik audio
+function.stopaudio=Zatrzymaj plik audio
 function.help=Pomoc
 function.showkeys=Poka\u017c klawisze skr\u00f3tu
 function.about=O Prune
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Zapisz punkty nast\u0119puj\u0105cych typ\u00f3w:
 dialog.pointtype.track=punkty \u015bcie\u017cki
 dialog.pointtype.waypoint=punkty po\u015brednie
 dialog.pointtype.photo=punkty zdj\u0119\u0107
+dialog.pointtype.audio=punkty audio
 dialog.pointtype.selection=Tylko wybrane
 dialog.confirmreversetrack.title=Potwierd\u017a odwr\u00f3cenie
 dialog.confirmreversetrack.text=Ta \u015bcie\u017cka zawiera znaczniki czasu, kt\u00f3re po odwr\u00f3ceniu nie b\u0119d\u0105 ustawione w kolejno\u015bci.\nCzy na pewno chcesz odwr\u00f3ci\u0107 ten fragment?
@@ -278,12 +292,14 @@ dialog.gpsies.activity.motorbiking=Wycieczka motocyklowa
 dialog.gpsies.activity.snowshoe=Snowshoeing
 dialog.gpsies.activity.sailing=\u017beglarstwo
 dialog.gpsies.activity.skating=Wrotki/rolki
+dialog.wikipedia.column.name=Tytu\u0142 artyku\u0142u
+dialog.wikipedia.column.distance=Odleg\u0142o\u015b\u0107
 dialog.correlate.notimestamps=Punkty nie maj\u0105 znacznik\u00f3w czasu, nie mo\u017cna ich powi\u0105za\u0107 ze zdj\u0119ciami.
 dialog.correlate.nouncorrelatedphotos=Nie ma nie powi\u0105zanych zdj\u0119\u0107.\nCzy na pewno chcesz kontynuowa\u0107?
 dialog.correlate.photoselect.intro=Wybierz jedno z powi\u0105zanych zdj\u0119\u0107 i u\u017cyj go jako wzorca do przesuni\u0119cia czasu
-dialog.correlate.photoselect.photoname=Nazwa zdj\u0119cia
-dialog.correlate.photoselect.timediff=R\u00f3\u017cnica czasowa
-dialog.correlate.photoselect.photolater=P\u00f3\u017aniejsze zdj\u0119cie
+dialog.correlate.select.photoname=Nazwa zdj\u0119cia
+dialog.correlate.select.timediff=R\u00f3\u017cnica czasowa
+dialog.correlate.select.photolater=P\u00f3\u017aniejsze zdj\u0119cie
 dialog.correlate.options.tip=Porada: Gdy powi\u0105\u017cesz r\u0119cznie przynajmniej jedno zdj\u0119cie, r\u00f3\u017cnica czasowa zostanie policzona automatycznie.
 dialog.correlate.options.intro=Wybierz opcje dla automatycznej korelacji
 dialog.correlate.options.offsetpanel=Przesuni\u0119cie czasowe
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=godzin,
 dialog.correlate.options.offset.minutes=minut i
 dialog.correlate.options.offset.seconds=sekund
 dialog.correlate.options.photolater=Zdj\u0119cie p\u00f3\u017aniejsze ni\u017c punkt
-dialog.correlate.options.pointlater=Punkt p\u00f3\u017aniejszy ni\u017c zdj\u0119cie
+dialog.correlate.options.pointlaterphoto=Punkt p\u00f3\u017aniejszy ni\u017c zdj\u0119cie
+dialog.correlate.options.audiolater=Plik audio p\u00f3\u017aniejszy ni\u017c zdj\u0119cie
+dialog.correlate.options.pointlateraudio=Punkt p\u00f3\u017aniejszy ni\u017c plik audio
 dialog.correlate.options.limitspanel=Ograniczenia korelacji
 dialog.correlate.options.notimelimit=Bez limitu czasu
 dialog.correlate.options.timelimit=Limit czasu
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Bez limitu odleg\u0142o\u015bci
 dialog.correlate.options.distancelimit=Limit odleg\u0142o\u015bci
 dialog.correlate.options.correlate=Powi\u0105\u017c ze sob\u0105
 dialog.correlate.alloutsiderange=Wszystkie zdj\u0119cia s\u0105 poza zakresem czasu \u015bcie\u017cki, tak \u017ce \u017cadne nie mo\u017ce zosta\u0107 z ni\u0105 skorelowane.\nSpr\u00f3buj zmieni\u0107 przesuni\u0119cie lub r\u0119cznie skoreluj przynajmniej jedno zdj\u0119cie.
+dialog.correlate.filetimes=Czas pliku oznacza:
+dialog.correlate.filetimes2=pliku audio
+dialog.correlate.correltimes=Do korelacji u\u017cyj:
+dialog.correlate.timestamp.beginning=pocz\u0105tku
+dialog.correlate.timestamp.middle=\u015brodka
+dialog.correlate.timestamp.end=ko\u0144ca
+dialog.correlate.audioselect.intro=Wybierz jeden z powi\u0105zanych plik\u00f3w audio i u\u017cyj go jako wzorca do przesuni\u0119cia czasu
+dialog.correlate.select.audioname=nazwa pliku audio
+dialog.correlate.select.audiolater=p\u00f3\u017aniejszy plik audio
 dialog.rearrangephotos.desc=Wybierz przeznaczenie i porz\u0105dek sortowania punkt\u00f3w ze zdj\u0119ciami
 dialog.rearrangephotos.tostart=Przesu\u0144 na pocz\u0105tek
 dialog.rearrangephotos.toend=Przesu\u0144 na koniec
@@ -360,6 +387,8 @@ dialog.checkversion.releasedate2=.
 dialog.checkversion.download=Aby \u015bci\u0105gn\u0105\u0107 now\u0105 wersj\u0119 przejd\u017a na http://activityworkshop.net/software/prune/download.html.
 dialog.keys.intro=U\u017cuwaj nast\u0119puj\u0105cych klawiszy skr\u00f3t\u00f3w zamiast myszki
 dialog.keys.keylist=<table><tr><td>klawisze strza\u0142ek</td><td>Przesuwa map\u0119 w lewo, w prawo, w g\u00f3r\u0119, w d\u00f3\u0142</td></tr><tr><td>Ctrl + lewa, prawa strza\u0142ka</td><td>Wybierz punkt poprzedni lub nast\u0119pny</td></tr><tr><td>Ctrl + strza\u0142ka w g\u00f3r\u0119, w d\u00f3\u0142</td><td>Powi\u0119ksz, pomniejsz</td></tr><tr><td>Del</td><td>Usun bie\u017c\u0105cy punkt</td></tr></table>
+dialog.keys.normalmodifier=Ctrl
+dialog.keys.macmodifier=Command
 dialog.saveconfig.desc=Nast\u0119puj\u0105ce ustawienia mog\u0105 zosta\u0107 zapisane w pliku konfiguracyjnym:
 dialog.saveconfig.prune.trackdirectory=Katalog ze \u015bcie\u017ckami
 dialog.saveconfig.prune.photodirectory=Katalog ze zdj\u0119ciami
@@ -378,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Pami\u0119\u0107 podr\u0119czna map
 dialog.saveconfig.prune.kmzimagewidth=szeroko\u015b\u0107 obrazka w KMZ
 dialog.saveconfig.prune.kmzimageheight=wysoko\u015b\u0107 obrazka w KMZ
 dialog.saveconfig.prune.colourscheme=Schemat kolor\u00f3w
+dialog.saveconfig.prune.linewidth=Szeroko\u015b\u0107 linii
 dialog.saveconfig.prune.kmltrackcolour=Kolor \u015bcie\u017cki w pliku KML
 dialog.setpaths.intro=Je\u015bli zachodzi tak potrzeba, mo\u017cesz wybra\u0107 \u015bcie\u017cki do aplikacji zewn\u0119trznych
 dialog.setpaths.found=Znalezione \u015bcie\u017cki?
@@ -407,10 +437,12 @@ dialog.diskcache.dir=katalog pami\u0119ci podr\u0119cznej
 dialog.diskcache.createdir=stw\u00f3rz katalog
 dialog.diskcache.nocreate=Nie utworzono katalogu pami\u0119ci podr\u0119cznej
 dialog.deletefieldvalues.intro=Wybierz pola do skasowania z wybranego zakresu
+dialog.setlinewidth.text=Wprowad\u017a grubo\u015b\u0107 linii do rysowania \u015bcie\u017cek
+dialog.downloadosm.desc=Potwierd\u017a \u015bci\u0105gni\u0119cie danych dla tego obszaru z OSM:
+dialog.searchwikipedianames.search=Szukaj
 
 # 3d window
 dialog.3d.title=Prune widok tr\u00f3jwymiarowy
-dialog.3d.altitudecap=Minimalny zakres wysoko\u015bci
 dialog.3d.altitudefactor=Wsp\u00f3\u0142czynnik skalowania wysoko\u015bci
 dialog.3dlines.title=Linie siatki
 dialog.3dlines.empty=Brak siatki do wy\u015bwietlenia!
@@ -437,16 +469,21 @@ confirm.undo.single=cofni\u0119to operacj\u0119
 confirm.undo.multi=operacje zosta\u0142y cofni\u0119te
 confirm.jpegload.single=dodano zdj\u0119cie
 confirm.jpegload.multi=zdj\u0119\u0107 dodano
-confirm.photo.connect=przy\u0142\u0105czono zdj\u0119cie
+confirm.media.connect=przy\u0142\u0105czono
 confirm.photo.disconnect=od\u0142\u0105czono zdj\u0119cie
-confirm.correlate.single=zdj\u0119cie zosta\u0142o po\u0142\u0105czone
-confirm.correlate.multi=zdj\u0119cia zosta\u0142y po\u0142\u0105czone
+confirm.audio.disconnect=od\u0142\u0105czono
+confirm.media.removed=usuni\u0119to
+confirm.correlatephotos.single=zdj\u0119cie zosta\u0142o po\u0142\u0105czone
+confirm.correlatephotos.multi=zdj\u0119cia zosta\u0142y po\u0142\u0105czone
 confirm.createpoint=stworzono punkt
 confirm.rotatephoto=obr\u00f3cono zdj\u0119cie
 confirm.running=Przetwarzam dane ...
 confirm.lookupsrtm1=Znaleziono
 confirm.lookupsrtm2=warto\u015bci wysoko\u015bci
 confirm.deletefieldvalues=Warto\u015bci p\u00f3l usuni\u0119to
+confirm.audioload=dodano
+confirm.correlateaudios.single=audio zosta\u0142o po\u0142\u0105czone
+confirm.correlateaudios.multi=audio zosta\u0142y po\u0142\u0105czone
 
 # Buttons
 button.ok=OK
@@ -489,6 +526,7 @@ filetype.kmz=Pliki KMZ
 filetype.gpx=Pliki GPX
 filetype.pov=Pliki POV
 filetype.svg=Pliki SVG
+filetype.audio=Pliki MP3, OGG, WAV
 
 # Display components
 display.nodata=Nie za\u0142adowano danych
@@ -523,12 +561,17 @@ details.range.maxspeed=Pr\u0119dko\u015b\u0107 maksymalna
 details.range.numsegments=Liczba segment\u00f3w
 details.range.pace=Tempo
 details.range.gradient=Nachylenie
-details.waypointsphotos.waypoints=Punkty po\u015brednie
-details.waypointsphotos.photos=Zdj\u0119cia
+details.lists.waypoints=Punkty po\u015brednie
+details.lists.photos=Zdj\u0119cia
+details.lists.audio=Audio
 details.photodetails=Szczeg\u00f3\u0142y zdj\u0119cia
 details.nophoto=Brak zaznaczonego zdj\u0119cia
 details.photo.loading=Wczytywanie
-details.photo.connected=Pod\u0142\u0105czony
+details.media.connected=Pod\u0142\u0105czony
+details.audiodetails=Szczeg\u00f3\u0142y audio
+details.noaudio=Brak zaznaczonego audio
+details.audio.file=Plik audio
+details.audio.playing=odtwarzam...
 map.overzoom=Brak map dla danego powi\u0119kszenia
 
 # Field names
@@ -571,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.pl
+wikipedia.lang=pl
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -581,9 +625,11 @@ cardinal.w=W
 # Undo operations
 undo.load=za\u0142aduj dane
 undo.loadphotos=za\u0142aduj zdj\u0119cia
+undo.loadaudios=za\u0142aduj audio
 undo.editpoint=edycja punktu
 undo.deletepoint=usu\u0144 punkt
-undo.deletephoto=usu\u0144 zdj\u0119cie (nie z dysku)
+undo.removephoto=usu\u0144 zdj\u0119cie (nie z dysku)
+undo.removeaudio=usu\u0144 audio
 undo.deleterange=usu\u0144 zakres
 undo.compress=skompresuj \u015bcie\u017ck\u0119
 undo.insert=wstaw punkty
@@ -593,15 +639,16 @@ undo.addtimeoffset=dodaj przesuni\u0119cie czasowe
 undo.addaltitudeoffset=dodaj przeuni\u0119cie wysoko\u015bci
 undo.rearrangewaypoints=przestaw punkty po\u015brednie
 undo.cutandmove=przesu\u0144 fragment
-undo.connectphoto=do\u0142\u0105cz zdj\u0119cie
-undo.disconnectphoto=od\u0142\u0105cz zdj\u0119cie
-undo.correlate=po\u0142\u0105cz ze zdj\u0119ciami
+undo.connect=do\u0142\u0105cz
+undo.disconnect=od\u0142\u0105cz
+undo.correlatephotos=po\u0142\u0105cz ze zdj\u0119ciami
 undo.rearrangephotos=zmie\u0144 kolejno\u015b\u0107 zdj\u0119\u0107
 undo.createpoint=stw\u00f3rz punkt
 undo.rotatephoto=obr\u00f3\u0107 zdj\u0119cie
 undo.convertnamestotimes=zamie\u0144 nazwy punkt\u00f3w
 undo.lookupsrtm=szukaj wysoko\u015bci w SRTM
 undo.deletefieldvalues=usu\u0144 warto\u015bci p\u00f3l
+undo.correlateaudios=skoreluj audio
 
 # Error messages
 error.save.dialogtitle=B\u0142\u0105d zapisu danych
@@ -623,9 +670,9 @@ error.load.othererror=B\u0142\u0105d czytania pliku:
 error.jpegload.dialogtitle=B\u0142\u0105d \u0142adowania zdj\u0119cia
 error.jpegload.nofilesfound=Nie znaleziono plik\u00f3w
 error.jpegload.nojpegsfound=Nie znaleziono plik\u00f3w jpeg
-error.jpegload.noexiffound=Nie znaleziono informacji EXIF
 error.jpegload.nogpsfound=Nie znaleziono informacji GPS
 error.jpegload.exifreadfailed=Nie powiod\u0142o si\u0119 odczytanie informacji EXIF\nInformacji tych nie mo\u017cna przeczyta\u0107 bez wewn\u0119trznej lub zewn\u0119trznej biblioteki.
+error.audioload.nofilesfound=Nie znaleziono plik\u00f3w audio
 error.gpsload.unknown=Nieznany b\u0142\u0105d
 error.undofailed.title=Cofnij nie powiod\u0142o si\u0119
 error.undofailed.text=Nie mo\u017cna cofn\u0105\u0107
@@ -643,3 +690,4 @@ error.lookupsrtm.nonefound=Nie znaleziono danych o wysoko\u015bci.
 error.lookupsrtm.nonerequired=Wszystkie pola maj\u0105 informacj\u0119 o wysoko\u015bci, nie ma czego szuka\u0107
 error.gpsies.uploadnotok=Serwer Gpsies zwr\u00f3ci\u0142 informacj\u0119
 error.gpsies.uploadfailed=B\u0142\u0105d wysy\u0142ania
+error.playaudiofailed=Nie powiod\u0142o si\u0119 odtwarzanie pliku audio
index dc609347d54bad6aea9f3546dc3d317009ff45b0..8596b3900b607670fc6283e7b60c5bf29d608556 100644 (file)
@@ -6,7 +6,7 @@ menu.file=Arquivo
 menu.file.addphotos=Adicionar fotos
 menu.file.save=Salvar
 menu.file.exit=Sair
-menu.track=Track
+menu.track=Rota
 menu.track.undo=Desfazer
 menu.track.clearundo=Limpar lista de desfazer
 menu.track.deletemarked=Remover pontos marcados
@@ -30,9 +30,7 @@ menu.point.editpoint=Editar ponto
 menu.point.deletepoint=Remover ponto
 menu.photo=Foto
 menu.photo.saveexif=Salvar para Exif
-menu.photo.connect=Conectar ao ponto
-menu.photo.disconnect=Desconectar do ponto
-menu.photo.delete=Remover foto
+menu.audio=\u00c1udio
 menu.view=Exibir
 menu.view.showsidebars=Mostrar barras laterais
 menu.view.browser=Mapear no navegador
@@ -49,6 +47,7 @@ menu.map.zoomin=Ampliar
 menu.map.zoomout=Reduzir
 menu.map.zoomfull=Ajustar para tela
 menu.map.newpoint=Criar novo ponto
+menu.map.drawpoints=Criar s\u00e9ries de pontos
 menu.map.connect=Conectar pontos da rota
 menu.map.autopan=Auto-ajustar
 menu.map.showmap=Mostrar o mapa
@@ -56,11 +55,12 @@ menu.map.showscalebar=Mostrar barra de escala
 
 # Alt keys for menus
 altkey.menu.file=A
-altkey.menu.track=T
+altkey.menu.track=R
 altkey.menu.range=I
 altkey.menu.point=P
 altkey.menu.view=X
 altkey.menu.photo=F
+altkey.menu.audio=U
 altkey.menu.settings=C
 altkey.menu.help=J
 
@@ -99,14 +99,27 @@ function.setpaths=Definir caminhos do programa
 function.getgpsies=Obter rotas Gpsies
 function.uploadgpsies=Enviar rotas para o Gpsies
 function.lookupsrtm=Obter altitudes a partir do SRTM
+function.getwikipedia=Obter artigos da Wikipedia das redondezas
+function.searchwikipedianames=Procurar na Wikipedia por nome
+function.downloadosm=Baixar dados OSM para a \u00e1rea
 function.duplicatepoint=Duplicar ponto
 function.setcolours=Definir cores
+function.setlinewidth=Definir espessura da linha
 function.setlanguage=Definir idioma
+function.connecttopoint=Conectar ao ponto
+function.disconnectfrompoint=Desconectar do ponto
+function.removephoto=Remover foto
 function.correlatephotos=Correlacionar fotos
 function.rearrangephotos=Rearrumar fotos
 function.rotatephotoleft=Roda foto \u00e0 esquerda
 function.rotatephotoright=Roda foto \u00e0 direita
+function.photopopup=Mostrar janela da foto
 function.ignoreexifthumb=Ignorar miniatura do exif
+function.loadaudio=Adicionar arquivos de \u00e1udio
+function.removeaudio=Remover arquivo de \u00e1udio
+function.correlateaudios=Correlacionar \u00e1udios
+function.playaudio=Reproduzir arquivo de \u00e1udio
+function.stopaudio=Parar arquivo de \u00e1udio
 function.help=Ajuda
 function.showkeys=Mostrar atalhos de teclado
 function.about=Sobre o Prune
@@ -193,6 +206,7 @@ dialog.pointtype.desc=Salvar os seguintes tipos de ponto:
 dialog.pointtype.track=Pontos de rotas
 dialog.pointtype.waypoint=Pontos
 dialog.pointtype.photo=Pontos de foto
+dialog.pointtype.audio=Pontos de \u00e1udio
 dialog.pointtype.selection=Apenas sele\u00e7\u00e3o
 dialog.confirmreversetrack.title=Confirmar invers\u00e3o
 dialog.confirmreversetrack.text=Esta rota possui informa\u00e7\u00f5es de data-hora, as quais estar\u00e3o fora de sequ\u00eancia ap\u00f3s a revers\u00e3o.\n Voc\u00ea tem certeza que deseja reverter esta se\u00e7\u00e3o?
@@ -278,12 +292,14 @@ dialog.gpsies.activity.motorbiking=Motocross
 dialog.gpsies.activity.snowshoe=Snowshoeing
 dialog.gpsies.activity.sailing=Sailing
 dialog.gpsies.activity.skating=Patina\u00e7\u00e3o
+dialog.wikipedia.column.name=Nome do artigo
+dialog.wikipedia.column.distance=Dist\u00e2ncia
 dialog.correlate.notimestamps=N\u00e3o existem data-hora nos dados dos pontos, assim n\u00e3o h\u00e1 nada para correlacionar com as fotos
 dialog.correlate.nouncorrelatedphotos=Existem fotos n\u00e3o correlacionadas.\nVoc\u00ea tem certeza que deseja continuar?
 dialog.correlate.photoselect.intro=Selecione uma destas fotos correlacionadas para usar como diferen\u00e7a de tempo
-dialog.correlate.photoselect.photoname=Nome da foto
-dialog.correlate.photoselect.timediff=Diferen\u00e7a de tempo
-dialog.correlate.photoselect.photolater=Foto \u00e9 mais recente
+dialog.correlate.select.photoname=Nome da foto
+dialog.correlate.select.timediff=Diferen\u00e7a de tempo
+dialog.correlate.select.photolater=Foto \u00e9 mais recente
 dialog.correlate.options.tip=Dica: Correlacionando pelo menos uma foto manualmente, a diferen\u00e7a de tempo pode ser calculada para voc\u00ea.
 dialog.correlate.options.intro=Selecione as op\u00e7\u00f5es para correla\u00e7\u00e3o autom\u00e1tica
 dialog.correlate.options.offsetpanel=Diferen\u00e7a de tempo
@@ -292,7 +308,9 @@ dialog.correlate.options.offset.hours=horas.
 dialog.correlate.options.offset.minutes=minutos e
 dialog.correlate.options.offset.seconds=segundos
 dialog.correlate.options.photolater=Foto mais recente que o ponto
-dialog.correlate.options.pointlater=Ponto mais recente que a foto
+dialog.correlate.options.pointlaterphoto=Ponto mais recente que a foto
+dialog.correlate.options.audiolater=\u00e1udio mais recente que o ponto
+dialog.correlate.options.pointlateraudio=Ponto mais recente que a \u00e1udio
 dialog.correlate.options.limitspanel=Limites de correla\u00e7\u00e3o
 dialog.correlate.options.notimelimit=Nenhum limite de tempo
 dialog.correlate.options.timelimit=Limite de tempo
@@ -300,6 +318,15 @@ dialog.correlate.options.nodistancelimit=Nenhum limite de dist\u00e2ncia
 dialog.correlate.options.distancelimit=Limite de dist\u00e2ncia
 dialog.correlate.options.correlate=Correlacionar
 dialog.correlate.alloutsiderange=Todas as fotos est\u00e3o fora do intervalo de tempo da rota, assim nenhuma pode ser correlacionada.\n Tente mudar a diferen\u00e7a de tempo ou manualmente correlacionar pelo menos uma foto.
+dialog.correlate.filetimes=Hora do arquivo denota:
+dialog.correlate.filetimes2=do clipe de \u00e1udio
+dialog.correlate.correltimes=Para correlacionar, usar:
+dialog.correlate.timestamp.beginning=In\u00edcio
+dialog.correlate.timestamp.middle=Meio
+dialog.correlate.timestamp.end=Fim
+dialog.correlate.audioselect.intro=Selecione um destes \u00e1udios correlacionados para usar como intervalo de tempo
+dialog.correlate.select.audioname=Nome do \u00e1udio
+dialog.correlate.select.audiolater=\u00c1udio posterior
 dialog.rearrangephotos.desc=Selecione o destino e a ordena\u00e7\u00e3o dos pontos das fotos
 dialog.rearrangephotos.tostart=Mover para o in\u00edcio
 dialog.rearrangephotos.toend=Mover para o fim
@@ -380,6 +407,7 @@ dialog.saveconfig.prune.diskcache=Cache de mapas
 dialog.saveconfig.prune.kmzimagewidth=Largura da imagem KMZ
 dialog.saveconfig.prune.kmzimageheight=Altura da imagem KMZ
 dialog.saveconfig.prune.colourscheme=Esquema de cores
+dialog.saveconfig.prune.linewidth=Espessura da linha
 dialog.saveconfig.prune.kmltrackcolour=Cor da rota KML
 dialog.setpaths.intro=Se voc\u00ea precisar, voc\u00ea pode escolher os caminhos para as aplica\u00e7\u00f5es externas:
 dialog.setpaths.found=Caminho encontrado?
@@ -409,10 +437,12 @@ dialog.diskcache.dir=Diret\u00f3rio da cache
 dialog.diskcache.createdir=Criar diret\u00f3rio
 dialog.diskcache.nocreate=Diret\u00f3rio da cache n\u00e3o foi criado
 dialog.deletefieldvalues.intro=Selecione o campo a remover para o intervalo atual
+dialog.setlinewidth.text=Insira a espessura das linhas para desenhar as rotas (1-4)
+dialog.downloadosm.desc=Confirmar a transfer\u00eancia de dados OSM brutos para a \u00e1rea especificada:
+dialog.searchwikipedianames.search=Procurar por:
 
 # 3d window
 dialog.3d.title=Vista 3D do Prune
-dialog.3d.altitudecap=Intervalo de altitude m\u00ednimo
 dialog.3d.altitudefactor=Fator de exagera\u00e7\u00e3o de altitude
 dialog.3dlines.title=Linhas da grade do Prune
 dialog.3dlines.empty=Nenhuma linha de grade para exibir!
@@ -439,16 +469,21 @@ confirm.undo.single=opera\u00e7\u00e3o desfeita
 confirm.undo.multi=opera\u00e7\u00f5es desfeitas
 confirm.jpegload.single=foto foi adicionada
 confirm.jpegload.multi=fotos foram adicionadas
-confirm.photo.connect=foto conectada
+confirm.media.connect=m\u00eddia conectada
 confirm.photo.disconnect=foto desconectada
-confirm.correlate.single=foto foi correlacionada
-confirm.correlate.multi=fotos foram correlacionadas
+confirm.audio.disconnect=\u00e1udio desconectado
+confirm.media.removed=removido
+confirm.correlatephotos.single=foto foi correlacionada
+confirm.correlatephotos.multi=fotos foram correlacionadas
 confirm.createpoint=ponto criado
 confirm.rotatephoto=foto rotacionada
 confirm.running=Rodando...
 confirm.lookupsrtm1=Encontrado
 confirm.lookupsrtm2=valores de altitude
 confirm.deletefieldvalues=Valores do campo removidos
+confirm.audioload=Arquivos de \u00e1udio adicionados
+confirm.correlateaudios.single=\u00e1udio foi correlacionado
+confirm.correlateaudios.multi=\u00e1udios foram correlacionados
 
 # Buttons
 button.ok=Ok
@@ -528,10 +563,15 @@ details.range.pace=Passo
 details.range.gradient=Gradiente
 details.lists.waypoints=Pontos
 details.lists.photos=Fotos
+details.lists.audio=\u00c1udio
 details.photodetails=Detalhes da foto
 details.nophoto=Nenhuma foto selecionada
 details.photo.loading=Carregando
-details.photo.connected=Conectada
+details.media.connected=Conectada
+details.audiodetails=Detalhes do \u00e1udio
+details.noaudio=Nenhum arquivo de \u00e1udio selecionado
+details.audio.file=Arquivo de \u00e1udio
+details.audio.playing=reproduzindo...
 map.overzoom=Nenhum mapa dispon\u00edvel neste n\u00edvel de amplia\u00e7\u00e3o
 
 # Field names
@@ -574,6 +614,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.com.br
+wikipedia.lang=pt
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -584,9 +625,11 @@ cardinal.w=O
 # Undo operations
 undo.load=carregar dados
 undo.loadphotos=carregar fotos
+undo.loadaudios=carregar arquivos de \u00e1udio
 undo.editpoint=editar ponto
 undo.deletepoint=remover ponto
-undo.deletephoto=remover foto
+undo.removephoto=remover foto
+undo.removeaudio=remover arquivo de \u00e1udio
 undo.deleterange=remover intervalo
 undo.compress=comprimir rota
 undo.insert=inserir pontos
@@ -596,15 +639,16 @@ undo.addtimeoffset=adicionar diferen\u00e7a de tempo
 undo.addaltitudeoffset=adicionar diferen\u00e7a de altitude
 undo.rearrangewaypoints=rearrumar pontos
 undo.cutandmove=mover se\u00e7\u00e3o
-undo.connectphoto=conectar foto
-undo.disconnectphoto=desconectar foto
-undo.correlate=conectar fotos
+undo.connect=conectar
+undo.disconnect=desconectar
+undo.correlatephotos=conectar fotos
 undo.rearrangephotos=rearrumar fotos
 undo.createpoint=criar ponto
 undo.rotatephoto=rotacionar foto
 undo.convertnamestotimes=converter nomes para tempos
 undo.lookupsrtm=procurar altitudes a partir do STRM
 undo.deletefieldvalues=remover valores do campo
+undo.correlateaudios=\u00e1udios correlacionados
 
 # Error messages
 error.save.dialogtitle=Erro ao salvar dados
@@ -626,9 +670,9 @@ error.load.othererror=Erro ao ler arquivo:
 error.jpegload.dialogtitle=Erro ao carregar fotos
 error.jpegload.nofilesfound=Nenhum arquivo encontrado
 error.jpegload.nojpegsfound=Nenhum arquivo jpeg encontrado
-error.jpegload.noexiffound=Nenhuma informa\u00e7\u00e3o EXIF encontrada
 error.jpegload.nogpsfound=Nenhuma informa\u00e7\u00e3o de GPS encontrada
 error.jpegload.exifreadfailed=Falha ao ler informa\u00e7\u00f5es do EXIF. Nenhuma informa\u00e7\u00e3o do EXIF pode ser lida\nseja na biblioteca interna, seja na externa.
+error.audioload.nofilesfound=Nenhum arquivo de \u00e1udio encontrado
 error.gpsload.unknown=Erro desconhecido
 error.undofailed.title=Falha ao desfazer
 error.undofailed.text=Falha para desfazer opera\u00e7\u00e3o
@@ -646,3 +690,4 @@ error.lookupsrtm.nonefound=Nenhum valor de altitude encontrado
 error.lookupsrtm.nonerequired=Todos os pontos j\u00e1 possuem altitude, assim n\u00e3o h\u00e1 nada a procurar
 error.gpsies.uploadnotok=O servidor Gpsies retornou a mensagem
 error.gpsies.uploadfailed=O envio falhou com o erro
+error.playaudiofailed=Falha ao reproduzir arquivo de \u00e1udio
index ea0d138e1b7e14d314dc1a28a1ad2b35668f2c6c..f6dd8037966f4402e8c9f9e844fb5e90d78ff28f 100644 (file)
@@ -29,9 +29,9 @@ menu.range.start=Seteaza inceputul selectiei
 menu.range.end=Seteaza sfarsitul selectiei
 menu.photo=Foto
 menu.photo.saveexif=Salveaza la Exif
-menu.photo.connect=Conecteaza la punct
-menu.photo.disconnect=Deconecteaza de la punct
-menu.photo.delete=Elimina foto
+function.connecttopoint=Conecteaza la punct
+function.disconnectfrompoint=Deconecteaza de la punct
+function.removephoto=Elimina foto
 menu.view=Vizualizare
 menu.view.browser=Harta in browser
 menu.view.browser.google=Harti Google
index d000f5d68f1d3e13d7297dac90b4608c738ece74..ad0ea20062006078bca5d1d860187d1b99968916 100644 (file)
@@ -30,9 +30,9 @@ menu.range.start=S\u0131ran\u0131n ba\u015fkang\u0131c\u0131 se\u00e7
 menu.range.end=S\u0131ran\u0131n sonu se\u00e7
 menu.photo=Foto
 menu.photo.saveexif=Exif'te kaydet
-menu.photo.connect=Noktaya ba\u011flan
-menu.photo.disconnect=Noktadan kopart
-menu.photo.delete=Fotoyu kald\u0131r
+function.connecttopoint=Noktaya ba\u011flan
+function.disconnectfrompoint=Noktadan kopart
+function.removephoto=Fotoyu kald\u0131r
 menu.view=G\u00f6r\u00fcn\u00fcm
 menu.view.browser=Taray\u0131c\u0131n\u0131n haritas\u0131
 menu.view.browser.google=Google haritalar\u0131
@@ -223,13 +223,13 @@ dialog.gpsies.activity.jogging=Ko\u015fma
 dialog.gpsies.activity.biking=Bisiklet
 dialog.gpsies.activity.sailing=Yelken
 dialog.gpsies.activity.skating=Paten
-dialog.correlate.photoselect.photoname=Foto ad\u0131
-dialog.correlate.photoselect.photolater=Foto sonra
+dialog.correlate.select.photoname=Foto ad\u0131
+dialog.correlate.select.photolater=Foto sonra
 dialog.correlate.options.offset.hours=saat,
 dialog.correlate.options.offset.minutes=dakika ve
 dialog.correlate.options.offset.seconds=saniye
 dialog.correlate.options.photolater=Foto noktadan sonra
-dialog.correlate.options.pointlater=Nokta fotodan sonra
+dialog.correlate.options.pointlaterphoto=Nokta fotodan sonra
 dialog.pastecoordinates.coords=Koordinatlar
 dialog.help.help=Ayr\u0131nt\u0131l\u0131 bilgi ve kullanma k\u0131lavuzu i\u00e7in l\u00fctfen\n http://activityworkshop.net/software/prune/\n sitesinde bak.
 dialog.about.version=S\u00fcr\u00fcm
@@ -290,12 +290,12 @@ dialog.colourchooser.red=K\u0131rm\u0131z\u0131
 dialog.colourchooser.green=Ye\u015fil
 dialog.colourchooser.blue=Mavi
 
-# Confirm messages || These are displayed as confirmation in the status bar
+# Confirm messages
 confirm.save.ok1=Ba\u011far\u0131yla kaydedildi
 confirm.save.ok2=points to file
 confirm.mergetracksegments=\u0130z par\u00e7alar\u0131 birle\u015ftirildi
 
-# Buttons || These are all the texts for buttons
+# Buttons
 button.ok=Tamam
 button.back=Geri
 button.next=\u0130leri
@@ -332,7 +332,7 @@ filetype.gpx=GPX dosyalar\u0131
 filetype.pov=POV dosyalar\u0131
 filetype.svg=SVG dosyalar\u0131
 
-# Display components || These are all for the side panels showing point/range details
+# Display components
 display.nodata=Hergangi veri y\u00fcklenmedi
 display.noaltitudes=Track verisinde y\u00fckseklik bilgisi yok
 details.trackdetails=\u0130z ayr\u0131nt\u0131lar\u0131
@@ -360,12 +360,12 @@ display.range.time.hours=saat
 display.range.time.days=g\u011fn
 details.range.avespeed=Ortalama h\u0131z\u0131
 details.range.avemovingspeed=Ortalama hareketi
-details.waypointsphotos.waypoints=Noktalar
-details.waypointsphotos.photos=Fotolar
+details.lists.waypoints=Noktalar
+details.lists.photos=Fotolar
 details.photodetails=Foto ayr\u0131nt\u0131lar\u0131
 details.nophoto=Herhangi foto se\u00e7ili de\u011fil
 details.photo.loading=Y\u00fckleniyor
-details.photo.connected=Ba\u011fland\u0131
+details.media.connected=Ba\u011fland\u0131
 map.overzoom=Fazla yak\u0131nla\u015ft\u0131n salak - harita g\u00f6r\u00fcnt\u00fclenmiyor
 
 # Field names
@@ -414,12 +414,12 @@ cardinal.s=G
 cardinal.e=D
 cardinal.w=B
 
-# Undo operations || These will be displayed in the undo list after you've performed the operation, to tell you what you did
+# Undo operations
 undo.load=veri y\u00fckle
 undo.loadphotos=fotolar y\u00fckle
 undo.editpoint=noktay\u0131 d\u00fczenle
 undo.deletepoint=noktay\u0131 sil
-undo.deletephoto=foto kald\u0131r
+undo.removephoto=foto kald\u0131r
 undo.deleterange=s\u0131ra sil
 undo.compress=izi s\u0131k\u0131\u015ft\u0131r
 undo.insert=noktalar\u0131 ekle
@@ -429,9 +429,9 @@ undo.addtimeoffset=zaman de\u011fi\u015ftir
 undo.addaltitudeoffset=y\u00fckseklik de\u011fi\u015ftir
 undo.rearrangewaypoints=noktalar\u0131 yeniden diz
 undo.cutandmove=par\u00e7as\u0131 ta\u015f\u0131
-undo.connectphoto=fotoyu ba\u011flan
-undo.disconnectphoto=fotonun ba\u011flant\u0131s\u0131 kes
-undo.correlate=fotolar\u0131n ba\u011fl\u0131la\u015f\u0131m\u0131
+undo.connect=ba\u011flan
+undo.disconnect=ba\u011flant\u0131s\u0131 kes
+undo.correlatephotos=fotolar\u0131n ba\u011fl\u0131la\u015f\u0131m\u0131
 undo.rearrangephotos=fotolar\u0131 yeniden diz
 undo.createpoint=noktay\u0131 olu\u015ftur
 undo.rotatephoto=fotoyu d\u00f6nder
index 9dd050e3ac371d02c61d0f4e490daf89153ea48a..9c8d7b2b285815df984ba3b5b1f94844c2aeea5f 100644 (file)
@@ -30,10 +30,11 @@ menu.point.editpoint=\u7f16\u8f91\u8f68\u8ff9\u70b9
 menu.point.deletepoint=\u5220\u9664\u8f68\u8ff9\u70b9
 menu.photo=\u76f8\u7247
 menu.photo.saveexif=\u5750\u6807\u4fdd\u5b58\u81f3Exif
-menu.photo.connect=\u94fe\u63a5\u76f8\u7247
-menu.photo.disconnect=\u64a4\u9500\u94fe\u63a5
-menu.photo.delete=\u5220\u9664\u7167\u7247
+function.connecttopoint=\u94fe\u63a5\u76f8\u7247
+function.disconnectfrompoint=\u64a4\u9500\u94fe\u63a5
+function.removephoto=\u5220\u9664\u7167\u7247
 menu.view=\u67e5\u770b
+menu.view.showsidebars=\u663e\u793a\u8fb9\u6846
 menu.view.browser=\u5728\u6d4f\u89c8\u5668\u4e2d\u6253\u5f00\u5730\u56fe
 menu.view.browser.google=Google\u5730\u56fe
 menu.view.browser.openstreetmap=Openstreet\u5730\u56fe
@@ -60,11 +61,13 @@ function.sendtogps=\u53d1\u9001\u81f3GPS
 function.exportkml=\u8f93\u51faKML\u6587\u4ef6
 function.exportgpx=\u8f93\u51faGPX\u6587\u4ef6
 function.exportpov=\u8f93\u51faPOV\u6587\u4ef6
+function.exportsvg=\u8f93\u51faSVG\u6587\u4ef6
 function.editwaypointname=\u7f16\u8f91\u822a\u70b9\u540d
 function.compress=\u538b\u7f29\u8f68\u8ff9(\u6807\u793a\u8981\u5220\u9664\u822a\u70b9\uff09
 function.addtimeoffset=\u52a0\u5165\u65f6\u95f4\u5dee
 function.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
 function.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u8f6c\u4e3a\u65f6\u95f4
+function.deletefieldvalues=\u5220\u9664\u533a\u57df\u6570\u503c
 function.findwaypoint=\u67e5\u627e\u822a\u70b9
 function.pastecoordinates=\u8f93\u5165\u65b0\u5750\u6807
 function.charts=\u9ad8\u5ea6\u901f\u5ea6\u56fe\u8868
@@ -75,6 +78,7 @@ function.setmapbg=\u80cc\u666f\u5730\u56fe
 function.setkmzimagesize=\u8bbe\u7f6eKMZ\u56fe\u50cf\u5c3a\u5bf8
 function.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
 function.getgpsies=Gpsies\u8f68\u8ff9
+function.uploadgpsies=\u8f68\u8ff9\u4e0a\u4f20\u5230 Gpsies
 function.lookupsrtm=\u4eceSRTM\u83b7\u5f97\u9ad8\u5ea6\u4fe1\u606f
 function.duplicatepoint=\u590d\u5236\u70b9
 function.setcolours=\u8bbe\u7f6e\u989c\u8272
@@ -116,6 +120,8 @@ dialog.openoptions.deliminfo.fields=\u6570\u636e\u6bb5
 dialog.openoptions.deliminfo.norecords=\u65e0\u7eaa\u5f55
 dialog.openoptions.altitudeunits=\u9ad8\u5ea6\u5355\u4f4d
 dialog.open.contentsdoubled=\u6587\u4ef6\u542b\u6709\u4e24\u5957\u70b9\u4fe1\u606f\uff0c\u4e00\u5957\u822a\u70b9\u4fe1\u606f\u548c\u4e00\u5957\u8f68\u8ff9\u70b9\u4fe1\u606f
+dialog.selecttracks.intro=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u8f68\u8ff9
+dialog.selecttracks.noname=\u672a\u547d\u540d
 dialog.jpegload.subdirectories=\u542b\u6b21\u7ea7\u65b9\u4f4d
 dialog.jpegload.loadjpegswithoutcoords=\u542b\u65e0\u5750\u6807\u70b9\u76f8\u7247
 dialog.jpegload.loadjpegsoutsidearea=\u542b\u533a\u57df\u5916\u76f8\u7247
@@ -160,6 +166,10 @@ dialog.exportpov.modelstyle=\u6a21\u578b\u7c7b\u578b
 dialog.exportpov.ballsandsticks=\u7403\u548c\u6746
 dialog.exportpov.tubesandwalls=\u7ba1\u548c\u5899
 dialog.exportpov.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.exportsvg.text=\u9009\u62e9\u8f93\u51faSVG\u6587\u4ef6\u7684\u53c2\u6570
+dialog.exportsvg.phi=\u963f\u6cfd\u8def\u89d2\u5ea6
+dialog.exportsvg.theta=\u9ad8\u7a0b\u89d2\u5ea6
+dialog.exportsvg.gradients=\u4f7f\u7528\u6e10\u53d8\u8272
 dialog.pointtype.desc=\u4fdd\u5b58\u4e0b\u5217\u70b9\uff1a
 dialog.pointtype.track=\u8f68\u8ff9\u70b9
 dialog.pointtype.waypoint=\u822a\u70b9
@@ -236,7 +246,11 @@ dialog.gpsies.column.length=\u957f\u5ea6
 dialog.gpsies.description=\u63cf\u8ff0
 dialog.gpsies.nodescription=\u65e0\u63cf\u8ff0
 dialog.gpsies.nonefound=\u672a\u627e\u5230\u8f68\u8ff9
-dialog.gpsies.activities=\u6d3b\u52a8,\u9002\u5408
+dialog.gpsies.username=Gpsies\u7f51\u7ad9\u7528\u6237\u540d
+dialog.gpsies.password=Gpsies\u7f51\u7ad9\u5bc6\u7801
+dialog.gpsies.keepprivate=\u8f68\u8ff9\u4fdd\u5bc6\uff08\u975e\u516c\u5f00\uff09
+dialog.gpsies.confirmopenpage=\u6253\u5f00\u4e0a\u4f20\u8f68\u8ff9\u7684\u7f51\u7ad9\uff1f
+dialog.gpsies.activities=\u6d3b\u52a8\u7c7b\u578b
 dialog.gpsies.activity.trekking=\u5f92\u6b65
 dialog.gpsies.activity.walking=\u7ade\u8d70
 dialog.gpsies.activity.jogging=\u8dd1\u6b65
@@ -248,9 +262,9 @@ dialog.gpsies.activity.skating=\u6ed1\u51b0
 dialog.correlate.notimestamps=\u6570\u636e\u70b9\u4e2d\u65e0\u65f6\u95f4\u4fe1\u606f\uff0c\u76f8\u7247\u65e0\u6cd5\u94fe\u63a5
 dialog.correlate.nouncorrelatedphotos=\u6240\u6709\u76f8\u7247\u5df2\u94fe\u63a5\n\u7ee7\u7eed\uff1f
 dialog.correlate.photoselect.intro=\u9009\u62e9\u5df2\u94fe\u63a5\u76f8\u7247\u4f5c\u4e3a\u65f6\u95f4\u504f\u79fb
-dialog.correlate.photoselect.photoname=\u76f8\u7247\u540d
-dialog.correlate.photoselect.timediff=\u65f6\u95f4\u5dee
-dialog.correlate.photoselect.photolater=\u76f8\u7247\u5ef6\u540e
+dialog.correlate.select.photoname=\u76f8\u7247\u540d
+dialog.correlate.select.timediff=\u65f6\u95f4\u5dee
+dialog.correlate.select.photolater=\u76f8\u7247\u5ef6\u540e
 dialog.correlate.options.tip=\u63d0\u793a\uff1a\u624b\u52a8\u94fe\u63a5\u81f3\u5c11\u4e00\u5f20\u76f8\u7247\uff0c\u53ef\u81ea\u52a8\u8ba1\u7b97\u65f6\u95f4\u504f\u79fb
 dialog.correlate.options.intro=\u9009\u62e9\u81ea\u52a8\u94fe\u63a5\u8bbe\u7f6e
 dialog.correlate.options.offsetpanel=\u65f6\u95f4\u504f\u79fb
@@ -259,7 +273,7 @@ dialog.correlate.options.offset.hours=\u5c0f\u65f6
 dialog.correlate.options.offset.minutes=\u5206\u949f
 dialog.correlate.options.offset.seconds=\u79d2
 dialog.correlate.options.photolater=\u76f8\u7247\u6ede\u540e\u4e8e\u8f68\u8ff9\u70b9
-dialog.correlate.options.pointlater=\u8f68\u8ff9\u70b9\u6ede\u540e\u4e8e\u76f8\u7247
+dialog.correlate.options.pointlaterphoto=\u8f68\u8ff9\u70b9\u6ede\u540e\u4e8e\u76f8\u7247
 dialog.correlate.options.limitspanel=\u94fe\u63a5\u9650\u5236
 dialog.correlate.options.notimelimit=\u65e0\u65f6\u95f4\u9650\u5236
 dialog.correlate.options.timelimit=\u65f6\u95f4\u9650\u5236
@@ -352,6 +366,7 @@ dialog.setpaths.intro=\u82e5\u9700\u8981\uff0c\u53ef\u8bbe\u5b9a\u5916\u6302\u7a
 dialog.setpaths.found=\u627e\u5230\u8def\u5f84\uff1f
 dialog.addaltitude.noaltitudes=\u8f68\u8ff9\u4e0d\u542b\u9ad8\u5ea6\u4fe1\u606f
 dialog.addaltitude.desc=\u9ad8\u5ea6\u504f\u79fb
+dialog.lookupsrtm.overwritezeros=\u8986\u76d6\u9ad8\u5ea6\u503c\u4e3a\u96f6\u7684\u70b9\uff1f
 dialog.setcolours.intro=\u70b9\u6309\u8272\u677f\u4ee5\u6539\u53d8\u989c\u8272
 dialog.setcolours.background=\u80cc\u666f
 dialog.setcolours.borders=\u8fb9\u754c
@@ -374,15 +389,15 @@ dialog.diskcache.save=\u5730\u56fe\u56fe\u7247\u4fdd\u5b58\u5230\u7535\u8111
 dialog.diskcache.dir=\u4fdd\u5b58\u8def\u5f84
 dialog.diskcache.createdir=\u65b0\u5efa\u8def\u5f84
 dialog.diskcache.nocreate=\u672a\u65b0\u5efa\u8def\u5f84
+dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u533a\u57df
 
 # 3d window
 dialog.3d.title=Prune 3D \u663e\u793a
-dialog.3d.altitudecap=\u9ad8\u5ea6\u6bd4\u4f8b
 dialog.3dlines.title=Prune \u7f51\u683c\u7ebf
 dialog.3dlines.empty=\u65e0\u6cd5\u663e\u793a\u7f51\u683c\u7ebf
 dialog.3dlines.intro=3D \u7f51\u683c\u7ebf
 
-# Confirm messages || These are displayed as confirmation in the status bar
+# Confirm messages
 confirm.loadfile=\u6570\u636e\u5df2\u4ece\u6587\u4ef6\u5bfc\u5165
 confirm.save.ok1=\u4fdd\u5b58\u6210\u529f
 confirm.save.ok2=\u5df2\u4fdd\u5b58\u7684\u8f68\u8ff9\u70b9
@@ -403,17 +418,16 @@ confirm.undo.single=\u5df2\u64a4\u9500\u7684\u64cd\u4f5c
 confirm.undo.multi=\u5df2\u64a4\u9500\u7684\u64cd\u4f5c
 confirm.jpegload.single=\u5df2\u52a0\u5165\u76f8\u7247
 confirm.jpegload.multi=\u5df2\u52a0\u5165\u76f8\u7247
-confirm.photo.connect=\u76f8\u7247\u5df2\u94fe\u63a5
 confirm.photo.disconnect=\u76f8\u7247\u672a\u94fe\u63a5
-confirm.correlate.single=\u76f8\u7247\u5df2\u94fe\u63a5
-confirm.correlate.multi=\u76f8\u7247\u5df2\u94fe\u63a5
+confirm.correlatephotos.single=\u76f8\u7247\u5df2\u94fe\u63a5
+confirm.correlatephotos.multi=\u76f8\u7247\u5df2\u94fe\u63a5
 confirm.createpoint=\u5df2\u521b\u5efa\u70b9
 confirm.rotatephoto=\u76f8\u7247\u5df2\u65cb\u8f6c
 confirm.running=\u8bf7\u7a0d\u7b49...
 confirm.lookupsrtm1=\u627e\u5230
 confirm.lookupsrtm2=\u9ad8\u5ea6\u503c
 
-# Buttons || These are all the texts for buttons
+# Buttons
 button.ok=\u786e\u5b9a
 button.back=\u8fd4\u56de
 button.next=\u4e0b\u4e00\u6b65
@@ -453,8 +467,9 @@ filetype.kmz=KMZ\u6587\u4ef6
 filetype.gpx=GPX\u6587\u4ef6
 filetype.pov=POV\u6587\u4ef6
 filetype.svg=SVG\u6587\u4ef6
+filetype.audio=WAV,OGG,MP3\u6587\u4ef6
 
-# Display components || These are all for the side panels showing point/range details
+# Display components
 display.nodata=\u65e0\u6570\u636e
 display.noaltitudes=\u8f68\u8ff9\u6570\u636e\u4e0d\u542b\u9ad8\u5ea6\u4fe1\u606f
 display.notimestamps=\u8f68\u8ff9\u6570\u636e\u672a\u542b\u65f6\u95f4\u4fe1\u606f
@@ -487,12 +502,12 @@ details.range.maxspeed=\u6700\u5927\u901f\u5ea6
 details.range.numsegments=\u6bb5\u6570
 details.range.pace=\u6b65\u901f
 details.range.gradient=\u5761\u5ea6
-details.waypointsphotos.waypoints=\u822a\u70b9
-details.waypointsphotos.photos=\u76f8\u7247
+details.lists.waypoints=\u822a\u70b9
+details.lists.photos=\u76f8\u7247
 details.photodetails=\u76f8\u7247\u4fe1\u606f
 details.nophoto=\u65e0\u76f8\u7247\u88ab\u9009\u4e2d
 details.photo.loading=\u6b63\u5bfc\u5165
-details.photo.connected=\u5df2\u94fe\u63a5
+details.media.connected=\u5df2\u94fe\u63a5
 map.overzoom=\u5728\u6b64\u653e\u5927\u5c3a\u5bf8\u4e0b\u65e0\u5730\u56fe\u8d44\u6599
 
 # Field names
@@ -535,6 +550,7 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=ditu.google.cn
+wikipedia.lang=zh
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -542,12 +558,12 @@ cardinal.s=S
 cardinal.e=E
 cardinal.w=W
 
-# Undo operations || These will be displayed in the undo list after you've performed the operation, to tell you what you did
+# Undo operations
 undo.load=\u5bfc\u5165\u6570\u636e
 undo.loadphotos=\u5bfc\u5165\u76f8\u7247
 undo.editpoint=\u7f16\u8f91\u8f68\u8ff9\u70b9
 undo.deletepoint=\u5220\u9664\u8f68\u8ff9\u70b9
-undo.deletephoto=\u5220\u9664\u76f8\u7247
+undo.removephoto=\u5220\u9664\u76f8\u7247
 undo.deleterange=\u5220\u9664\u6bb5
 undo.compress=\u538b\u7f29\u8f68\u8ff9
 undo.insert=\u63d2\u5165\u822a\u70b9
@@ -557,9 +573,9 @@ undo.addtimeoffset=\u6dfb\u52a0\u65f6\u95f4\u504f\u79fb
 undo.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
 undo.rearrangewaypoints=\u91cd\u65b0\u914d\u7f6e\u822a\u70b9
 undo.cutandmove=\u79fb\u52a8\u6bb5
-undo.connectphoto=\u94fe\u63a5\u76f8\u7247
-undo.disconnectphoto=\u65ad\u5f00\u94fe\u63a5
-undo.correlate=\u94fe\u63a5\u76f8\u7247
+undo.connect=\u94fe\u63a5\u76f8\u7247
+undo.disconnect=\u65ad\u5f00\u94fe\u63a5
+undo.correlatephotos=\u94fe\u63a5\u76f8\u7247
 undo.rearrangephotos=\u91cd\u62cd\u76f8\u7247
 undo.createpoint=\u521b\u5efa\u8f68\u8ff9\u70b9
 undo.rotatephoto=\u65cb\u8f6c\u76f8\u7247
@@ -586,7 +602,6 @@ error.load.othererror=\u8bfb\u6587\u4ef6\u9519\u8bef
 error.jpegload.dialogtitle=\u5bfc\u5165\u76f8\u7247\u9519\u8bef
 error.jpegload.nofilesfound=\u627e\u4e0d\u5230\u6587\u4ef6
 error.jpegload.nojpegsfound=\u627e\u4e0d\u5230Jpeg\u6587\u4ef6
-error.jpegload.noexiffound=\u627e\u4e0d\u5230EXIF\u4fe1\u606f
 error.jpegload.nogpsfound=\u627e\u4e0d\u5230GPS\u4fe1\u606f
 error.jpegload.exifreadfailed=Exif\u8bfb\u53d6\u9519\u8bef\u3002\u9700\u8981\u5185\u90e8\n\u6216\u8005\u5916\u90e8\u5e93\u624d\u80fd\u8bfb\u53d6
 error.gpsload.unknown=\u672a\u77e5\u9519\u8bef
diff --git a/tim/prune/load/AudioFileFilter.java b/tim/prune/load/AudioFileFilter.java
new file mode 100644 (file)
index 0000000..3d8675e
--- /dev/null
@@ -0,0 +1,13 @@
+package tim.prune.load;
+
+/**
+ * File filter for audio files
+ */
+public class AudioFileFilter extends GenericFileFilter
+{
+       /** Constructor */
+       public AudioFileFilter()
+       {
+               super("filetype.audio", new String[] {"mp3", "ogg", "wav"});
+       }
+}
diff --git a/tim/prune/load/AudioLoader.java b/tim/prune/load/AudioLoader.java
new file mode 100644 (file)
index 0000000..1997db2
--- /dev/null
@@ -0,0 +1,99 @@
+package tim.prune.load;
+
+import java.io.File;
+import java.util.TreeSet;
+import javax.swing.JFileChooser;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+import tim.prune.data.AudioFile;
+import tim.prune.undo.UndoLoadAudios;
+
+/**
+ * Class to manage the loading of audio files
+ */
+public class AudioLoader extends GenericFunction
+{
+       private JFileChooser _fileChooser = null;
+       private GenericFileFilter _fileFilter = null;
+       private TreeSet<AudioFile> _fileList = null;
+
+
+       /**
+        * Constructor
+        * @param inApp Application object to inform of data load
+        */
+       public AudioLoader(App inApp)
+       {
+               super(inApp);
+               _fileFilter = new AudioFileFilter();
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.loadaudio";
+       }
+
+       /**
+        * Open the GUI to select options and start the load
+        */
+       public void begin()
+       {
+               // Create file chooser if necessary
+               if (_fileChooser == null)
+               {
+                       _fileChooser = new JFileChooser();
+                       _fileChooser.setMultiSelectionEnabled(true);
+                       _fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+                       _fileChooser.setFileFilter(_fileFilter);
+                       _fileChooser.setDialogTitle(I18nManager.getText(getNameKey()));
+                       // start from directory in config if already set by other operations
+                       String configDir = Config.getConfigString(Config.KEY_PHOTO_DIR);
+                       if (configDir == null) {configDir = Config.getConfigString(Config.KEY_TRACK_DIR);}
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
+               }
+               // Show file dialog to choose file / directory(ies)
+               if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
+               {
+                       _fileList = new TreeSet<AudioFile>(new MediaSorter());
+                       processFileList(_fileChooser.getSelectedFiles());
+                       final int numFiles = _fileList.size();
+                       if (numFiles == 0) {
+                               _app.showErrorMessage(getNameKey(), "error.audioload.nofilesfound");
+                       }
+                       else
+                       {
+                               // Construct undo object
+                               UndoLoadAudios undo = new UndoLoadAudios(numFiles);
+                               _app.getTrackInfo().addAudios(_fileList);
+                               _app.completeFunction(undo, I18nManager.getText("confirm.audioload"));
+                               UpdateMessageBroker.informSubscribers();
+                       }
+               }
+       }
+
+       /**
+        * Process an array of File objects to load
+        * @param inFiles array of selected Files
+        */
+       private void processFileList(File[] inFiles)
+       {
+               for (File file : inFiles)
+               {
+                       if (file.exists() && file.canRead())
+                       {
+                               if (file.isFile()) {
+                                       if (_fileFilter.accept(file)) {
+                                               _fileList.add(new AudioFile(file));
+                                       }
+                               }
+                               else if (file.isDirectory()) {
+                                       processFileList(file.listFiles());
+                               }
+                       }
+               }
+       }
+}
index 3af4d9e05ef704abc0bf95c7506462599a6dbb63..733dee65e214f11e25b7816f1d9c2eca9a8e9b4b 100644 (file)
@@ -31,9 +31,8 @@ public class GenericFileFilter extends FileFilter
                        _threeCharSuffixes = new String[inSuffixes.length];
                        _fourCharSuffixes = new String[inSuffixes.length];
                        int threeIndex = 0, fourIndex = 0;
-                       for (int i=0; i<inSuffixes.length; i++)
+                       for (String suffix : inSuffixes)
                        {
-                               String suffix = inSuffixes[i];
                                if (suffix != null)
                                {
                                        suffix = suffix.trim().toLowerCase();
@@ -66,7 +65,7 @@ public class GenericFileFilter extends FileFilter
        {
                if (inName != null)
                {
-                       int nameLen = inName.length();
+                       final int nameLen = inName.length();
                        if (nameLen > 4)
                        {
                                // Check for three character suffixes
@@ -93,15 +92,14 @@ public class GenericFileFilter extends FileFilter
         * @param inAllowedSuffixes array of allowed suffixes
         * @return true if accepted, false otherwise
         */
-       public boolean acceptFilename(String inSuffixToCheck, String[] inAllowedSuffixes)
+       private static boolean acceptFilename(String inSuffixToCheck, String[] inAllowedSuffixes)
        {
                if (inSuffixToCheck != null && inAllowedSuffixes != null)
                {
                        // Loop over allowed suffixes
-                       for (int i=0; i<inAllowedSuffixes.length; i++)
+                       for (String allowed : inAllowedSuffixes)
                        {
-                               if (inAllowedSuffixes[i] != null && inSuffixToCheck.equals(inAllowedSuffixes[i]))
-                               {
+                               if (allowed != null && inSuffixToCheck.equals(allowed)) {
                                        return true;
                                }
                        }
@@ -119,5 +117,4 @@ public class GenericFileFilter extends FileFilter
        {
                return _filterDesc;
        }
-
 }
diff --git a/tim/prune/load/JpegFileFilter.java b/tim/prune/load/JpegFileFilter.java
new file mode 100644 (file)
index 0000000..d84b9b3
--- /dev/null
@@ -0,0 +1,13 @@
+package tim.prune.load;
+
+/**
+ * File filter for jpegs
+ */
+public class JpegFileFilter extends GenericFileFilter
+{
+       /** Constructor */
+       public JpegFileFilter()
+       {
+               super("filetype.jpeg", new String[] {"jpg", "jpe", "jpeg"});
+       }
+}
index feec754f22f7049dc7bcef4b26d71cba534d2ed2..a6a45c60b5d1ffe56c8257ac29b9f5e3f31e4a3b 100644 (file)
@@ -59,8 +59,7 @@ public class JpegLoader implements Runnable
        {
                _app = inApp;
                _parentFrame = inParentFrame;
-               String[] fileTypes = {"jpg", "jpe", "jpeg"};
-               _fileFilter = new GenericFileFilter("filetype.jpeg", fileTypes);
+               _fileFilter = new JpegFileFilter();
        }
 
 
@@ -150,8 +149,8 @@ public class JpegLoader implements Runnable
        public void run()
        {
                // Initialise arrays, errors, summaries
-               _fileCounts = new int[4]; // files, jpegs, exifs, gps
-               _photos = new TreeSet<Photo>(new PhotoSorter());
+               _fileCounts = new int[3]; // files, jpegs, gps
+               _photos = new TreeSet<Photo>(new MediaSorter());
                File[] files = _fileChooser.getSelectedFiles();
                // Loop recursively over selected files/directories to count files
                int numFiles = countFileList(files, true, _subdirCheckbox.isSelected());
@@ -166,8 +165,6 @@ public class JpegLoader implements Runnable
                _progressDialog.dispose(); // Sometimes dialog doesn't disappear without this dispose
                if (_cancelled) {return;}
 
-               //System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1]
-               //  + ", " + _fileCounts[2] + ", " + _fileCounts[3]);
                if (_fileCounts[0] == 0)
                {
                        // No files found at all
@@ -179,11 +176,6 @@ public class JpegLoader implements Runnable
                        _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.nojpegsfound");
                }
                else if (!_noExifCheckbox.isSelected() && _fileCounts[2] == 0)
-               {
-                       // Need coordinates but no exif found
-                       _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.noexiffound");
-               }
-               else if (!_noExifCheckbox.isSelected() && _fileCounts[3] == 0)
                {
                        // Need coordinates but no gps information found
                        _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.nogpsfound");
@@ -247,18 +239,36 @@ public class JpegLoader implements Runnable
                if (!_fileFilter.acceptFilename(inFile.getName())) {return;}
                // If it's a Jpeg, we can use ExifReader to get coords, otherwise we could try exiftool (if it's installed)
 
-               // Create Photo object
-               Photo photo = new Photo(inFile);
                if (inFile.exists() && inFile.canRead()) {
                        _fileCounts[1]++; // jpeg found
                }
+               Photo photo = createPhoto(inFile);
+               if (photo.getDataPoint() != null) {
+                       _fileCounts[2]++; // photo has coordinates
+               }
+               // Check the criteria for adding the photo - check whether the photo has coordinates and if so if they're within the rectangle
+               if ( (photo.getDataPoint() != null || _noExifCheckbox.isSelected())
+                       && (photo.getDataPoint() == null || !_outsideAreaCheckbox.isEnabled()
+                               || _outsideAreaCheckbox.isSelected() || _trackRectangle.containsPoint(photo.getDataPoint())))
+               {
+                       _photos.add(photo);
+               }
+       }
+
+       /**
+        * Create a Photo object for the given file, including reading exif information
+        * @param inFile file object
+        * @return Photo object
+        */
+       public static Photo createPhoto(File inFile)
+       {
+               // Create Photo object
+               Photo photo = new Photo(inFile);
                // Try to get information out of exif
                JpegData jpegData = ExifGateway.getJpegData(inFile);
                Timestamp timestamp = null;
                if (jpegData != null)
                {
-                       if (jpegData.getExifDataPresent())
-                               {_fileCounts[2]++;} // exif found
                        if (jpegData.isGpsValid())
                        {
                                timestamp = createTimestamp(jpegData.getGpsDatestamp(), jpegData.getGpsTimestamp());
@@ -268,7 +278,6 @@ public class JpegLoader implements Runnable
                                point.setSegmentStart(true);
                                photo.setDataPoint(point);
                                photo.setOriginalStatus(Photo.Status.TAGGED);
-                               _fileCounts[3]++;
                        }
                        // Use exif timestamp if gps timestamp not available
                        if (timestamp == null && jpegData.getOriginalTimestamp() != null) {
@@ -290,13 +299,7 @@ public class JpegLoader implements Runnable
                if (photo.getDataPoint() != null) {
                        photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.FORMAT_ISO_8601), false);
                }
-               // Check the criteria for adding the photo - check whether the photo has coordinates and if so if they're within the rectangle
-               if ( (photo.getDataPoint() != null || _noExifCheckbox.isSelected())
-                       && (photo.getDataPoint() == null || !_outsideAreaCheckbox.isEnabled()
-                               || _outsideAreaCheckbox.isSelected() || _trackRectangle.containsPoint(photo.getDataPoint())))
-               {
-                       _photos.add(photo);
-               }
+               return photo;
        }
 
 
diff --git a/tim/prune/load/MediaHelper.java b/tim/prune/load/MediaHelper.java
new file mode 100644 (file)
index 0000000..9b029d7
--- /dev/null
@@ -0,0 +1,70 @@
+package tim.prune.load;
+
+import java.io.File;
+
+import tim.prune.data.AudioFile;
+import tim.prune.data.MediaFile;
+import tim.prune.data.MediaList;
+import tim.prune.data.Photo;
+import tim.prune.data.Track;
+
+/**
+ * Class to provide helper functions for loading media
+ */
+public abstract class MediaHelper
+{
+       /** File filters */
+       private static GenericFileFilter _jpegFilter = null, _audioFilter = null;
+
+       /**
+        * Construct a MediaFile object for the given path
+        * @param inPath path to file
+        * @return either Photo or AudioFile object as appropriate, or null
+        */
+       public static MediaFile createMediaFile(String inPath)
+       {
+               if (inPath == null) {return null;}
+               File file = new File(inPath);
+               if (!file.exists() || !file.canRead() || !file.isFile()) {return null;}
+               // Initialise filters if necessary
+               if (_jpegFilter == null) {
+                       _jpegFilter = new JpegFileFilter();
+                       _audioFilter = new AudioFileFilter();
+               }
+               // Check if filename looks like a jpeg
+               if (_jpegFilter.acceptFilename(file.getName())) {
+                       return JpegLoader.createPhoto(file);
+               }
+               // Check if filename looks like an audio file
+               if (_audioFilter.acceptFilename(file.getName())) {
+                       return new AudioFile(file);
+               }
+               // Neither photo nor audio
+               return null;
+       }
+
+       /**
+        * Add all the media from the given track into the specified list
+        * @param inTrack track from which media to take
+        * @param inMediaList list to which media should be added
+        * @param inMediaClass class of media, either Photo or AudioFile
+        */
+       public static void addMediaFromTrack(Track inTrack, MediaList inMediaList,
+               Class<?> inMediaClass)
+       {
+               final int numPoints = inTrack.getNumPoints();
+               for (int i=0; i<numPoints; i++)
+               {
+                       MediaFile media = null;
+                       if (inMediaClass == Photo.class) {
+                               media = inTrack.getPoint(i).getPhoto();
+                       }
+                       else if (inMediaClass == AudioFile.class) {
+                               media = inTrack.getPoint(i).getAudio();
+                       }
+                       if (media != null) {
+                               inMediaList.addMedia(media);
+                       }
+               }
+       }
+}
similarity index 75%
rename from tim/prune/load/PhotoSorter.java
rename to tim/prune/load/MediaSorter.java
index 6da6f64828aa559a858dc83472fced11c4c23f80..897b5aa0ff6957e80b859fa485c15b554cf73d8d 100644 (file)
@@ -2,20 +2,20 @@ package tim.prune.load;
 
 import java.io.File;
 import java.util.Comparator;
+import tim.prune.data.MediaFile;
 
-import tim.prune.data.Photo;
 
 /**
  * Class to sort photos by name
  */
-public class PhotoSorter implements Comparator<Photo>
+public class MediaSorter implements Comparator<MediaFile>
 {
 
        /**
-        * Compare two photos
+        * Compare two media files
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
-       public int compare(Photo o1, Photo o2)
+       public int compare(MediaFile o1, MediaFile o2)
        {
                File file1 = o1.getFile();
                File file2 = o2.getFile();
index 287e71d09975f9a67c98655cc0fd549208447ba8..2f2b7dcf6a3d31cc3ef4e7415a125ad769e143a1 100644 (file)
@@ -30,10 +30,9 @@ public class TrackNameList
                _pointNum++;
                if (inIsTrackpoint)
                {
-                       // System.out.println("Point " + _pointNum + " is in track '" + inTrackName + "' (" + inTrackNum + ")");
                        if (inTrackNum != _trackNum) {
                                _trackNames.add(inTrackName);
-                               _startIndices.add(new Integer(_pointNum));
+                               _startIndices.add(Integer.valueOf(_pointNum));
                        }
                }
                _trackNum = inTrackNum;
@@ -81,16 +80,4 @@ public class TrackNameList
                if (inTrackNum < 0 || inTrackNum >= getNumTracks()) {return 0;}
                return _startIndices.get(inTrackNum);
        }
-
-       /**
-        * Print summary for debug
-        * @deprecated
-        */
-       public void summarize()
-       {
-               System.out.println("File has " + getNumTracks() + " tracks:");
-               for (int i=0; i<getNumTracks(); i++) {
-                       System.out.println("  Track " + i + " is called '" + _trackNames.get(i) + "' and has " + getNumPointsInTrack(i) + " points");
-               }
-       }
 }
index a4906ac0a5b81d9dac6076d1e542a0b4339a7e7f..ec52f8b5b4b81c54cadb5976a0ed4633d528f494 100644 (file)
@@ -16,19 +16,16 @@ public class GpxHandler extends XmlHandler
 {
        private boolean _insidePoint = false;
        private boolean _insideWaypoint = false;
-       private boolean _insideName = false;
-       private boolean _insideElevation = false;
-       private boolean _insideTime = false;
-       private boolean _insideType = false;
        private boolean _startSegment = true;
        private boolean _isTrackPoint = false;
        private int _trackNum = -1;
-       private String _trackName = null;
-       private String _name = null, _latitude = null, _longitude = null;
-       private String _elevation = null;
-       private String _time = null;
-       private String _type = null;
+       private GpxTag _name = new GpxTag(), _trackName = new GpxTag();
+       private String _latitude = null, _longitude = null;
+       private GpxTag _elevation = new GpxTag(), _time = new GpxTag();
+       private GpxTag _type = new GpxTag(), _link = new GpxTag();
+       private GpxTag _currentTag = null;
        private ArrayList<String[]> _pointList = new ArrayList<String[]>();
+       private ArrayList<String> _linkList = new ArrayList<String>();
        private TrackNameList _trackNameList = new TrackNameList();
 
 
@@ -40,48 +37,47 @@ public class GpxHandler extends XmlHandler
                Attributes attributes) throws SAXException
        {
                // Read the parameters for waypoints and track points
-               if (qName.equalsIgnoreCase("wpt") || qName.equalsIgnoreCase("trkpt") || qName.equalsIgnoreCase("rtept"))
+               String tag = qName.toLowerCase();
+               if (tag.equals("wpt") || tag.equals("trkpt") || tag.equals("rtept"))
                {
                        _insidePoint = true;
-                       _insideWaypoint = qName.equalsIgnoreCase("wpt");
-                       _isTrackPoint = qName.equalsIgnoreCase("trkpt");
-                       int numAttributes = attributes.getLength();
+                       _insideWaypoint = tag.equals("wpt");
+                       _isTrackPoint = tag.equals("trkpt");
+                       final int numAttributes = attributes.getLength();
                        for (int i=0; i<numAttributes; i++)
                        {
-                               String att = attributes.getQName(i);
+                               String att = attributes.getQName(i).toLowerCase();
                                if (att.equals("lat")) {_latitude = attributes.getValue(i);}
                                else if (att.equals("lon")) {_longitude = attributes.getValue(i);}
                        }
-                       _elevation = null;
-                       _name = null;
-                       _time = null;
-                       _type = null;
+                       _elevation.setValue(null);
+                       _name.setValue(null);
+                       _time.setValue(null);
+                       _type.setValue(null);
+                       _link.setValue(null);
                }
-               else if (qName.equalsIgnoreCase("ele"))
-               {
-                       _insideElevation = true;
+               else if (tag.equals("ele")) {
+                       _currentTag = _elevation;
                }
-               else if (qName.equalsIgnoreCase("name"))
-               {
-                       _name = null;
-                       _insideName = true;
+               else if (tag.equals("name")) {
+                       _currentTag = (_insidePoint?_name:_trackName);
                }
-               else if (qName.equalsIgnoreCase("time"))
-               {
-                       _insideTime = true;
+               else if (tag.equals("time")) {
+                       _currentTag = _time;
                }
-               else if (qName.equalsIgnoreCase("type"))
-               {
-                       _insideType = true;
+               else if (tag.equals("type")) {
+                       _currentTag = _type;
                }
-               else if (qName.equalsIgnoreCase("trkseg"))
-               {
+               else if (tag.equals("link")) {
+                       _link.setValue(attributes.getValue("href"));
+               }
+               else if (tag.equals("trkseg")) {
                        _startSegment = true;
                }
-               else if (qName.equalsIgnoreCase("trk"))
+               else if (tag.equals("trk"))
                {
                        _trackNum++;
-                       _trackName = null;
+                       _trackName.setValue(null);
                }
                super.startElement(uri, localName, qName, attributes);
        }
@@ -94,26 +90,14 @@ public class GpxHandler extends XmlHandler
        public void endElement(String uri, String localName, String qName)
                throws SAXException
        {
-               if (qName.equalsIgnoreCase("wpt") || qName.equalsIgnoreCase("trkpt") || qName.equalsIgnoreCase("rtept"))
+               String tag = qName.toLowerCase();
+               if (tag.equals("wpt") || tag.equals("trkpt") || tag.equals("rtept"))
                {
                        processPoint();
                        _insidePoint = false;
                }
-               else if (qName.equalsIgnoreCase("ele"))
-               {
-                       _insideElevation = false;
-               }
-               else if (qName.equalsIgnoreCase("name"))
-               {
-                       _insideName = false;
-               }
-               else if (qName.equalsIgnoreCase("time"))
-               {
-                       _insideTime = false;
-               }
-               else if (qName.equalsIgnoreCase("type"))
-               {
-                       _insideType = false;
+               else {
+                       _currentTag = null;
                }
                super.endElement(uri, localName, qName);
        }
@@ -127,11 +111,9 @@ public class GpxHandler extends XmlHandler
                throws SAXException
        {
                String value = new String(ch, start, length);
-               if (_insideName && _insideWaypoint) {_name = checkCharacters(_name, value);}
-               if (_insideName && !_insidePoint) {_trackName = checkCharacters(_trackName, value);}
-               else if (_insideElevation) {_elevation = checkCharacters(_elevation, value);}
-               else if (_insideTime) {_time = checkCharacters(_time, value);}
-               else if (_insideType) {_type = checkCharacters(_type, value);}
+               if (_currentTag != null) {
+                       _currentTag.setValue(checkCharacters(_currentTag.getValue(), value));
+               }
                super.characters(ch, start, length);
        }
 
@@ -156,16 +138,19 @@ public class GpxHandler extends XmlHandler
        {
                // Put the values into a String array matching the order in getFieldArray()
                String[] values = new String[7];
-               values[0] = _latitude; values[1] = _longitude;
-               values[2] = _elevation; values[3] = _name;
-               values[4] = _time;
+               values[0] = _latitude;
+               values[1] = _longitude;
+               values[2] = _elevation.getValue();
+               if (_insideWaypoint) {values[3] = _name.getValue();}
+               values[4] = _time.getValue();
                if (_startSegment && !_insideWaypoint) {
                        values[5] = "1";
                        _startSegment = false;
                }
-               values[6] = _type;
+               values[6] = _type.getValue();
                _pointList.add(values);
-               _trackNameList.addPoint(_trackNum, _trackName, _isTrackPoint);
+               _trackNameList.addPoint(_trackNum, _trackName.getValue(), _isTrackPoint);
+               _linkList.add(_link.getValue());
        }
 
 
@@ -196,6 +181,22 @@ public class GpxHandler extends XmlHandler
                return result;
        }
 
+       /**
+        * @return array of links, or null if none
+        */
+       public String[] getLinkArray()
+       {
+               int numPoints = _linkList.size();
+               boolean hasLink = false;
+               String[] result = new String[numPoints];
+               for (int i=0; i<numPoints; i++)
+               {
+                       result[i] = _linkList.get(i);
+                       if (result[i] != null) {hasLink = true;}
+               }
+               if (!hasLink) {result = null;}
+               return result;
+       }
 
        /**
         * @return track name list
diff --git a/tim/prune/load/xml/GpxTag.java b/tim/prune/load/xml/GpxTag.java
new file mode 100644 (file)
index 0000000..b2d8ebc
--- /dev/null
@@ -0,0 +1,24 @@
+package tim.prune.load.xml;
+
+/**
+ * Class to hold a single tag value from a gpx file
+ */
+public class GpxTag
+{
+       /** value of tag */
+       private String _value = null;
+
+       /**
+        * @param inVal value to set
+        */
+       public void setValue(String inVal) {
+               _value = inVal;
+       }
+
+       /**
+        * @return value
+        */
+       public String getValue() {
+               return _value;
+       }
+}
index 70419f36681a8076dcb65c40f8ca3eb569733c2f..55b88f11847aef3d33c0599f80b9fe3899692bf3 100644 (file)
@@ -53,7 +53,7 @@ public class GzipFileLoader
                                SourceInfo sourceInfo = new SourceInfo(inFile,
                                        (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
-                                       Altitude.Format.METRES, sourceInfo, handler.getTrackNameList());
+                                       Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(), handler.getLinkArray());
                        }
                }
                catch (Exception e) {
index d528420029a325f8cfc33238de6c80b5c55ef8cc..454149f2c35cd432761edde2c8d271c50d2b3b15 100644 (file)
@@ -2,6 +2,7 @@ package tim.prune.load.xml;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
@@ -84,20 +85,18 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
                                SourceInfo sourceInfo = new SourceInfo(_file,
                                        (_handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(_handler.getFieldArray(), _handler.getDataArray(),
-                                       Altitude.Format.METRES, sourceInfo, _handler.getTrackNameList());
+                                       Altitude.Format.METRES, sourceInfo, _handler.getTrackNameList(), _handler.getLinkArray());
                        }
                }
                catch (Exception e)
                {
-                       // clean up file stream
-                       try {
-                               inStream.close();
-                       }
-                       catch (Exception e2) {}
                        // Show error dialog
                        _app.showErrorMessageNoLookup("error.load.dialogtitle",
                                I18nManager.getText("error.load.othererror") + " " + e.getMessage());
                }
+               finally {
+                       try {inStream.close();} catch (IOException e2) {}
+               }
        }
 
 
index bdd351d399ceca68d7ff303d223e4e439592ddd6..b604fde4d0d04b35695344ec6cf6d30db855854c 100644 (file)
@@ -25,8 +25,15 @@ public abstract class XmlHandler extends DefaultHandler
         * Can be overriden (eg by gpx handler) to provide a track name list
         * @return track name list object if any, or null
         */
-       public TrackNameList getTrackNameList()
-       {
+       public TrackNameList getTrackNameList() {
+               return null;
+       }
+
+       /**
+        * Can be overriden (eg by gpx handler) to provide an array of links to media
+        * @return array of Strings if any, or null
+        */
+       public String[] getLinkArray() {
                return null;
        }
 }
index df60576a057617eccae0652889afe6ce2b6ad2d8..20b3ab2f753d69d302cb27cb13098a8109ad3069 100644 (file)
@@ -67,7 +67,7 @@ public class ZipFileLoader
                                                        SourceInfo sourceInfo = new SourceInfo(inFile,
                                                                (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                                        _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
-                                                               Altitude.Format.METRES, sourceInfo, handler.getTrackNameList());
+                                                               Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(), handler.getLinkArray());
                                                        xmlFound = true;
                                                }
                                        }
index 3e26b1e88226a6fc993cabc51f96eb20398fb91c..ccdc8cbebdf35897e79bba10fef58279183b5c0a 100644 (file)
@@ -1,5 +1,5 @@
-Prune version 11.2
-==================
+Prune version 12
+================
 
 Prune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting and photo correlation.
@@ -17,7 +17,7 @@ Running
 =======
 
 To run Prune from the jar file, simply call it from a command prompt or shell:
-   java -jar prune_11.2.jar
+   java -jar prune_12.jar
 
 If the jar file is saved in a different directory, you will need to include the path.
 Depending on your system settings, you may be able to click or double-click on the jar file
@@ -25,23 +25,20 @@ in a file manager window to execute it.  A shortcut, menu item, alias, desktop i
 or other link can of course be made should you wish.
 
 To specify a language other than the default, use an additional parameter, eg:
-   java -jar prune_11.2.jar --lang=DE
-
-New with version 11.2
-=====================
+   java -jar prune_12.jar --lang=DE
 
-The following were added since version 11.1:
-  - Correction of xml encoding for ISO8859_15 systems so exported GPX is loadable by Google Earth
-  - Updating of Polish and Portuguese texts
-
-New with version 11.1
-=====================
-
-The following were added since version 11:
-  - Bugfix for exporting non-ASCII characters to a GPX file on a non-UTF8 system
-  - Bugfix for freeing file resources when GPX load fails
-  - Bugfix for resizing photo thumbnail
-  - Menu item for deselecting current photo
+New with version 12
+===================
+The following features were added since version 11:
+  - Options to load audio files, play them, connect to points and auto-correlate them
+  - Extend Gpx export to include links to photos and audio files
+  - Extend Gpx load to also load referenced photos, audio files
+  - Function to search for places in Wikipedia by name
+  - Function to search for places in Wikipedia near the current point
+  - Function to download OSM data using the XAPI and save to osm file
+  - New drawing mode to add points with one click each
+  - Hungarian translations thanks to György
+  - Korean translations thanks to HooAU
 
 New with version 11
 ===================
@@ -132,12 +129,6 @@ The following features were added since version 4.1:
   - Statusbar showing confirmation of actions
   - Much improved French texts thanks to generous user input
 
-New with version 4.1
-====================
-
-The following fix was added since version 4:
-  - Exiftool calls now made in series rather than in parallel to avoid performance problems in Windows
-
 New with version 4
 ==================
 
index 78fbe3e5d38e49cecc98fee01b360c27e0b154b0..f2ea066179efed753aef009243bc4ab174b12ba5 100644 (file)
@@ -500,8 +500,9 @@ public class FileSaver
                                {
                                        DataPoint point = track.getPoint(p);
                                        boolean savePoint = ((point.isWaypoint() && _pointTypeSelector.getWaypointsSelected())
-                                               || (!point.isWaypoint() && point.getPhoto()==null && _pointTypeSelector.getTrackpointsSelected())
-                                               || (!point.isWaypoint() && point.getPhoto()!=null && _pointTypeSelector.getPhotopointsSelected()))
+                                               || (!point.isWaypoint() && !point.hasMedia() && _pointTypeSelector.getTrackpointsSelected())
+                                               || (!point.isWaypoint() && point.getPhoto()!=null && _pointTypeSelector.getPhotopointsSelected())
+                                               || (!point.isWaypoint() && point.getAudio()!=null && _pointTypeSelector.getAudiopointsSelected()))
                                                && (!_pointTypeSelector.getJustSelection() || (p>=selStart && p<=selEnd));
                                        if (!savePoint) {continue;}
                                        numSaved++;
@@ -512,9 +513,8 @@ public class FileSaver
                                                info = _model.getFieldInfo(f);
                                                if (info.isSelected())
                                                {
-                                                       if (!firstField)
-                                                       {
-                                                               // output field separator
+                                                       // output field separator
+                                                       if (!firstField) {
                                                                buffer.append(delimiter);
                                                        }
                                                        saveField(buffer, point, info.getField(), coordFormat, altitudeFormat, timestampFormat);
index 944a7e4e4f1ff4780263d2a3ec58f80245caa5d6..76ec88f4de89db662f6761ae21e737c378fd2d1d 100644 (file)
@@ -6,6 +6,8 @@ import java.awt.FlowLayout;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
@@ -121,6 +123,19 @@ public class GpsSaver extends GenericFunction implements Runnable
                gridPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
                gridPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 20));
                mainPanel.add(gridPanel);
+               // close dialog when escape pressed
+               KeyAdapter closer = new KeyAdapter() {
+                       public void keyReleased(KeyEvent e)
+                       {
+                               // close dialog if escape pressed
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               _deviceField.addKeyListener(closer);
+               _formatField.addKeyListener(closer);
+               _trackNameField.addKeyListener(closer);
 
                // checkboxes
                ChangeListener checkboxListener = new ChangeListener() {
@@ -257,7 +272,7 @@ public class GpsSaver extends GenericFunction implements Runnable
                if (trackName == null || trackName.equals("")) {trackName = "prune";}
                // Generate the GPX file and send to the GPS
                OutputStreamWriter writer = new OutputStreamWriter(process.getOutputStream());
-               boolean[] saveFlags = {true, true, true, false, true}; // export everything
+               boolean[] saveFlags = {true, true, true, true, false, true}; // export everything
                GpxExporter.exportData(writer, _app.getTrackInfo(), trackName, null, saveFlags, false);
                writer.close();
 
index 8203593b0d6d72387293df350e1366a8ddf097ae..d57d5f9b69f020d407e2ed5257f9244704144c85 100644 (file)
@@ -6,8 +6,6 @@ import java.awt.FlowLayout;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -35,11 +33,15 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.Altitude;
+import tim.prune.data.AudioFile;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
+import tim.prune.data.MediaFile;
+import tim.prune.data.Photo;
 import tim.prune.data.Timestamp;
 import tim.prune.data.TrackInfo;
+import tim.prune.gui.DialogCloser;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.save.xml.GpxCacherList;
 
@@ -133,13 +135,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                dialogPanel.add(mainPanel, BorderLayout.CENTER);
 
                // close dialog if escape pressed
-               _nameField.addKeyListener(new KeyAdapter() {
-                       public void keyReleased(KeyEvent e) {
-                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
-                                       _dialog.dispose();
-                               }
-                       }
-               });
+               _nameField.addKeyListener(new DialogCloser(_dialog));
                // button panel at bottom
                JPanel buttonPanel = new JPanel();
                buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
@@ -248,9 +244,9 @@ public class GpxExporter extends GenericFunction implements Runnable
                {
                        // normal writing to file
                        writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
-                       boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
-                               _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getJustSelection(),
-                               _timestampsCheckbox.isSelected()};
+                       final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
+                               _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
+                               _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
                        // write file
                        final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
                                _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
@@ -289,7 +285,7 @@ public class GpxExporter extends GenericFunction implements Runnable
         * @param inInfo track info object
         * @param inName name of track (optional)
         * @param inDesc description of track (optional)
-        * @param inSaveFlags array of booleans to export tracks, waypoints, photos, timestamps
+        * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
         * @param inUseCopy true to copy source if available
         * @return number of points written
         * @throws IOException if io errors occur on write
@@ -319,12 +315,12 @@ public class GpxExporter extends GenericFunction implements Runnable
 
                int i = 0;
                DataPoint point = null;
-               boolean hasTrackpoints = false;
                final boolean exportTrackpoints = inSaveFlags[0];
                final boolean exportWaypoints = inSaveFlags[1];
                final boolean exportPhotos = inSaveFlags[2];
-               final boolean exportSelection = inSaveFlags[3];
-               final boolean exportTimestamps = inSaveFlags[4];
+               final boolean exportAudios = inSaveFlags[3];
+               final boolean exportSelection = inSaveFlags[4];
+               final boolean exportTimestamps = inSaveFlags[5];
                // Examine selection
                int selStart = -1, selEnd = -1;
                if (exportSelection) {
@@ -348,27 +344,25 @@ public class GpxExporter extends GenericFunction implements Runnable
                                                        inWriter.write('\n');
                                                }
                                                else {
-                                                       exportWaypoint(point, inWriter, exportTimestamps);
+                                                       exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
                                                }
                                                numSaved++;
                                        }
                                }
-                               else {
-                                       hasTrackpoints = true;
-                               }
                        }
                }
                // Export both route points and then track points
-               if (hasTrackpoints && (exportTrackpoints || exportPhotos))
+               if (exportTrackpoints || exportPhotos || exportAudios)
                {
                        // Output all route points (if any)
                        numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
-                               exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n", null, "\t</rte>\n");
+                               exportAudios, exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n",
+                               null, "\t</rte>\n");
                        // Output all track points, if any
                        String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
                        numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
-                               exportTimestamps, false, gpxCachers, "<trkpt", trackStart, "\t</trkseg>\n\t<trkseg>\n",
-                               "\t</trkseg></trk>\n");
+                               exportAudios, exportTimestamps, false, gpxCachers, "<trkpt", trackStart,
+                               "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
                }
 
                inWriter.write("</gpx>\n");
@@ -382,7 +376,8 @@ public class GpxExporter extends GenericFunction implements Runnable
         * @param inExportSelection true to just output current selection
         * @param inExportTrackpoints true to output track points
         * @param inExportPhotos true to output photo points
-        * @param exportTimestamps true to include timestamps in export
+        * @param inExportAudios true to output audio points
+        * @param inExportTimestamps true to include timestamps in export
         * @param inOnlyCopies true to only export if source can be copied
         * @param inCachers list of GpxCachers
         * @param inPointTag tag to match for each point
@@ -392,9 +387,9 @@ public class GpxExporter extends GenericFunction implements Runnable
         */
        private static int writeTrackPoints(OutputStreamWriter inWriter,
                TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
-               boolean inExportPhotos, boolean exportTimestamps, boolean inOnlyCopies,
-               GpxCacherList inCachers, String inPointTag, String inStartTag,
-               String inSegmentTag, String inEndTag)
+               boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
+               boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
+               String inStartTag, String inSegmentTag, String inEndTag)
        throws IOException
        {
                // Note: far too many input parameters to this method but avoids duplication
@@ -409,13 +404,15 @@ public class GpxExporter extends GenericFunction implements Runnable
                        DataPoint point = inInfo.getTrack().getPoint(i);
                        if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
                        {
-                               if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos))
+                               if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
+                                       || (point.getAudio()!=null && inExportAudios))
                                {
+                                       if (point.getPhoto() != null) System.out.println("Writetrackpoints: Point has photo " + point.getPhoto().getFile().getName());
                                        // get the source from the point (if any)
                                        String pointSource = getPointSource(inCachers, point);
-                                       boolean writePoint = (pointSource != null && pointSource.toLowerCase().startsWith(inPointTag))
-                                               || (pointSource == null && !inOnlyCopies);
-                                       if (writePoint)
+                                       // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
+                                       if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
+                                       if (pointSource != null || !inOnlyCopies)
                                        {
                                                // restart track segment if necessary
                                                if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
@@ -423,11 +420,12 @@ public class GpxExporter extends GenericFunction implements Runnable
                                                }
                                                if (numSaved == 0) {inWriter.write(inStartTag);}
                                                if (pointSource != null) {
+                                                       if (point.getPhoto() != null) System.out.println("Point has photo but using source");
                                                        inWriter.write(pointSource);
                                                        inWriter.write('\n');
                                                }
                                                else {
-                                                       if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps);}
+                                                       if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
                                                }
                                                numSaved++;
                                        }
@@ -456,6 +454,10 @@ public class GpxExporter extends GenericFunction implements Runnable
                source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
                source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
                if (inPoint.isWaypoint()) {source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());}  // only for waypoints
+               // photo / audio links
+               if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
+                       source = replaceMediaLinks(source, makeMediaLink(inPoint));
+               }
                return source;
        }
 
@@ -494,6 +496,43 @@ public class GpxExporter extends GenericFunction implements Runnable
                return null;
        }
 
+       /**
+        * Replace the media tags in the given XML string
+        * @param inSource source XML for point
+        * @param inValue value for the current point
+        * @return modified String, or null if not possible
+        */
+       private static String replaceMediaLinks(String inSource, String inValue)
+       {
+               if (inSource == null) {return null;}
+               // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
+               // and the tags must have attributes.  So either one heavily parameterized method or two.
+               // Look for start and end tags within source
+               final String STARTTEXT = "<link";
+               final String ENDTEXT = "</link>";
+               final int startPos = inSource.indexOf(STARTTEXT);
+               final int endPos = inSource.lastIndexOf(ENDTEXT);
+               if (startPos > 0 && endPos > 0)
+               {
+                       String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
+                       if (inValue != null && origValue.equals(inValue)) {
+                               // Value unchanged
+                               return inSource;
+                       }
+                       else if (inValue == null || inValue.equals("")) {
+                               // Need to delete value
+                               return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
+                       }
+                       else {
+                               // Need to replace value
+                               return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
+                       }
+               }
+               // Value not found for this field in original source
+               if (inValue == null || inValue.equals("")) {return inSource;}
+               return null;
+       }
+
        /**
         * Get the header string for the xml document including encoding
         * @param inWriter writer object
@@ -534,9 +573,12 @@ public class GpxExporter extends GenericFunction implements Runnable
         * @param inPoint waypoint to export
         * @param inWriter writer object
         * @param inTimestamps true to export timestamps too
+        * @param inPhoto true to export link to photo
+        * @param inAudio true to export link to audio
         * @throws IOException on write failure
         */
-       private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
+       private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
+               boolean inPhoto, boolean inAudio)
                throws IOException
        {
                inWriter.write("\t<wpt lat=\"");
@@ -562,6 +604,19 @@ public class GpxExporter extends GenericFunction implements Runnable
                inWriter.write("\t\t<name>");
                inWriter.write(inPoint.getWaypointName().trim());
                inWriter.write("</name>\n");
+               // Media links, if any
+               if (inPhoto && inPoint.getPhoto() != null)
+               {
+                       inWriter.write("\t\t");
+                       inWriter.write(makeMediaLink(inPoint.getPhoto()));
+                       inWriter.write('\n');
+               }
+               if (inAudio && inPoint.getAudio() != null)
+               {
+                       inWriter.write("\t\t");
+                       inWriter.write(makeMediaLink(inPoint.getAudio()));
+                       inWriter.write('\n');
+               }
                // write waypoint type if any
                String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
                if (type != null)
@@ -583,10 +638,14 @@ public class GpxExporter extends GenericFunction implements Runnable
         * @param inPoint trackpoint to export
         * @param inWriter writer object
         * @param inTimestamps true to export timestamps too
+        * @param inExportPhoto true to export photo link
+        * @param inExportAudio true to export audio link
         */
-       private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
+       private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
+               boolean inExportPhoto, boolean inExportAudio)
                throws IOException
        {
+               if (inPoint.getPhoto() != null) System.out.println("Point has photo " + inPoint.getPhoto().getFile().getName());
                inWriter.write("\t\t<trkpt lat=\"");
                inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                inWriter.write("\" lon=\"");
@@ -606,6 +665,45 @@ public class GpxExporter extends GenericFunction implements Runnable
                        inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
                        inWriter.write("</time>");
                }
+               // photo, audio
+               if (inPoint.getPhoto() != null && inExportPhoto) {
+                       inWriter.write(makeMediaLink(inPoint.getPhoto()));
+               }
+               if (inPoint.getAudio() != null && inExportAudio) {
+                       inWriter.write(makeMediaLink(inPoint.getAudio()));
+               }
                inWriter.write("</trkpt>\n");
        }
+
+       /**
+        * Make the xml for the media link(s)
+        * @param inPoint point to generate text for
+        * @return link tags, or null if no links
+        */
+       private static String makeMediaLink(DataPoint inPoint)
+       {
+               Photo photo = inPoint.getPhoto();
+               AudioFile audio = inPoint.getAudio();
+               if (photo == null && audio == null) {
+                       return null;
+               }
+               String linkText = "";
+               if (photo != null) {
+                       linkText = makeMediaLink(photo);
+               }
+               if (audio != null) {
+                       linkText += makeMediaLink(audio);
+               }
+               return linkText;
+       }
+
+       /**
+        * Make the media link for a single media item
+        * @param inMedia media item, either photo or audio
+        * @return link for this media
+        */
+       private static String makeMediaLink(MediaFile inMedia)
+       {
+               return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getFile().getName() + "</text></link>";
+       }
 }
index a5a4ee26a87e0bc7d383acf70d24fbc42434f270..7f90dbc8cca16e897ee70072aabc1e88c8f82b10 100644 (file)
@@ -49,6 +49,7 @@ import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 import tim.prune.gui.ColourChooser;
 import tim.prune.gui.ColourPatch;
+import tim.prune.gui.DialogCloser;
 import tim.prune.gui.ImageUtils;
 import tim.prune.load.GenericFileFilter;
 
@@ -81,9 +82,6 @@ public class KmlExporter extends GenericFunction implements Runnable
        // Default width and height of thumbnail images in Kmz
        private static final int DEFAULT_THUMBNAIL_WIDTH = 240;
        private static final int DEFAULT_THUMBNAIL_HEIGHT = 240;
-       // Actual selected width and height of thumbnail images in Kmz
-       private static int THUMBNAIL_WIDTH = 0;
-       private static int THUMBNAIL_HEIGHT = 0;
        // Default track colour
        private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red
 
@@ -143,6 +141,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                descPanel.setLayout(new FlowLayout());
                descPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.text")));
                _descriptionField = new JTextField(20);
+               _descriptionField.addKeyListener(new DialogCloser(_dialog));
                descPanel.add(_descriptionField);
                descPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(descPanel);
@@ -325,10 +324,10 @@ public class KmlExporter extends GenericFunction implements Runnable
                _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
 
                // Determine photo thumbnail size from config
-               THUMBNAIL_WIDTH = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
-               if (THUMBNAIL_WIDTH < DEFAULT_THUMBNAIL_WIDTH) {THUMBNAIL_WIDTH = DEFAULT_THUMBNAIL_WIDTH;}
-               THUMBNAIL_HEIGHT = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
-               if (THUMBNAIL_HEIGHT < DEFAULT_THUMBNAIL_HEIGHT) {THUMBNAIL_HEIGHT = DEFAULT_THUMBNAIL_HEIGHT;}
+               int thumbWidth = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
+               if (thumbWidth < DEFAULT_THUMBNAIL_WIDTH) {thumbWidth = DEFAULT_THUMBNAIL_WIDTH;}
+               int thumbHeight = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
+               if (thumbHeight < DEFAULT_THUMBNAIL_HEIGHT) {thumbHeight = DEFAULT_THUMBNAIL_HEIGHT;}
                // Create array for image dimensions in case it's required
                _imageDimensions = new Dimension[_track.getNumPoints()];
 
@@ -351,7 +350,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                                {
                                        // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
                                        // This is done first so that photo sizes are known for later
-                                       exportThumbnails(zipOutputStream);
+                                       exportThumbnails(zipOutputStream, thumbWidth, thumbHeight);
                                }
                                writer = new OutputStreamWriter(zipOutputStream);
                                // Make an entry in the zip file for the kml file
@@ -414,6 +413,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                boolean writeTrack = _pointTypeSelector.getTrackpointsSelected();
                boolean writeWaypoints = _pointTypeSelector.getWaypointsSelected();
                boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
+               boolean writeAudios = _pointTypeSelector.getAudiopointsSelected();
                boolean justSelection = _pointTypeSelector.getJustSelection();
                inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
                inWriter.write("\t<name>");
@@ -421,8 +421,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                {
                        inWriter.write(_descriptionField.getText());
                }
-               else
-               {
+               else {
                        inWriter.write("Export from Prune");
                }
                inWriter.write("</name>\n");
@@ -438,7 +437,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                int i = 0;
                DataPoint point = null;
                boolean hasTrackpoints = false;
-               boolean writtenPhotoHeader = false;
+               boolean writtenPhotoHeader = false, writtenAudioHeader = false;
                final int numPoints = _track.getNumPoints();
                int numSaved = 0;
                int photoNum = 0;
@@ -456,7 +455,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                                        numSaved++;
                                }
                        }
-                       else if (point.getPhoto() == null)
+                       else if (!point.hasMedia())
                        {
                                hasTrackpoints = true;
                        }
@@ -473,6 +472,17 @@ public class KmlExporter extends GenericFunction implements Runnable
                                exportPhotoPoint(point, inWriter, inExportImages, i, photoNum, absoluteAltitudes);
                                numSaved++;
                        }
+                       // Make a blob with description for each audio file
+                       if (point.getAudio() != null && writeAudios && writeCurrentPoint)
+                       {
+                               if (!writtenAudioHeader)
+                               {
+                                       inWriter.write("<Style id=\"audio_icon\"><IconStyle><color>ff00ffff</color><Icon><href>http://maps.google.com/mapfiles/kml/shapes/star.png</href></Icon></IconStyle></Style>");
+                                       writtenAudioHeader = true;
+                               }
+                               exportAudioPoint(point, inWriter, absoluteAltitudes);
+                               numSaved++;
+                       }
                }
                // Make a line for the track, if there is one
                if (hasTrackpoints && writeTrack)
@@ -541,28 +551,23 @@ public class KmlExporter extends GenericFunction implements Runnable
         */
        private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException
        {
-               inWriter.write("\t<Placemark>\n\t\t<name>");
-               inWriter.write(inPoint.getWaypointName().trim());
-               inWriter.write("</name>\n");
-               inWriter.write("\t\t<Point>\n");
-               if (inAbsoluteAltitude && inPoint.hasAltitude()) {
-                       inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
-               }
-               else {
-                       inWriter.write("\t\t\t<altitudeMode>clampToGround</altitudeMode>\n");
-               }
-               inWriter.write("\t\t\t<coordinates>");
-               inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
-               inWriter.write(',');
-               inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
-               inWriter.write(",");
-               if (inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
-               }
-               else {
-                       inWriter.write("0");
-               }
-               inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
+               String name = inPoint.getWaypointName().trim();
+               exportNamedPoint(inPoint, inWriter, name, null, null, inAbsoluteAltitude);
+       }
+
+
+       /**
+        * Export the specified audio point into the file
+        * @param inPoint audio point to export
+        * @param inWriter writer object
+        * @param inAbsoluteAltitude true for absolute altitude
+        * @throws IOException on write failure
+        */
+       private void exportAudioPoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException
+       {
+               String name = inPoint.getAudio().getFile().getName();
+               String desc = inPoint.getAudio().getFile().getAbsolutePath();
+               exportNamedPoint(inPoint, inWriter, name, desc, "audio_icon", inAbsoluteAltitude);
        }
 
 
@@ -580,18 +585,51 @@ public class KmlExporter extends GenericFunction implements Runnable
                int inPointNumber, int inImageNumber, boolean inAbsoluteAltitude)
        throws IOException
        {
-               inWriter.write("\t<Placemark>\n\t\t<name>");
-               inWriter.write(inPoint.getPhoto().getFile().getName());
-               inWriter.write("</name>\n");
+               String name = inPoint.getPhoto().getFile().getName();
+               String desc = null;
                if (inImageLink)
                {
                        Dimension imageSize = _imageDimensions[inPointNumber];
-                       // Write out some html for the thumbnail images
-                       inWriter.write("<description><![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
+                       // Create html for the thumbnail images
+                       desc = "<![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
                                + inImageNumber + ".jpg' width='" + imageSize.width + "' height='" + imageSize.height + "'></center></td></tr>"
-                               + "<tr><td><center>" + inPoint.getPhoto().getFile().getName() + "</center></td></tr></table>]]></description>");
+                               + "<tr><td><center>" + inPoint.getPhoto().getFile().getName() + "</center></td></tr></table>]]>";
+               }
+               // Export point
+               exportNamedPoint(inPoint, inWriter, name, desc, "camera_icon", inAbsoluteAltitude);
+       }
+
+
+       /**
+        * Export the specified named point into the file, like waypoint or photo point
+        * @param inPoint data point
+        * @param inWriter writer object
+        * @param inName name of point
+        * @param inDesc description of point, or null
+        * @param inStyle style of point, or null
+        * @param inAbsoluteAltitude true for absolute altitudes
+        * @throws IOException on write failure
+        */
+       private void exportNamedPoint(DataPoint inPoint, Writer inWriter, String inName,
+               String inDesc, String inStyle, boolean inAbsoluteAltitude)
+       throws IOException
+       {
+               inWriter.write("\t<Placemark>\n\t\t<name>");
+               inWriter.write(inName);
+               inWriter.write("</name>\n");
+               if (inDesc != null)
+               {
+                       // Write out description
+                       inWriter.write("<description>");
+                       inWriter.write(inDesc);
+                       inWriter.write("</description>");
+               }
+               if (inStyle != null)
+               {
+                       inWriter.write("<styleUrl>#");
+                       inWriter.write(inStyle);
+                       inWriter.write("</styleUrl>\n");
                }
-               inWriter.write("<styleUrl>#camera_icon</styleUrl>\n");
                inWriter.write("\t\t<Point>\n");
                if (inAbsoluteAltitude && inPoint.hasAltitude()) {
                        inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
@@ -603,13 +641,13 @@ public class KmlExporter extends GenericFunction implements Runnable
                inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                inWriter.write(',');
                inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
-               inWriter.write(",");
+               inWriter.write(',');
                // Altitude if point has one
                if (inPoint.hasAltitude()) {
                        inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
                }
                else {
-                       inWriter.write("0");
+                       inWriter.write('0');
                }
                inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
        }
@@ -626,22 +664,25 @@ public class KmlExporter extends GenericFunction implements Runnable
                inWriter.write(',');
                inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                // Altitude if point has one
-               inWriter.write(",");
+               inWriter.write(',');
                if (inPoint.hasAltitude()) {
                        inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
                }
                else {
-                       inWriter.write("0");
+                       inWriter.write('0');
                }
-               inWriter.write("\n");
+               inWriter.write('\n');
        }
 
 
        /**
         * Loop through the photos and create thumbnails
         * @param inZipStream zip stream to save image files to
+        * @param inThumbWidth thumbnail width
+        * @param inThumbHeight thumbnail height
         */
-       private void exportThumbnails(ZipOutputStream inZipStream) throws IOException
+       private void exportThumbnails(ZipOutputStream inZipStream, int inThumbWidth, int inThumbHeight)
+       throws IOException
        {
                // set up image writer
                Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
@@ -677,7 +718,7 @@ public class KmlExporter extends GenericFunction implements Runnable
 
                                // Scale and smooth image to required size
                                BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(),
-                                       THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, point.getPhoto().getRotationDegrees());
+                                       inThumbWidth, inThumbHeight, point.getPhoto().getRotationDegrees());
                                // Store image dimensions so that it doesn't have to be calculated again for the points
                                _imageDimensions[i] = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight());
 
index 91632b49ad0e4c45405938b803dd728a4d7ea867..ce7adeb823f244aca4a610d87e43c198d771bef5 100644 (file)
@@ -14,13 +14,15 @@ import tim.prune.data.TrackInfo;
 
 /**
  * GUI element to allow the selection of point types for saving,
- * including checkboxes for track points, waypoints, photo points
+ * including checkboxes for track points, waypoints, photo points, audio points
  * and also a checkbox for the current selection
  */
 public class PointTypeSelector extends JPanel
 {
        /** Array of checkboxes */
-       private JCheckBox[] _checkboxes = new JCheckBox[4];
+       private JCheckBox[] _checkboxes = new JCheckBox[5];
+       /** Grid panel for top row */
+       private JPanel _gridPanel = null;
 
 
        /**
@@ -40,23 +42,23 @@ public class PointTypeSelector extends JPanel
        private void createComponents()
        {
                setLayout(new BorderLayout());
-               // Need label to explain what it is
                add(new JLabel(I18nManager.getText("dialog.pointtype.desc")), BorderLayout.NORTH);
                // panel for the checkboxes
-               JPanel gridPanel = new JPanel();
-               gridPanel.setLayout(new GridLayout(0, 3, 15, 3));
-               final String[] keys = {"track", "waypoint", "photo"};
-               for (int i=0; i<3; i++)
+               _gridPanel = new JPanel();
+               _gridPanel.setLayout(new GridLayout(0, 3, 15, 3));
+               final String[] keys = {"track", "waypoint", "photo", "audio"};
+               for (int i=0; i<4; i++)
                {
                        _checkboxes[i] = new JCheckBox(I18nManager.getText("dialog.pointtype." + keys[i]));
                        _checkboxes[i].setSelected(true);
-                       gridPanel.add(_checkboxes[i]);
+                       if (i<3) {_gridPanel.add(_checkboxes[i]);}
                }
-               add(gridPanel, BorderLayout.CENTER);
-               _checkboxes[3] = new JCheckBox(I18nManager.getText("dialog.pointtype.selection"));
-               add(_checkboxes[3], BorderLayout.SOUTH);
+               add(_gridPanel, BorderLayout.CENTER);
+               _checkboxes[4] = new JCheckBox(I18nManager.getText("dialog.pointtype.selection"));
+               add(_checkboxes[4], BorderLayout.SOUTH);
        }
 
+
        /**
         * Initialize the checkboxes from the given data
         * @param inTrackInfo TrackInfo object
@@ -64,14 +66,22 @@ public class PointTypeSelector extends JPanel
        public void init(TrackInfo inTrackInfo)
        {
                // Get whether track has track points, waypoints, photos
-               boolean[] flags = {inTrackInfo.getTrack().hasTrackPoints(),
+               boolean[] dataFlags = {inTrackInfo.getTrack().hasTrackPoints(),
                                inTrackInfo.getTrack().hasWaypoints(),
-                               inTrackInfo.getPhotoList().getNumPhotos() > 0
+                               inTrackInfo.getPhotoList().getNumPhotos() > 0,
+                               inTrackInfo.getAudioList().getNumAudios() > 0
                };
+               // Rearrange grid to just show the appropriate entries
+               final boolean[] showFlags = {true, true, dataFlags[2] || !dataFlags[3], dataFlags[3]};
+               _gridPanel.removeAll();
+               for (int i=0; i<4; i++) {
+                       if (showFlags[i]) {_gridPanel.add(_checkboxes[i]);}
+               }
                // Enable or disable checkboxes according to data present
-               for (int i=0; i<3; i++)
+               for (int i=0; i<4; i++)
                {
-                       if (flags[i]) {
+                       if (dataFlags[i]) {
+                               if (!_checkboxes[i].isEnabled()) {_checkboxes[i].setSelected(true);}
                                _checkboxes[i].setEnabled(true);
                        }
                        else {
@@ -79,8 +89,8 @@ public class PointTypeSelector extends JPanel
                                _checkboxes[i].setEnabled(false);
                        }
                }
-               _checkboxes[3].setEnabled(inTrackInfo.getSelection().hasRangeSelected());
-               _checkboxes[3].setSelected(false);
+               _checkboxes[4].setEnabled(inTrackInfo.getSelection().hasRangeSelected());
+               _checkboxes[4].setSelected(false);
        }
 
        /**
@@ -107,12 +117,20 @@ public class PointTypeSelector extends JPanel
                return _checkboxes[2].isSelected();
        }
 
+       /**
+        * @return true if audio points selected
+        */
+       public boolean getAudiopointsSelected()
+       {
+               return _checkboxes[3].isSelected();
+       }
+
        /**
         * @return true if only the current selection should be saved
         */
        public boolean getJustSelection()
        {
-               return _checkboxes[3].isSelected();
+               return _checkboxes[4].isSelected();
        }
 
        /**
@@ -121,6 +139,6 @@ public class PointTypeSelector extends JPanel
        public boolean getAnythingSelected()
        {
                return getTrackpointsSelected() || getWaypointsSelected()
-                       || getPhotopointsSelected();
+                       || getPhotopointsSelected() || getAudiopointsSelected();
        }
 }
index cec38a2ef0d4efef537246dca164aa587e614e57..313b72aea19ff9080917377516bc285c532d8cea 100644 (file)
@@ -32,6 +32,7 @@ import tim.prune.config.Config;
 import tim.prune.data.NumberUtils;
 import tim.prune.data.Track;
 import tim.prune.function.Export3dFunction;
+import tim.prune.gui.DialogCloser;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.threedee.LineDialog;
 import tim.prune.threedee.ThreeDModel;
@@ -161,6 +162,7 @@ public class PovExporter extends Export3dFunction
                }
                _fontName = new JTextField(defaultFont, 12);
                _fontName.setAlignmentX(Component.LEFT_ALIGNMENT);
+               _fontName.addKeyListener(new DialogCloser(_dialog));
                centralPanel.add(_fontName);
                //coordinates of camera
                JLabel cameraXLabel = new JLabel(I18nManager.getText("dialog.exportpov.camerax"));
index c27b2f359ae680fc560db1922a869b08000c30a8..5bfd7c033adf5a62764304da5fe799666aa93fce 100644 (file)
@@ -31,6 +31,7 @@ import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.Track;
 import tim.prune.function.Export3dFunction;
+import tim.prune.gui.DialogCloser;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.threedee.ThreeDModel;
 
@@ -131,8 +132,7 @@ public class SvgExporter extends Export3dFunction
                buttonPanel.add(okButton);
                JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
                cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
+                       public void actionPerformed(ActionEvent e) {
                                _dialog.dispose();
                        }
                });
@@ -148,6 +148,7 @@ public class SvgExporter extends Export3dFunction
                phiLabel.setHorizontalAlignment(SwingConstants.TRAILING);
                centralPanel.add(phiLabel);
                _phiField = new JTextField("" + _phi);
+               _phiField.addKeyListener(new DialogCloser(_dialog));
                centralPanel.add(_phiField);
                JLabel thetaLabel = new JLabel(I18nManager.getText("dialog.exportsvg.theta"));
                thetaLabel.setHorizontalAlignment(SwingConstants.TRAILING);
index fc019747b2fa5de600bb5e575deace6309604c15..0d9c5d4988320c14a4cc98aaee72d96c50ff2f33 100644 (file)
@@ -45,4 +45,12 @@ public class SvgFragment implements Comparable<SvgFragment>
        {
                return _fragment.equals(inOther._fragment);
        }
+
+       /**
+        * @param inOther other object to compare this one with
+        * @return true if the objects are equal
+        */
+       public boolean equals(Object inOther) {
+               return (inOther instanceof SvgFragment?equals((SvgFragment) inOther):false);
+       }
 }
diff --git a/tim/prune/undo/UndoConnectMedia.java b/tim/prune/undo/UndoConnectMedia.java
new file mode 100644 (file)
index 0000000..e75f02f
--- /dev/null
@@ -0,0 +1,75 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.UpdateMessageBroker;\r
+import tim.prune.data.AudioFile;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.Photo;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo the connection of a photo and/or audio to a point\r
+ */\r
+public class UndoConnectMedia implements UndoOperation\r
+{\r
+       private DataPoint _point = null;\r
+       private String _photoFilename = null;\r
+       private String _audioFilename = null;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inPoint data point\r
+        * @param inPhotoFilename filename of photo, or null if photo not connected\r
+        * @param inAudioFilename filename of audio, or null of audio not connected\r
+        */\r
+       public UndoConnectMedia(DataPoint inPoint, String inPhotoFilename, String inAudioFilename)\r
+       {\r
+               _point = inPoint;\r
+               _photoFilename = inPhotoFilename;\r
+               _audioFilename = inAudioFilename;\r
+       }\r
+\r
+\r
+       /**\r
+        * @return description of operation including photo and/or audio filename(s)\r
+        */\r
+       public String getDescription()\r
+       {\r
+               String desc = I18nManager.getText("undo.connect") + " " + (_photoFilename==null?"":_photoFilename)\r
+                + (_photoFilename!=null && _audioFilename!=null?", ":"")\r
+                + (_audioFilename==null?"":_audioFilename);\r
+               return desc;\r
+       }\r
+\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               if (_photoFilename != null)\r
+               {\r
+                       // Disconnect photo\r
+                       Photo photo = _point.getPhoto();\r
+                       if (photo != null)\r
+                       {\r
+                               _point.setPhoto(null);\r
+                               photo.setDataPoint(null);\r
+                       }\r
+               }\r
+               if (_audioFilename != null)\r
+               {\r
+                       // Disconnect audio\r
+                       AudioFile audio = _point.getAudio();\r
+                       if (audio != null)\r
+                       {\r
+                               _point.setAudio(null);\r
+                               audio.setDataPoint(null);\r
+                       }\r
+               }\r
+               // inform subscribers\r
+               UpdateMessageBroker.informSubscribers();\r
+       }\r
+}
\ No newline at end of file
diff --git a/tim/prune/undo/UndoConnectPhoto.java b/tim/prune/undo/UndoConnectPhoto.java
deleted file mode 100644 (file)
index 3e653ce..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package tim.prune.undo;\r
-\r
-import tim.prune.I18nManager;\r
-import tim.prune.UpdateMessageBroker;\r
-import tim.prune.data.DataPoint;\r
-import tim.prune.data.Photo;\r
-import tim.prune.data.TrackInfo;\r
-\r
-/**\r
- * Operation to undo the connection of a photo to a point\r
- */\r
-public class UndoConnectPhoto implements UndoOperation\r
-{\r
-       private DataPoint _point = null;\r
-       private String _filename = null;\r
-\r
-\r
-       /**\r
-        * Constructor\r
-        * @param inPoint data point\r
-        * @param inFilename filename of photo\r
-        */\r
-       public UndoConnectPhoto(DataPoint inPoint, String inFilename)\r
-       {\r
-               _point = inPoint;\r
-               _filename = inFilename;\r
-       }\r
-\r
-\r
-       /**\r
-        * @return description of operation including photo filename\r
-        */\r
-       public String getDescription()\r
-       {\r
-               String desc = I18nManager.getText("undo.connectphoto") + " " + _filename;\r
-               return desc;\r
-       }\r
-\r
-\r
-       /**\r
-        * Perform the undo operation on the given Track\r
-        * @param inTrackInfo TrackInfo object on which to perform the operation\r
-        */\r
-       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
-       {\r
-               // Disconnect again\r
-               Photo photo = _point.getPhoto();\r
-               if (photo != null)\r
-               {\r
-                       _point.setPhoto(null);\r
-                       photo.setDataPoint(null);\r
-                       // inform subscribers\r
-                       UpdateMessageBroker.informSubscribers();\r
-               }\r
-               else\r
-               {\r
-                       // throw exception if failed\r
-                       throw new UndoException(getDescription());\r
-               }\r
-       }\r
-}
\ No newline at end of file
diff --git a/tim/prune/undo/UndoCorrelateAudios.java b/tim/prune/undo/UndoCorrelateAudios.java
new file mode 100644 (file)
index 0000000..e0c1f95
--- /dev/null
@@ -0,0 +1,81 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.AudioFile;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo an auto-correlation of audios with points\r
+ * (very similar to UndoCorrelatePhotos)\r
+ */\r
+public class UndoCorrelateAudios implements UndoOperation\r
+{\r
+       private DataPoint[] _contents = null;\r
+       private DataPoint[] _audioPoints = null;\r
+       private int _numCorrelated = -1;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inTrackInfo track information\r
+        */\r
+       public UndoCorrelateAudios(TrackInfo inTrackInfo)\r
+       {\r
+               // Copy track contents\r
+               _contents = inTrackInfo.getTrack().cloneContents();\r
+               // Copy points associated with audios before correlation\r
+               int numAudios = inTrackInfo.getAudioList().getNumAudios();\r
+               _audioPoints = new DataPoint[numAudios];\r
+               for (int i=0; i<numAudios; i++) {\r
+                       _audioPoints[i] = inTrackInfo.getAudioList().getAudio(i).getDataPoint();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * @param inNumCorrelated number of audios correlated\r
+        */\r
+       public void setNumAudiosCorrelated(int inNumCorrelated)\r
+       {\r
+               _numCorrelated = inNumCorrelated;\r
+       }\r
+\r
+       /**\r
+        * @return description of operation including parameters\r
+        */\r
+       public String getDescription()\r
+       {\r
+               return I18nManager.getText("undo.correlateaudios") + " (" + _numCorrelated + ")";\r
+       }\r
+\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // restore track to previous values\r
+               inTrackInfo.getTrack().replaceContents(_contents);\r
+               // restore audio association\r
+               for (int i=0; i<_audioPoints.length; i++)\r
+               {\r
+                       AudioFile audio = inTrackInfo.getAudioList().getAudio(i);\r
+                       // Only need to look at connected ones, since correlation wouldn't disconnect\r
+                       if (audio.getCurrentStatus() == AudioFile.Status.CONNECTED)\r
+                       {\r
+                               DataPoint prevPoint = _audioPoints[i];\r
+                               DataPoint currPoint = audio.getDataPoint();\r
+                               audio.setDataPoint(prevPoint);\r
+                               if (currPoint != null) {\r
+                                       currPoint.setAudio(null); // disconnect\r
+                               }\r
+                               if (prevPoint != null) {\r
+                                       prevPoint.setAudio(audio); // reconnect to prev point\r
+                               }\r
+                       }\r
+               }\r
+               // clear selection\r
+               inTrackInfo.getSelection().clearAll();\r
+       }\r
+}\r
index 39c7a86468fcaffde516767591def878307b6c60..9b8e1af35acad0d823600151d27b4b04f0f5b45b 100644 (file)
@@ -44,7 +44,7 @@ public class UndoCorrelatePhotos implements UndoOperation
         */\r
        public String getDescription()\r
        {\r
-               return I18nManager.getText("undo.correlate") + " (" + _numPhotosCorrelated + ")";\r
+               return I18nManager.getText("undo.correlatephotos") + " (" + _numPhotosCorrelated + ")";\r
        }\r
 \r
 \r
@@ -60,17 +60,21 @@ public class UndoCorrelatePhotos implements UndoOperation
                for (int i=0; i<_photoPoints.length; i++)\r
                {\r
                        Photo photo = inTrackInfo.getPhotoList().getPhoto(i);\r
-                       // Only need to look at connected photos, if they're still tagged then leave them\r
+                       // Only need to look at connected photos, since correlation wouldn't disconnect\r
                        if (photo.getCurrentStatus() == Photo.Status.CONNECTED)\r
                        {\r
-                               DataPoint point = _photoPoints[i];\r
-                               photo.setDataPoint(point);\r
-                               if (point != null) {\r
-                                       point.setPhoto(photo);\r
+                               DataPoint prevPoint = _photoPoints[i];\r
+                               DataPoint currPoint = photo.getDataPoint();\r
+                               photo.setDataPoint(prevPoint);\r
+                               if (currPoint != null) {\r
+                                       currPoint.setPhoto(null); // disconnect\r
+                               }\r
+                               if (prevPoint != null) {\r
+                                       prevPoint.setPhoto(photo); // reconnect to prev point\r
                                }\r
                        }\r
                }\r
                // clear selection\r
                inTrackInfo.getSelection().clearAll();\r
        }\r
-}
\ No newline at end of file
+}\r
diff --git a/tim/prune/undo/UndoDeleteAudio.java b/tim/prune/undo/UndoDeleteAudio.java
new file mode 100644 (file)
index 0000000..e413426
--- /dev/null
@@ -0,0 +1,70 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.UpdateMessageBroker;\r
+import tim.prune.data.AudioFile;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo a delete of a single audio item, either with or without point\r
+ */\r
+public class UndoDeleteAudio implements UndoOperation\r
+{\r
+       private int _audioIndex = -1;\r
+       private AudioFile _audio = null;\r
+       private int _pointIndex = -1;\r
+       private DataPoint _point = null;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inAudio audio item\r
+        * @param inAudioIndex index number of audio within list\r
+        * @param inPoint data point\r
+        * @param inPointIndex index number of point within track\r
+        */\r
+       public UndoDeleteAudio(AudioFile inAudio, int inAudioIndex, DataPoint inPoint, int inPointIndex)\r
+       {\r
+               _audio = inAudio;\r
+               _audioIndex = inAudioIndex;\r
+               _point = inPoint;\r
+               _pointIndex = inPointIndex;\r
+       }\r
+\r
+\r
+       /**\r
+        * @return description of operation including filename\r
+        */\r
+       public String getDescription() {\r
+               return I18nManager.getText("undo.removeaudio") + " " + _audio.getFile().getName();\r
+       }\r
+\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // restore audio\r
+               inTrackInfo.getAudioList().addAudio(_audio, _audioIndex);\r
+               // if there's a point to restore, restore it\r
+               if (_point != null)\r
+               {\r
+                       if (!inTrackInfo.getTrack().insertPoint(_point, _pointIndex)) {\r
+                               throw new UndoException(getDescription());\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       // update needed if not already triggered by track update\r
+                       UpdateMessageBroker.informSubscribers();\r
+               }\r
+               // Ensure that audio is associated with point and vice versa\r
+               _audio.setDataPoint(_point);\r
+               if (_point != null) {\r
+                       _point.setAudio(_audio);\r
+               }\r
+       }\r
+}\r
index b2831f0301ab32c217ec81848074e2f1213facf7..4c37e616faf31f76c320ddaafe69e11edb00b982 100644 (file)
@@ -38,7 +38,7 @@ public class UndoDeletePhoto implements UndoOperation
         */\r
        public String getDescription()\r
        {\r
-               String desc = I18nManager.getText("undo.deletephoto") + " " + _photo.getFile().getName();\r
+               String desc = I18nManager.getText("undo.removephoto") + " " + _photo.getFile().getName();\r
                return desc;\r
        }\r
 \r
@@ -66,8 +66,7 @@ public class UndoDeletePhoto implements UndoOperation
                }\r
                // Ensure that photo is associated with point and vice versa\r
                _photo.setDataPoint(_point);\r
-               if (_point != null)\r
-               {\r
+               if (_point != null) {\r
                        _point.setPhoto(_photo);\r
                }\r
        }\r
diff --git a/tim/prune/undo/UndoDisconnectMedia.java b/tim/prune/undo/UndoDisconnectMedia.java
new file mode 100644 (file)
index 0000000..51b310f
--- /dev/null
@@ -0,0 +1,73 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.AudioFile;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.Photo;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo the disconnection of a photo or audio from a point\r
+ */\r
+public class UndoDisconnectMedia implements UndoOperation\r
+{\r
+       private DataPoint _point = null;\r
+       private Photo _photo = null;\r
+       private AudioFile _audio = null;\r
+       private String _filename = null;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inPoint data point\r
+        * @param inPhoto true if photo was disconnected\r
+        * @param inAudio true if audio was disconnected\r
+        * @param inFilename filename of photo / audio\r
+        */\r
+       public UndoDisconnectMedia(DataPoint inPoint, boolean inPhoto, boolean inAudio, String inFilename)\r
+       {\r
+               _point = inPoint;\r
+               if (inPhoto) {\r
+                       _photo = inPoint.getPhoto();\r
+               }\r
+               if (inAudio) {\r
+                       _audio = inPoint.getAudio();\r
+               }\r
+               _filename = inFilename;\r
+       }\r
+\r
+\r
+       /**\r
+        * @return description of operation including filename\r
+        */\r
+       public String getDescription()\r
+       {\r
+               return I18nManager.getText("undo.disconnect") + " " + _filename;\r
+       }\r
+\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // Connect again\r
+               if (_point != null && _photo != null)\r
+               {\r
+                       _point.setPhoto(_photo);\r
+                       _photo.setDataPoint(_point);\r
+               }\r
+               else if (_point != null && _audio != null)\r
+               {\r
+                       _point.setAudio(_audio);\r
+                       _audio.setDataPoint(_point);\r
+               }\r
+               else {\r
+                       // throw exception if failed\r
+                       throw new UndoException(getDescription());\r
+               }\r
+               // clear selection\r
+               inTrackInfo.getSelection().clearAll();\r
+       }\r
+}
\ No newline at end of file
diff --git a/tim/prune/undo/UndoDisconnectPhoto.java b/tim/prune/undo/UndoDisconnectPhoto.java
deleted file mode 100644 (file)
index c12f74e..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package tim.prune.undo;\r
-\r
-import tim.prune.I18nManager;\r
-import tim.prune.UpdateMessageBroker;\r
-import tim.prune.data.DataPoint;\r
-import tim.prune.data.Photo;\r
-import tim.prune.data.TrackInfo;\r
-\r
-/**\r
- * Operation to undo the disconnection of a photo from a point\r
- */\r
-public class UndoDisconnectPhoto implements UndoOperation\r
-{\r
-       private DataPoint _point = null;\r
-       private Photo _photo = null;\r
-       private String _filename = null;\r
-\r
-\r
-       /**\r
-        * Constructor\r
-        * @param inPoint data point\r
-        * @param inFilename filename of photo\r
-        */\r
-       public UndoDisconnectPhoto(DataPoint inPoint, String inFilename)\r
-       {\r
-               _point = inPoint;\r
-               _photo = inPoint.getPhoto();\r
-               _filename = inFilename;\r
-       }\r
-\r
-\r
-       /**\r
-        * @return description of operation including photo filename\r
-        */\r
-       public String getDescription()\r
-       {\r
-               String desc = I18nManager.getText("undo.disconnectphoto") + " " + _filename;\r
-               return desc;\r
-       }\r
-\r
-\r
-       /**\r
-        * Perform the undo operation on the given Track\r
-        * @param inTrackInfo TrackInfo object on which to perform the operation\r
-        */\r
-       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
-       {\r
-               // Connect again\r
-               if (_point != null && _photo != null)\r
-               {\r
-                       _point.setPhoto(_photo);\r
-                       _photo.setDataPoint(_point);\r
-                       // inform subscribers\r
-                       UpdateMessageBroker.informSubscribers();\r
-               }\r
-               else\r
-               {\r
-                       // throw exception if failed\r
-                       throw new UndoException(getDescription());\r
-               }\r
-       }\r
-}
\ No newline at end of file
index 5c0818fc94a65eade07b5d0fd391f482ebbc4469..377f21c22f93f7b0a95f7c73378a30a09d5276c7 100644 (file)
@@ -16,6 +16,8 @@ public class UndoLoad implements UndoOperation
        private DataPoint[] _contents = null;\r
        private PhotoList _photoList = null;\r
        private FileInfo _oldFileInfo = null;\r
+       // Numbers of each media before operation\r
+       private int _numPhotos = -1, _numAudios = -1;\r
 \r
 \r
        /**\r
@@ -58,6 +60,16 @@ public class UndoLoad implements UndoOperation
                return desc;\r
        }\r
 \r
+       /**\r
+        * Set the number of photos and audios before the load operation\r
+        * @param inNumPhotos number of photos\r
+        * @param inNumAudios number of audios\r
+        */\r
+       public void setNumPhotosAudios(int inNumPhotos, int inNumAudios)\r
+       {\r
+               _numPhotos = inNumPhotos;\r
+               _numAudios = inNumAudios;\r
+       }\r
 \r
        /**\r
         * Perform the undo operation on the given Track\r
@@ -81,10 +93,12 @@ public class UndoLoad implements UndoOperation
                else\r
                {\r
                        // replace photos how they were\r
-                       if (_photoList != null)\r
-                       {\r
+                       if (_photoList != null) {\r
                                inTrackInfo.getPhotoList().restore(_photoList);\r
                        }\r
+                       // Crop media lists to previous size (if specified)\r
+                       if (_numPhotos > -1) {inTrackInfo.getPhotoList().cropTo(_numPhotos);}\r
+                       if (_numAudios > -1) {inTrackInfo.getAudioList().cropTo(_numAudios);}\r
                        // replace track contents with old\r
                        if (!inTrackInfo.getTrack().replaceContents(_contents))\r
                        {\r
@@ -94,4 +108,4 @@ public class UndoLoad implements UndoOperation
                // clear selection\r
                inTrackInfo.getSelection().clearAll();\r
        }\r
-}
\ No newline at end of file
+}\r
diff --git a/tim/prune/undo/UndoLoadAudios.java b/tim/prune/undo/UndoLoadAudios.java
new file mode 100644 (file)
index 0000000..c5b49fc
--- /dev/null
@@ -0,0 +1,49 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo a load audios operation\r
+ */\r
+public class UndoLoadAudios implements UndoOperation\r
+{\r
+       /** Number of audio files added */\r
+       private int _numAudios = -1;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inNumAudios number of audios loaded\r
+        */\r
+       public UndoLoadAudios(int inNumAudios)\r
+       {\r
+               _numAudios = inNumAudios;\r
+       }\r
+\r
+\r
+       /**\r
+        * @return description of operation including number of audios loaded\r
+        */\r
+       public String getDescription()\r
+       {\r
+               String desc = I18nManager.getText("undo.loadaudios");\r
+               if (_numAudios > 0)\r
+                       desc = desc + " (" + _numAudios + ")";\r
+               return desc;\r
+       }\r
+\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // crop audio list to previous size\r
+               int cropIndex = inTrackInfo.getAudioList().getNumAudios() - _numAudios;\r
+               inTrackInfo.getAudioList().cropTo(cropIndex);\r
+               // clear selection\r
+               inTrackInfo.getSelection().clearAll();\r
+       }\r
+}\r