]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Version 6, October 2008
authoractivityworkshop <mail@activityworkshop.net>
Sat, 14 Feb 2015 14:09:17 +0000 (15:09 +0100)
committeractivityworkshop <mail@activityworkshop.net>
Sat, 14 Feb 2015 14:09:17 +0000 (15:09 +0100)
70 files changed:
tim/prune/App.java
tim/prune/Config.java [new file with mode: 0644]
tim/prune/ConfigException.java [new file with mode: 0644]
tim/prune/GpsPruner.java
tim/prune/I18nManager.java
tim/prune/browser/UrlGenerator.java
tim/prune/configuration.txt [new file with mode: 0644]
tim/prune/correlate/PhotoCorrelator.java
tim/prune/data/Altitude.java
tim/prune/data/Coordinate.java
tim/prune/data/DataPoint.java
tim/prune/data/LatLonRectangle.java [new file with mode: 0644]
tim/prune/data/Selection.java
tim/prune/data/Timestamp.java
tim/prune/data/Track.java
tim/prune/data/TrackInfo.java
tim/prune/gui/AboutScreen.java
tim/prune/gui/CheckVersionScreen.java [new file with mode: 0644]
tim/prune/gui/DetailsDisplay.java
tim/prune/gui/GenericChart.java
tim/prune/gui/IconManager.java [new file with mode: 0644]
tim/prune/gui/MapChart.java [deleted file]
tim/prune/gui/MenuManager.java
tim/prune/gui/SelectorDisplay.java
tim/prune/gui/StatusBar.java
tim/prune/gui/TimeOffsetDialog.java [new file with mode: 0644]
tim/prune/gui/WholeNumberField.java [new file with mode: 0644]
tim/prune/gui/images/autopan.gif [new file with mode: 0644]
tim/prune/gui/images/autopan_on.gif [new file with mode: 0644]
tim/prune/gui/images/cut_and_move.gif [new file with mode: 0644]
tim/prune/gui/images/delete_point_icon.gif [new file with mode: 0644]
tim/prune/gui/images/delete_range_icon.gif [new file with mode: 0644]
tim/prune/gui/images/link.gif [new file with mode: 0644]
tim/prune/gui/images/map_icon.gif [new file with mode: 0644]
tim/prune/gui/images/map_icon_on.gif [new file with mode: 0644]
tim/prune/gui/images/points_connected.gif [new file with mode: 0644]
tim/prune/gui/images/points_disconnected.gif [new file with mode: 0644]
tim/prune/gui/images/zoom_in.gif [new file with mode: 0644]
tim/prune/gui/images/zoom_out.gif [new file with mode: 0644]
tim/prune/gui/map/MapCanvas.java
tim/prune/gui/map/MapPosition.java [new file with mode: 0644]
tim/prune/gui/map/MapTileCacher.java [new file with mode: 0644]
tim/prune/gui/map/MapUtils.java [new file with mode: 0644]
tim/prune/gui/map/MapWindow.java [deleted file]
tim/prune/lang/prune-texts.properties
tim/prune/lang/prune-texts_de.properties
tim/prune/lang/prune-texts_de_CH.properties
tim/prune/lang/prune-texts_es.properties
tim/prune/lang/prune-texts_fr.properties
tim/prune/lang/prune-texts_it.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_pl.properties
tim/prune/load/FileLoader.java
tim/prune/load/GenericFileFilter.java [new file with mode: 0644]
tim/prune/load/GpsLoader.java [new file with mode: 0644]
tim/prune/load/JpegLoader.java
tim/prune/load/TextFileLoader.java
tim/prune/load/xml/XmlFileLoader.java
tim/prune/readme.txt
tim/prune/save/FileSaver.java
tim/prune/save/GpxExporter.java
tim/prune/save/KmlExporter.java
tim/prune/save/ModelSegment.java [new file with mode: 0644]
tim/prune/save/PovExporter.java
tim/prune/threedee/Java3DWindow.java
tim/prune/threedee/ThreeDModel.java
tim/prune/threedee/WindowFactory.java
tim/prune/undo/UndoAddTimeOffset.java [new file with mode: 0644]
tim/prune/undo/UndoConnectPhotoWithClone.java [new file with mode: 0644]
tim/prune/undo/UndoCreatePoint.java [new file with mode: 0644]
tim/prune/undo/UndoCutAndMove.java [new file with mode: 0644]

index cb532dbf9d6bcb10a0dfb72ea756b1ac26f1a2b3..ecde5481a1d269ed23028cb48208d4adcf6abb94 100644 (file)
@@ -11,8 +11,12 @@ import tim.prune.browser.BrowserLauncher;
 import tim.prune.browser.UrlGenerator;
 import tim.prune.correlate.PhotoCorrelator;
 import tim.prune.correlate.PointPair;
+import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
+import tim.prune.data.LatLonRectangle;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
 import tim.prune.data.Photo;
 import tim.prune.data.PhotoList;
 import tim.prune.data.Track;
@@ -21,9 +25,10 @@ import tim.prune.edit.FieldEditList;
 import tim.prune.edit.PointEditor;
 import tim.prune.edit.PointNameEditor;
 import tim.prune.gui.MenuManager;
+import tim.prune.gui.TimeOffsetDialog;
 import tim.prune.gui.UndoManager;
-import tim.prune.gui.map.MapWindow;
 import tim.prune.load.FileLoader;
+import tim.prune.load.GpsLoader;
 import tim.prune.load.JpegLoader;
 import tim.prune.save.ExifSaver;
 import tim.prune.save.FileSaver;
@@ -33,9 +38,13 @@ import tim.prune.save.PovExporter;
 import tim.prune.threedee.ThreeDException;
 import tim.prune.threedee.ThreeDWindow;
 import tim.prune.threedee.WindowFactory;
+import tim.prune.undo.UndoAddTimeOffset;
 import tim.prune.undo.UndoCompress;
 import tim.prune.undo.UndoConnectPhoto;
+import tim.prune.undo.UndoConnectPhotoWithClone;
 import tim.prune.undo.UndoCorrelatePhotos;
+import tim.prune.undo.UndoCreatePoint;
+import tim.prune.undo.UndoCutAndMove;
 import tim.prune.undo.UndoDeleteDuplicates;
 import tim.prune.undo.UndoDeletePhoto;
 import tim.prune.undo.UndoDeletePoint;
@@ -65,13 +74,14 @@ public class App
        private MenuManager _menuManager = null;
        private FileLoader _fileLoader = null;
        private JpegLoader _jpegLoader = null;
+       private GpsLoader _gpsLoader = null;
        private FileSaver _fileSaver = null;
        private KmlExporter _kmlExporter = null;
        private GpxExporter _gpxExporter = null;
        private PovExporter _povExporter = null;
        private BrowserLauncher _browserLauncher = null;
        private Stack _undoStack = null;
-       private boolean _reversePointsConfirmed = false;
+       private boolean _mangleTimestampsConfirmed = false;
 
        // Constants
        public static final int REARRANGE_TO_START   = 0;
@@ -146,9 +156,18 @@ public class App
        {
                if (_jpegLoader == null)
                        _jpegLoader = new JpegLoader(this, _frame);
-               _jpegLoader.openDialog();
+               _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
        }
 
+       /**
+        * Start a load from Gps
+        */
+       public void beginLoadFromGps()
+       {
+               if (_gpsLoader == null)
+                       _gpsLoader = new GpsLoader(this, _frame);
+               _gpsLoader.openDialog();
+       }
 
        /**
         * Save the file in the selected format
@@ -165,7 +184,9 @@ public class App
                        if (_fileSaver == null) {
                                _fileSaver = new FileSaver(this, _frame, _track);
                        }
-                       _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter());
+                       char delim = ',';
+                       if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
+                       _fileSaver.showDialog(delim);
                }
        }
 
@@ -569,11 +590,11 @@ public class App
                int selStart = _trackInfo.getSelection().getStart();
                int selEnd = _trackInfo.getSelection().getEnd();
                if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
-                       || _reversePointsConfirmed
+                       || _mangleTimestampsConfirmed
                        || (JOptionPane.showConfirmDialog(_frame,
                                 I18nManager.getText("dialog.confirmreversetrack.text"),
                                 I18nManager.getText("dialog.confirmreversetrack.title"),
-                                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true)))
+                                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
                {
                        UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
                        // call track to reverse range
@@ -586,6 +607,43 @@ public class App
                }
        }
 
+       /**
+        * Trigger the dialog to add a time offset to the current selection
+        */
+       public void beginAddTimeOffset()
+       {
+               int selStart = _trackInfo.getSelection().getStart();
+               int selEnd = _trackInfo.getSelection().getEnd();
+               if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)) {
+                       JOptionPane.showMessageDialog(_frame,
+                               I18nManager.getText("dialog.addtimeoffset.notimestamps"),
+                               I18nManager.getText("dialog.addtimeoffset.title"), JOptionPane.ERROR_MESSAGE);
+               }
+               else {
+                       TimeOffsetDialog timeDialog = new TimeOffsetDialog(this, _frame);
+                       timeDialog.showDialog();
+               }
+       }
+
+       /**
+        * Complete the add time offset function with the specified offset
+        * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
+        */
+       public void finishAddTimeOffset(long inTimeOffset)
+       {
+               // Construct undo information
+               int selStart = _trackInfo.getSelection().getStart();
+               int selEnd = _trackInfo.getSelection().getEnd();
+               UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
+               if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset))
+               {
+                       _undoStack.add(undo);
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
+                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
+               }
+       }
+
+
        /**
         * Merge the track segments within the current selection
         */
@@ -632,6 +690,27 @@ public class App
        }
 
 
+       /**
+        * Create a new point at the given lat/long coordinates
+        * @param inLat latitude
+        * @param inLong longitude
+        */
+       public void createPoint(double inLat, double inLong)
+       {
+               // create undo object
+               UndoCreatePoint undo = new UndoCreatePoint();
+               // create point and add to track
+               DataPoint point = new DataPoint(new Latitude(inLat, Coordinate.FORMAT_NONE), new Longitude(inLong, Coordinate.FORMAT_NONE), null);
+               point.setSegmentStart(true);
+               _track.appendPoints(new DataPoint[] {point});
+               _trackInfo.getSelection().selectPoint(_trackInfo.getTrack().getNumPoints()-1);
+               // add undo object to stack
+               _undoStack.add(undo);
+               // update listeners
+               UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
+       }
+
+
        /**
         * Rearrange the waypoints into track order
         * @param inFunction nearest point, all to end or all to start
@@ -653,6 +732,7 @@ public class App
                if (success)
                {
                        _undoStack.add(undo);
+                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.rearrangewaypoints"));
                }
                else
                {
@@ -662,6 +742,46 @@ public class App
        }
 
 
+       /**
+        * Cut the current selection and move it to before the currently selected point
+        */
+       public void cutAndMoveSelection()
+       {
+               int startIndex = _trackInfo.getSelection().getStart();
+               int endIndex = _trackInfo.getSelection().getEnd();
+               int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
+               // If timestamps would be mangled by cut/move, confirm
+               if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
+                       || _mangleTimestampsConfirmed
+                       || (JOptionPane.showConfirmDialog(_frame,
+                                I18nManager.getText("dialog.confirmcutandmove.text"),
+                                I18nManager.getText("dialog.confirmcutandmove.title"),
+                                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
+               {
+                       // Find points to set segment flags
+                       DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
+                       DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
+                       DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
+                       // Make undo object
+                       UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
+                       // Call track info to move track section
+                       if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
+                       {
+                               // Set segment start flags (first track point, next track point, move to point)
+                               if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
+                               if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
+                               if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
+
+                               // Add undo object to stack, set confirm message
+                               _undoStack.add(undo);
+                               _trackInfo.getSelection().deselectRange();
+                               UpdateMessageBroker.informSubscribers();
+                               UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
+                       }
+               }
+       }
+
+
        /**
         * Open a new window with the 3d view
         */
@@ -716,6 +836,20 @@ public class App
         * @param inFilename filename used
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
+       {
+               informDataLoaded(inFieldArray, inDataArray, inAltFormat, inFilename, false);
+       }
+
+       /**
+        * Receive loaded data and optionally merge with current Track
+        * @param inFieldArray array of fields
+        * @param inDataArray array of data
+        * @param inAltFormat altitude format
+        * @param inFilename filename used
+        * @param inOverrideAppend true to override append question and always append
+        */
+       public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat,
+               String inFilename, boolean inOverrideAppend)
        {
                // Check whether loaded array can be properly parsed into a Track
                Track loadedTrack = new Track();
@@ -732,10 +866,13 @@ public class App
                if (_track != null && _track.getNumPoints() > 0)
                {
                        // ask whether to replace or append
-                       int answer = JOptionPane.showConfirmDialog(_frame,
-                               I18nManager.getText("dialog.openappend.text"),
-                               I18nManager.getText("dialog.openappend.title"),
-                               JOptionPane.YES_NO_CANCEL_OPTION);
+                       int answer = JOptionPane.YES_OPTION;
+                       if (!inOverrideAppend) {
+                               answer = JOptionPane.showConfirmDialog(_frame,
+                                       I18nManager.getText("dialog.openappend.text"),
+                                       I18nManager.getText("dialog.openappend.title"),
+                                       JOptionPane.YES_NO_CANCEL_OPTION);
+                       }
                        if (answer == JOptionPane.YES_OPTION)
                        {
                                // append data to current Track
@@ -826,14 +963,39 @@ public class App
        {
                Photo photo = _trackInfo.getCurrentPhoto();
                DataPoint point = _trackInfo.getCurrentPoint();
-               if (photo != null && point != null && point.getPhoto() == null)
+               if (photo != null && point != null)
                {
-                       // connect
-                       _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"));
+                       if (point.getPhoto() != null)
+                       {
+                               // point already has a photo, confirm cloning of new point
+                               if (JOptionPane.showConfirmDialog(_frame,
+                                       I18nManager.getText("dialog.connectphoto.clonepoint"),
+                                       I18nManager.getText("dialog.connect.title"),
+                                       JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)
+                               {
+                                       // Create undo, clone point and attach
+                                       int pointIndex = _trackInfo.getSelection().getCurrentPointIndex() + 1;
+                                       // insert new point after current one
+                                       point = point.clonePoint();
+                                       UndoConnectPhotoWithClone undo = new UndoConnectPhotoWithClone(
+                                               point, photo.getFile().getName(), pointIndex);
+                                       _track.insertPoint(point, pointIndex);
+                                       photo.setDataPoint(point);
+                                       point.setPhoto(photo);
+                                       _undoStack.add(undo);
+                                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+                                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
+                               }
+                       }
+                       else
+                       {
+                               // 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"));
+                       }
                }
        }
 
@@ -990,6 +1152,8 @@ public class App
                                                        // link photo to point
                                                        pointToAdd.setPhoto(pair.getPhoto());
                                                        pair.getPhoto().setDataPoint(pointToAdd);
+                                                       // set to start of segment so not joined in track
+                                                       pointToAdd.setSegmentStart(true);
                                                        // add to point array
                                                        addedPoints[pointNum] = pointToAdd;
                                                        pointNum++;
@@ -1126,19 +1290,17 @@ public class App
         */
        public void showHelp()
        {
-               JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.help.help"),
-                       I18nManager.getText("menu.help"),
-                       JOptionPane.INFORMATION_MESSAGE);
-       }
-
-       /**
-        * Show an OSM map window
-        */
-       public void showOsmMap()
-       {
-               MapWindow map = new MapWindow(_track);
-               map.pack();
-               map.show();
+               // show the dialog and offer to open home page
+               Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")};
+               if (JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.help.help"),
+                               I18nManager.getText("menu.help"), JOptionPane.YES_NO_OPTION,
+                               JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1])
+                       == JOptionPane.YES_OPTION)
+               {
+                       // User selected to launch home page
+                       if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();}
+                       _browserLauncher.launchBrowser("http://activityworkshop.net/software/prune/index.html");
+               }
        }
 
        /**
diff --git a/tim/prune/Config.java b/tim/prune/Config.java
new file mode 100644 (file)
index 0000000..d667b59
--- /dev/null
@@ -0,0 +1,147 @@
+package tim.prune;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+/**
+ * Abstract class to hold application-wide configuration
+ */
+public abstract class Config
+{
+       /** Working directory for loading and saving */
+       private static File _workingDir = null;
+       /** Default language */
+       private static String _langCode = null;
+       /** GPS device name */
+       private static String _gpsDevice = null;
+       /** GPS format name */
+       private static String _gpsFormat = null;
+       /** Font to use for povray */
+       private static String _povrayFont = null;
+       /** True to use metric units */
+       private static boolean _metricUnits = true;
+
+
+       /** Default config file */
+       private static final File DEFAULT_CONFIG_FILE = new File(".pruneconfig");
+
+       /** Key for working directory */
+       private static final String KEY_WORKING_DIR = "prune.directory";
+       /** Key for language code */
+       private static final String KEY_LANGUAGE_CODE = "prune.languagecode";
+       /** Key for GPS device */
+       private static final String KEY_GPS_DEVICE = "prune.gpsdevice";
+       /** Key for GPS format */
+       private static final String KEY_GPS_FORMAT = "prune.gpsformat";
+       /** Key for Povray font */
+       private static final String KEY_POVRAY_FONT = "prune.povrayfont";
+       /** Key for metric/imperial */
+       private static final String KEY_METRIC_UNITS = "prune.metricunits";
+       // TODO: Save config file location so save possible
+
+
+       /**
+        * @return working directory for loading and saving
+        */
+       public static File getWorkingDirectory()
+       {
+               return _workingDir;
+       }
+
+       /**
+        * @param inDirectory working directory to use
+        */
+       public static void setWorkingDirectory(File inDirectory)
+       {
+               _workingDir = inDirectory;
+       }
+
+       /**
+        * Load the default configuration file
+        */
+       public static void loadDefaultFile()
+       {
+               try
+               {
+                       loadFile(DEFAULT_CONFIG_FILE);
+               }
+               catch (ConfigException ce) {} // ignore
+       }
+
+
+       /**
+        * Load configuration from file
+        * @param inFile file to load
+        */
+       public static void loadFile(File inFile) throws ConfigException
+       {
+               // Start with default properties
+               Properties props = getDefaultProperties();
+               // Try to load the file into a properties object
+               boolean loadFailed = false;
+               try
+               {
+                       props.load(new FileInputStream(inFile));
+               }
+               catch (Exception e)
+               {
+                       loadFailed = true;
+               }
+               // Save the properties we know about, ignore the rest
+               _langCode = props.getProperty(KEY_LANGUAGE_CODE);
+               String dir = props.getProperty(KEY_WORKING_DIR);
+               if (dir != null) {setWorkingDirectory(new File(dir));}
+               _gpsDevice = props.getProperty(KEY_GPS_DEVICE);
+               _gpsFormat = props.getProperty(KEY_GPS_FORMAT);
+               _povrayFont = props.getProperty(KEY_POVRAY_FONT);
+               String useMetric = props.getProperty(KEY_METRIC_UNITS);
+               _metricUnits = (useMetric == null || useMetric.equals("") || useMetric.toLowerCase().equals("y"));
+               if (loadFailed) {
+                       throw new ConfigException();
+               }
+       }
+
+       /**
+        * @return Properties object containing default values
+        */
+       private static Properties getDefaultProperties()
+       {
+               Properties props = new Properties();
+               // Fill in defaults
+               props.put(KEY_GPS_DEVICE, "usb:");
+               props.put(KEY_GPS_FORMAT, "garmin");
+               props.put(KEY_POVRAY_FONT, "crystal.ttf"); // alternative: DejaVuSans-Bold.ttf
+               return props;
+       }
+
+       /** @return language code */
+       public static String getLanguageCode()
+       {
+               return _langCode;
+       }
+
+       /** @return gps device */
+       public static String getGpsDevice()
+       {
+               return _gpsDevice;
+       }
+
+       /** @return gps format */
+       public static String getGpsFormat()
+       {
+               return _gpsFormat;
+       }
+
+       /** @return povray font */
+       public static String getPovrayFont()
+       {
+               return _povrayFont;
+       }
+
+       /** @return true to use metric units */
+       public static boolean getUseMetricUnits()
+       {
+               return _metricUnits;
+       }
+}
diff --git a/tim/prune/ConfigException.java b/tim/prune/ConfigException.java
new file mode 100644 (file)
index 0000000..edf6bee
--- /dev/null
@@ -0,0 +1,9 @@
+package tim.prune;
+
+/**
+ * Exception thrown when something went wrong with the config
+ */
+public class ConfigException extends Exception
+{
+
+}
index 7208eb7e83815b2de02341954cb9df3cdfa5441d..eafbf1977de170622e3a8ae3fb1290a59bc126ae 100644 (file)
@@ -3,30 +3,31 @@ package tim.prune;
 import java.awt.event.WindowAdapter;
 import java.awt.BorderLayout;
 import java.awt.event.WindowEvent;
+import java.io.File;
 import java.util.Locale;
-
-import javax.swing.ImageIcon;
 import javax.swing.JFrame;
 import javax.swing.JSplitPane;
 import javax.swing.JToolBar;
 import javax.swing.WindowConstants;
 
 import tim.prune.gui.DetailsDisplay;
-import tim.prune.gui.MapChart;
+import tim.prune.gui.IconManager;
 import tim.prune.gui.MenuManager;
 import tim.prune.gui.ProfileChart;
 import tim.prune.gui.SelectorDisplay;
 import tim.prune.gui.StatusBar;
+import tim.prune.gui.map.MapCanvas;
 
 /**
  * Tool to visualize, edit and prune GPS data
  * Please see the included readme.txt or http://activityworkshop.net
+ * This software is copyright activityworkshop.net and made available through the Gnu GPL
  */
 public class GpsPruner
 {
-       // Final build of version 5
-       public static final String VERSION_NUMBER = "5";
-       public static final String BUILD_NUMBER = "100";
+       // Final build of version 6
+       public static final String VERSION_NUMBER = "6";
+       public static final String BUILD_NUMBER = "117";
        private static App APP = null;
 
 
@@ -37,23 +38,63 @@ public class GpsPruner
        public static void main(String[] args)
        {
                Locale locale = null;
-               if (args.length == 1)
+               String langFilename = null;
+               String configFilename = null;
+               boolean showUsage = false;
+               for (int i=0; i<args.length; i++)
                {
-                       if (args[0].startsWith("--locale="))
+                       if (args[i].startsWith("--locale="))
+                       {
+                               locale = getLanguage(args[i].substring(9));
+                       }
+                       else if (args[i].startsWith("--lang="))
                        {
-                               locale = getLanguage(args[0].substring(9));
+                               locale = getLanguage(args[i].substring(7));
                        }
-                       else if (args[0].startsWith("--lang="))
+                       else if (args[i].startsWith("--langfile="))
                        {
-                               locale = getLanguage(args[0].substring(7));
+                               langFilename = args[i].substring(11);
+                       }
+                       else if (args[i].startsWith("--configfile="))
+                       {
+                               configFilename = args[i].substring(13);
                        }
                        else
                        {
-                               System.out.println("Unknown parameter '" + args[0] +
-                                       "'. Possible parameters:\n   --locale= or --lang=  used for overriding language settings\n");
+                               System.out.println("Unknown parameter '" + args[i] + "'.");
+                               showUsage = true;
                        }
                }
+               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   --langfile=<file>   used to specify an alternative language file\n");
+               }
+               // Initialise configuration if selected
+               try
+               {
+                       if (configFilename != null) {
+                               Config.loadFile(new File(configFilename));
+                       }
+                       else {
+                               Config.loadDefaultFile();
+                       }
+               }
+               catch (ConfigException ce) {
+                       System.err.println("Failed to load config file: " + configFilename);
+               }
+               // Set locale according to Config's language property
+               String langCode = Config.getLanguageCode();
+               if (locale == null && langCode != null) {
+                       Locale configLocale = getLanguage(langCode);
+                       if (configLocale != null) {locale = configLocale;}
+               }
                I18nManager.init(locale);
+               if (langFilename != null) {
+                       I18nManager.addLanguageFile(langFilename);
+               }
+               // Set up the window and go
                launch();
        }
 
@@ -100,7 +141,7 @@ public class GpsPruner
                UpdateMessageBroker.addSubscriber(leftPanel);
                DetailsDisplay rightPanel = new DetailsDisplay(APP.getTrackInfo());
                UpdateMessageBroker.addSubscriber(rightPanel);
-               MapChart mapDisp = new MapChart(APP, APP.getTrackInfo());
+               MapCanvas mapDisp = new MapCanvas(APP, APP.getTrackInfo());
                UpdateMessageBroker.addSubscriber(mapDisp);
                ProfileChart profileDisp = new ProfileChart(APP.getTrackInfo());
                UpdateMessageBroker.addSubscriber(profileDisp);
@@ -131,7 +172,7 @@ public class GpsPruner
 
                // set icon
                try {
-                       frame.setIconImage(new ImageIcon(GpsPruner.class.getResource("gui/images/window_icon.png")).getImage());
+                       frame.setIconImage(IconManager.getImageIcon(IconManager.WINDOW_ICON).getImage());
                }
                catch (Exception e) {} // ignore
 
index 5af9898ed2c56f5ae1ee949ae19bb71dd25f3d2c..87d1df4997c1ab78f5eb0ac4d0ac7dc95552c5c0 100644 (file)
@@ -1,7 +1,11 @@
 package tim.prune;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.Locale;
 import java.util.MissingResourceException;
+import java.util.Properties;
 import java.util.ResourceBundle;
 
 /**
@@ -15,7 +19,10 @@ public abstract class I18nManager
        private static final Locale BACKUP_LOCALE = new Locale("en", "GB");
 
        private static ResourceBundle EnglishTexts = null;
-       private static ResourceBundle ExtraTexts = null;
+       private static ResourceBundle LocalTexts = null;
+
+       /** External properties file for developer testing */
+       private static Properties ExternalPropsFile = null;
 
 
        /**
@@ -30,16 +37,32 @@ public abstract class I18nManager
                // Get bundle for selected locale, if any
                if (inLocale != null)
                {
-                       ExtraTexts = ResourceBundle.getBundle(BUNDLE_NAME, inLocale);
+                       LocalTexts = ResourceBundle.getBundle(BUNDLE_NAME, inLocale);
                }
                else
                {
                        // locale is null so just use the system default
-                       ExtraTexts = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault());
+                       LocalTexts = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault());
                }
        }
 
 
+       /**
+        * Add a language file
+        * @param inFilename filename of file
+        */
+       public static void addLanguageFile(String inFilename)
+       {
+               try
+               {
+                       File file = new File(inFilename);
+                       ExternalPropsFile = new Properties();
+                       ExternalPropsFile.load(new FileInputStream(file));
+               }
+               catch (IOException ioe) {}
+       }
+
+
        /**
         * Lookup the given key and return the associated text
         * @param inKey key to lookup
@@ -48,12 +71,19 @@ public abstract class I18nManager
        public static String getText(String inKey)
        {
                String value = null;
+               // look in external props file if available
+               if (ExternalPropsFile != null)
+               {
+                       value = ExternalPropsFile.getProperty(inKey);
+                       if (value != null && !value.equals(""))
+                               return value;
+               }
                // look in extra texts if available
-               if (ExtraTexts != null)
+               if (LocalTexts != null)
                {
                        try
                        {
-                               value = ExtraTexts.getString(inKey);
+                               value = LocalTexts.getString(inKey);
                                if (value != null && !value.equals(""))
                                        return value;
                        }
index 07b8ff14a3e871fa9914017eaf3510b3421a94a5..aa78a22344c57167cdb4c9c6d22cb8e05209cfe2 100644 (file)
@@ -1,6 +1,8 @@
 package tim.prune.browser;
 
 import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
 
 import tim.prune.I18nManager;
 import tim.prune.data.DataPoint;
@@ -14,7 +16,11 @@ import tim.prune.data.TrackInfo;
 public abstract class UrlGenerator
 {
        /** Number formatter for five dp */
-       public static final DecimalFormat FIVE_DP = new DecimalFormat("0.00000");
+       public static final NumberFormat FIVE_DP = NumberFormat.getNumberInstance(Locale.UK);
+       // Select the UK locale for this formatter so that decimal point is always used (not comma)
+       static {
+               if (FIVE_DP instanceof DecimalFormat) ((DecimalFormat) FIVE_DP).applyPattern("0.00000");
+       }
 
        /** Constant for Google Maps */
        public static final int MAP_SOURCE_GOOGLE = 0;
@@ -66,6 +72,7 @@ public abstract class UrlGenerator
                                url = url + "(" + currPoint.getWaypointName() + ")";
                        }
                }
+               //System.out.println(url);
                return url;
        }
 
diff --git a/tim/prune/configuration.txt b/tim/prune/configuration.txt
new file mode 100644 (file)
index 0000000..56e7caa
--- /dev/null
@@ -0,0 +1,62 @@
+
+== Prune configuration ==
+=========================
+
+Starting with version 6 of Prune, it's possible to create a configuration file which controls a few basic configuration options.  Currently there isn't a nice gui to let you select these options, although this may be added in the future.  Instead, you can just create a text file with the configuration, and save it with any regular text editor.  Then Prune will load these options when it starts up.
+
+== File location ==
+By default, Prune will try to load a file called .pruneconfig from the current directory (note the dot at the start of the filename).  If this file isn't there, it doesn't matter, it'll just be ignored and Prune will use default settings as before.  The file won't be created for you if it doesn't exist.
+If you create this file in your home directory and you also launch Prune from the same directory, Prune will find the file and use whatever configuration is specified inside.
+
+If you want to use another location for this file, or another name, you'll have to tell Prune where it is.  You can do this with an extra commandline parameter, like this:
+   java -jar prune.jar --configfile=h:/gps/pruneconfig.txt
+You can obviously set this in any shortcuts or aliases that you're currently using so you don't have to type it in every time.  If you specify a file like this and Prune can't find it or can't read it, you'll get a warning message in the console.
+
+== File structure ==
+The configuration file is a simple text file, so you can edit it with any editor like Notepad, Kate or gedit.  Each line in the file represents a configuration setting, with the format:
+   key=value
+The key should match one of the recognised keys given below.  Any unrecognised keys will just be ignored.
+The file can contain any number of settings, in any order.  You can have just one in there or all possible settings.
+
+== Possible configuration settings ==
+The following settings can be defined in the file, in any order:
+
+== Working directory ==
+This setting defines the default directory when you start Prune.  If your track files are always in a certain directory, you can set this here so that Prune starts from that directory.  Example:
+   prune.directory=/home/user/gps/
+
+== Language code ==
+This setting defines the language to use for Prune's interface.  By default, Prune will try to use the language set in your system.  If you use the --lang= command line parameter, you can override this to use a different one.  Or you can put a line in your configuration file to define it there instead.  Possible codes are currently EN, DE, DE_ch, ES, FR, IT and PL.  Example:
+   prune.languagecode=ES
+If you use the --lang= command line parameter, this takes priority over what's in the configuration file.
+
+== Metric/imperial units ==
+By default Prune uses metric units, displaying altitudes in metres and distances in kilometres.  This setting allows you to specify whether you want metric or imperial units by default.  Example:
+   prune.metricunits=n
+By using a value of "n" (for "no"), Prune then displays values in imperial units of feet and miles.
+
+== Povray font ==
+When Prune exports to Pov format, it specifies a font to use for the cardinals N, S, E, W.  Usually this is "crystal.ttf" because this comes as standard with povray.  This setting allows you to change that default if you always want to use a specific font installed on your system.  Example:
+   prune.povrayfont=DejaVuSans-Bold.ttf
+The available fonts will depend on what you have installed.  See the povray documentation for more on this.
+
+== GPS device ==
+When Prune invokes Gpsbabel to load data, it needs to specify the GPS device to load from.  The name of this will depend on your GPS receiver and your connection (eg serial/usb).  If you call gpsbabel from the command line, it's the -f parameter.  This setting allows you to specify the default value used by Prune when you select the load from gps function.  Example:
+   prune.gpsdevice=usb:
+A GPS connected to the serial port may appear as /dev/ttyS0.
+
+== GPS format ==
+This setting controls the default value of the format parameter to gpsbabel.  If you call gpsbabel from the command line, it's the -i parameter.  Example:
+   prune.gpsformat=garmin
+
+== Example file ==
+The following can be used as a template to make your own file.
+
+# Prune configuration file
+prune.directory=/home/user/gps/
+prune.languagecode=ES
+prune.metricunits=y
+prune.povrayfont=crystal.ttf
+prune.gpsdevice=usb:
+prune.gpsformat=garmin
+
index f2c1fc8961ecdc412a5a91495129911bd6f5ea4d..ccf5345103826ee2fb05f9e9b9253562835e2ab2 100644 (file)
@@ -536,8 +536,8 @@ public class PhotoCorrelator
        private static PointPair getPointPairForPhoto(Track inTrack, Photo inPhoto, TimeDifference inOffset)
        {
                PointPair pair = new PointPair(inPhoto);
-               // Add offet to photo timestamp
-               Timestamp photoStamp = inPhoto.getTimestamp().subtractOffset(inOffset);
+               // Add/subtract offet to photo timestamp
+               Timestamp photoStamp = inPhoto.getTimestamp().createMinusOffset(inOffset);
                int numPoints = inTrack.getNumPoints();
                for (int i=0; i<numPoints; i++)
                {
index af247cfe0217d07acce4791832a39e0207298b6e..78a98e04f93fba650517cc79201c8d322c379fa6 100644 (file)
@@ -8,13 +8,20 @@ public class Altitude
        private boolean _valid = false;
        private int _value = 0;
        private int _format = -1;
+       private String _stringValue = null;
+
+       /** Altitude formats */
        public static final int FORMAT_NONE   = -1;
        public static final int FORMAT_METRES = 0;
        public static final int FORMAT_FEET = 1;
 
+       /** Constants for conversion */
        private static final double CONVERT_FEET_TO_METRES = 0.3048;
        private static final double CONVERT_METRES_TO_FEET = 3.28084;
 
+       /** Constant for no altitude value */
+       public static final Altitude NONE = new Altitude(null, FORMAT_NONE);
+
 
        /**
         * Constructor using String
@@ -27,6 +34,7 @@ public class Altitude
                {
                        try
                        {
+                               _stringValue = inString;
                                _value = (int) Double.parseDouble(inString.trim());
                                _format = inFormat;
                                _valid = true;
@@ -83,7 +91,7 @@ public class Altitude
         */
        public int getValue(int inFormat)
        {
-               // TODO: Fix rounding errors here converting between units - return double?
+               // Note possible rounding errors here if converting to/from units
                if (inFormat == _format)
                        return _value;
                if (inFormat == FORMAT_METRES)
@@ -93,6 +101,19 @@ public class Altitude
                return _value;
        }
 
+       /**
+        * Get a string version of the value
+        * @param inFormat specified format
+        * @return string value, if possible the original one
+        */
+       public String getStringValue(int inFormat)
+       {
+               if (inFormat == _format && _stringValue != null && !_stringValue.equals("")) {
+                       return _stringValue;
+               }
+               return "" + getValue(inFormat);
+       }
+
 
        /**
         * Interpolate a new Altitude object between the given ones
index 7a572688ed2a0eb0eb63e6a3e6fda5f9037554d9..7e52d839cf1dfae49a649df188eac87d902b7928 100644 (file)
@@ -50,7 +50,14 @@ public abstract class Coordinate
                if (strLen > 1)
                {
                        // Check for cardinal character either at beginning or end
+                       boolean hasCardinal = true;
                        _cardinal = getCardinal(inString.charAt(0), inString.charAt(strLen-1));
+                       if (_cardinal == NO_CARDINAL) {
+                               hasCardinal = false;
+                               // use default from concrete subclass
+                               _cardinal = getDefaultCardinal();
+                       }
+
                        // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s
                        int numFields = 0;
                        boolean inNumeric = false;
@@ -93,7 +100,7 @@ public abstract class Coordinate
                        }
                        // parse fields according to number found
                        _degrees = (int) fields[0];
-                       _originalFormat = FORMAT_DEG;
+                       _originalFormat = hasCardinal?FORMAT_DEG:FORMAT_DEG_WITHOUT_CARDINAL;
                        _fracDenom = 10;
                        if (numFields == 2)
                        {
@@ -147,10 +154,6 @@ public abstract class Coordinate
                if (cardinal == NO_CARDINAL) {
                        cardinal = getCardinal(inLastChar);
                }
-               // use default from concrete subclass
-               if (cardinal == NO_CARDINAL) {
-                       cardinal = getDefaultCardinal();
-               }
                return cardinal;
        }
 
index 38ec19a4549e38dbe0d8aba6709d43546e8faf3a..4cf7c827952a8c10c8c46adb5602e4fcbbf3d956 100644 (file)
@@ -70,8 +70,13 @@ public class DataPoint
                _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_DEG_MIN_SEC);
                _longitude = inLongitude;
                _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_DEG_MIN_SEC);
-               _altitude = inAltitude;
-               if (inAltitude != null) {_fieldValues[2] = "" + inAltitude.getValue(Altitude.FORMAT_METRES);}
+               if (inAltitude == null) {
+                       _altitude = Altitude.NONE;
+               }
+               else {
+                       _altitude = inAltitude;
+                       _fieldValues[2] = "" + inAltitude.getValue(Altitude.FORMAT_METRES); // units are ignored
+               }
                _timestamp = new Timestamp(null);
        }
 
diff --git a/tim/prune/data/LatLonRectangle.java b/tim/prune/data/LatLonRectangle.java
new file mode 100644 (file)
index 0000000..74c9855
--- /dev/null
@@ -0,0 +1,50 @@
+package tim.prune.data;
+
+/**
+ * Class to hold a rectangle of latitude/longitude
+ * with minimum and maximum values for each
+ */
+public class LatLonRectangle
+{
+       private DoubleRange _latRange = null;
+       private DoubleRange _lonRange = null;
+
+
+       /**
+        * Constructor
+        * @param inLatRange latitude range
+        * @param inLonRange longitude range
+        */
+       public LatLonRectangle(DoubleRange inLatRange, DoubleRange inLonRange)
+       {
+               _latRange = inLatRange;
+               _lonRange = inLonRange;
+               // TODO: Expand range by certain percentage
+       }
+
+       /**
+        * @return true if the range is empty
+        */
+       public boolean isEmpty()
+       {
+               return _latRange == null || _lonRange == null
+                       || !_latRange.hasData() || !_lonRange.hasData();
+       }
+
+       /**
+        * Check if a point is within the rectangle
+        * @param inPoint point to check
+        * @return true if point within rectangle
+        */
+       public boolean containsPoint(DataPoint inPoint)
+       {
+               if (inPoint != null && !isEmpty())
+               {
+                       double pointLat = inPoint.getLatitude().getDouble();
+                       double pointLon = inPoint.getLongitude().getDouble();
+                       return (pointLat >= _latRange.getMinimum() && pointLat <= _latRange.getMaximum()
+                               && pointLon >= _lonRange.getMinimum() && pointLon <= _lonRange.getMaximum());
+               }
+               return false;
+       }
+}
index cdbe58ecca33035a7a012d5ee5e3be402e422ccc..95e0cfbd7ded462f51fb750c79df010e18a2ce20 100644 (file)
@@ -17,8 +17,8 @@ public class Selection
        private IntegerRange _altitudeRange = null;
        private int _climb = -1, _descent = -1;
        private int _altitudeFormat = Altitude.FORMAT_NONE;
-       private long _seconds = 0L;
-       private double _angDistance = -1.0; //, _averageSpeed = -1.0;
+       private long _totalSeconds = 0L, _movingSeconds = 0L;
+       private double _angDistance = -1.0, _angMovingDistance = -1.0;
 
 
        /**
@@ -117,8 +117,10 @@ public class Selection
                        _descent = 0;
                        Altitude altitude = null;
                        Timestamp time = null, startTime = null, endTime = null;
+                       Timestamp previousTime = null;
                        DataPoint lastPoint = null, currPoint = null;
-                       _angDistance = 0.0;
+                       _angDistance = 0.0; _angMovingDistance = 0.0;
+                       _totalSeconds = 0L; _movingSeconds = 0L;
                        int altValue = 0;
                        int lastAltValue = 0;
                        boolean foundAlt = false;
@@ -147,26 +149,30 @@ public class Selection
                                time = currPoint.getTimestamp();
                                if (time.isValid())
                                {
-                                       if (startTime == null) startTime = time;
-                                       endTime = time;
+                                       if (startTime == null || startTime.isAfter(time)) startTime = time;
+                                       if (endTime == null || time.isAfter(endTime)) endTime = time;
+                                       // add moving time
+                                       if (!currPoint.getSegmentStart() && previousTime != null && time.isAfter(previousTime)) {
+                                               _movingSeconds += time.getSecondsSince(previousTime);
+                                       }
+                                       previousTime = time;
                                }
                                // Calculate distances, again ignoring waypoints
                                if (!currPoint.isWaypoint())
                                {
                                        if (lastPoint != null)
                                        {
-                                               _angDistance += DataPoint.calculateRadiansBetween(lastPoint, currPoint);
+                                               double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
+                                               _angDistance += radians;
+                                               if (!currPoint.getSegmentStart()) {
+                                                       _angMovingDistance += radians;
+                                               }
                                        }
                                        lastPoint = currPoint;
                                }
                        }
-                       if (endTime != null)
-                       {
-                               _seconds = endTime.getSecondsSince(startTime);
-                       }
-                       else
-                       {
-                               _seconds = 0L;
+                       if (endTime != null) {
+                               _totalSeconds = endTime.getSecondsSince(startTime);
                        }
                }
                _valid = true;
@@ -236,9 +242,17 @@ public class Selection
        public long getNumSeconds()
        {
                if (!_valid) recalculate();
-               return _seconds;
+               return _totalSeconds;
        }
 
+       /**
+        * @return number of seconds spanned by segments within selection
+        */
+       public long getMovingSeconds()
+       {
+               if (!_valid) recalculate();
+               return _movingSeconds;
+       }
 
        /**
         * @param inUnits distance units to use, from class Distance
@@ -249,6 +263,14 @@ public class Selection
                return Distance.convertRadiansToDistance(_angDistance, inUnits);
        }
 
+       /**
+        * @param inUnits distance units to use, from class Distance
+        * @return moving distance of Selection in specified units
+        */
+       public double getMovingDistance(int inUnits)
+       {
+               return Distance.convertRadiansToDistance(_angMovingDistance, inUnits);
+       }
 
        /**
         * Clear selected point and range
index 334bf499e94729ca32d18fdccaaf41c429a5708f..6b7f77b55d361d12de91897f8ee34983d305e9de 100644 (file)
@@ -166,6 +166,14 @@ public class Timestamp
                return _valid;
        }
 
+       /**
+        * @param inOther other Timestamp
+        * @return true if this one is after the other
+        */
+       public boolean isAfter(Timestamp inOther)
+       {
+               return _seconds > inOther._seconds;
+       }
 
        /**
         * Calculate the difference between two Timestamps in seconds
@@ -177,13 +185,22 @@ public class Timestamp
                return _seconds - inOther._seconds;
        }
 
+       /**
+        * Add the given number of seconds offset
+        * @param inOffset number of seconds to add/subtract
+        */
+       public void addOffset(long inOffset)
+       {
+               _seconds += inOffset;
+               _text = null;
+       }
 
        /**
         * Add the given TimeDifference to this Timestamp
         * @param inOffset TimeDifference to add
         * @return new Timestamp object
         */
-       public Timestamp addOffset(TimeDifference inOffset)
+       public Timestamp createPlusOffset(TimeDifference inOffset)
        {
                return new Timestamp((_seconds + inOffset.getTotalSeconds()) * 1000L);
        }
@@ -194,7 +211,7 @@ public class Timestamp
         * @param inOffset TimeDifference to subtract
         * @return new Timestamp object
         */
-       public Timestamp subtractOffset(TimeDifference inOffset)
+       public Timestamp createMinusOffset(TimeDifference inOffset)
        {
                return new Timestamp((_seconds - inOffset.getTotalSeconds()) * 1000L);
        }
index 9c65437b60abc86df5bd803c3f045d946e89a2f5..eeb5b626bb484e59750b605849d653d77ca2a25b 100644 (file)
@@ -5,6 +5,7 @@ import java.util.List;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.edit.FieldEdit;
 import tim.prune.edit.FieldEditList;
+import tim.prune.gui.map.MapUtils;
 
 
 /**
@@ -18,6 +19,8 @@ public class Track
        // Scaled x, y values
        private double[] _xValues = null;
        private double[] _yValues = null;
+       private double[] _xValuesNew = null;
+       private double[] _yValuesNew = null;
        private boolean _scaled = false;
        private int _numPoints = 0;
        private boolean _mixedData = false;
@@ -137,6 +140,7 @@ public class Track
                // (maybe should be separate thread?)
                // (maybe should be in separate class?)
                // (maybe should be based on subtended angles instead of distances?)
+               // Suggestion: Find last track point, don't delete it (or maybe preserve first and last of each segment?)
 
                if (inResolution <= 0) return 0;
                int numCopied = 0;
@@ -363,6 +367,35 @@ public class Track
        }
 
 
+       /**
+        * Add the given time offset to the specified range
+        * @param inStart start of range
+        * @param inEnd end of range
+        * @param inOffset offset to add (-ve to subtract)
+        * @return true on success
+        */
+       public boolean addTimeOffset(int inStart, int inEnd, long inOffset)
+       {
+               // sanity check
+               if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
+                       return false;
+               }
+               boolean foundTimestamp = false;
+               // Loop over all points within range
+               for (int i=inStart; i<=inEnd; i++)
+               {
+                       Timestamp timestamp = _dataPoints[i].getTimestamp();
+                       if (timestamp != null)
+                       {
+                               // This point has a timestamp so add the offset to it
+                               foundTimestamp = true;
+                               timestamp.addOffset(inOffset);
+                       }
+               }
+               return foundTimestamp;
+       }
+
+
        /**
         * Merge the track segments within the given range
         * @param inStart start index
@@ -501,6 +534,61 @@ public class Track
        }
 
 
+       /**
+        * Cut and move the specified section
+        * @param inSectionStart start index of section
+        * @param inSectionEnd end index of section
+        * @param inMoveTo index of move to point
+        * @return true if move successful
+        */
+       public boolean cutAndMoveSection(int inSectionStart, int inSectionEnd, int inMoveTo)
+       {
+               // Check that indices make sense
+               if (inSectionStart > 0 && inSectionEnd > inSectionStart && inMoveTo > 0
+                       && (inMoveTo < inSectionStart || inMoveTo > (inSectionEnd+1)))
+               {
+                       // do the cut and move
+                       DataPoint[] newPointArray = new DataPoint[_numPoints];
+                       // System.out.println("Cut/move section (" + inSectionStart + " - " + inSectionEnd + ") to before point " + inMoveTo);
+                       // Is it a forward copy or a backward copy?
+                       if (inSectionStart > inMoveTo)
+                       {
+                               int sectionLength = inSectionEnd - inSectionStart + 1;
+                               // move section to earlier point
+                               if (inMoveTo > 0)
+                                       System.arraycopy(_dataPoints, 0, newPointArray, 0, inMoveTo); // unchanged points before
+                               System.arraycopy(_dataPoints, inSectionStart, newPointArray, inMoveTo, sectionLength); // moved bit
+                               // after insertion point, before moved bit
+                               if (inSectionStart > (inMoveTo + 1))
+                                       System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo + sectionLength, inSectionStart - inMoveTo);
+                               // after moved bit
+                               if (inSectionEnd < (_numPoints - 1))
+                                       System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionEnd+1, _numPoints - inSectionEnd - 1);
+                       }
+                       else
+                       {
+                               // Move section to later point
+                               if (inSectionStart > 0)
+                                       System.arraycopy(_dataPoints, 0, newPointArray, 0, inSectionStart); // unchanged points before
+                               // from end of section to move to point
+                               if (inMoveTo > (inSectionEnd + 1))
+                                       System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionStart, inMoveTo - inSectionEnd - 1);
+                               // moved bit
+                               System.arraycopy(_dataPoints, inSectionStart, newPointArray, inSectionStart + inMoveTo - inSectionEnd - 1,
+                                       inSectionEnd - inSectionStart + 1);
+                               // unchanged bit after
+                               if (inSectionEnd < (_numPoints - 1))
+                                       System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo, _numPoints - inMoveTo);
+                       }
+                       // Copy array references
+                       _dataPoints = newPointArray;
+                       _scaled = false;
+                       return true;
+               }
+               return false;
+       }
+
+
        /**
         * Interpolate extra points between two selected ones
         * @param inStartIndex start index of interpolation
@@ -631,6 +719,26 @@ public class Track
                return _yValues[inPointNum];
        }
 
+       /**
+        * @param inPointNum point index, starting at 0
+        * @return scaled x value of specified point
+        */
+       public double getXNew(int inPointNum)
+       {
+               if (!_scaled) scalePoints();
+               return _xValuesNew[inPointNum];
+       }
+
+       /**
+        * @param inPointNum point index, starting at 0
+        * @return scaled y value of specified point
+        */
+       public double getYNew(int inPointNum)
+       {
+               if (!_scaled) scalePoints();
+               return _yValuesNew[inPointNum];
+       }
+
        /**
         * @return the master field list
         */
@@ -765,6 +873,8 @@ public class Track
                // Loop over points and calculate scales
                _xValues = new double[getNumPoints()];
                _yValues = new double[getNumPoints()];
+               _xValuesNew = new double[getNumPoints()];
+               _yValuesNew = new double[getNumPoints()];
                _xRange = new DoubleRange();
                _yRange = new DoubleRange();
                for (p=0; p < getNumPoints(); p++)
@@ -774,8 +884,10 @@ public class Track
                        {
                                _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor;
                                _xRange.addValue(_xValues[p]);
+                               _xValuesNew[p] = MapUtils.getXFromLongitude(point.getLongitude().getDouble());
                                _yValues[p] = (point.getLatitude().getDouble() - latMedian);
                                _yRange.addValue(_yValues[p]);
+                               _yValuesNew[p] = MapUtils.getYFromLatitude(point.getLatitude().getDouble());
                        }
                }
                _scaled = true;
@@ -817,6 +929,40 @@ public class Track
        }
 
 
+       /**
+        * Find the nearest point to the specified x and y coordinates
+        * or -1 if no point is within the specified max distance
+        * @param inX x coordinate
+        * @param inY y coordinate
+        * @param inMaxDist maximum distance from selected coordinates
+        * @param inJustTrackPoints true if waypoints should be ignored
+        * @return index of nearest point or -1 if not found
+        */
+       public int getNearestPointIndexNew(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
+       {
+               int nearestPoint = 0;
+               double nearestDist = -1.0;
+               double currDist;
+               for (int i=0; i < getNumPoints(); i++)
+               {
+                       if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
+                       {
+                               currDist = Math.abs(_xValuesNew[i] - inX) + Math.abs(_yValuesNew[i] - inY);
+                               if (currDist < nearestDist || nearestDist < 0.0)
+                               {
+                                       nearestPoint = i;
+                                       nearestDist = currDist;
+                               }
+                       }
+               }
+               // Check whether it's within required distance
+               if (nearestDist > inMaxDist && inMaxDist > 0.0)
+               {
+                       return -1;
+               }
+               return nearestPoint;
+       }
+
        /**
         * Get the next track point starting from the given index
         * @param inStartIndex index to start looking from
@@ -824,7 +970,18 @@ public class Track
         */
        public DataPoint getNextTrackPoint(int inStartIndex)
        {
-               return getNextTrackPoint(inStartIndex, 1);
+               return getNextTrackPoint(inStartIndex, _numPoints, true);
+       }
+
+       /**
+        * Get the next track point in the given range
+        * @param inStartIndex index to start looking from
+        * @param inEndIndex index to stop looking
+        * @return next track point, or null if end of data reached
+        */
+       public DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex)
+       {
+               return getNextTrackPoint(inStartIndex, inEndIndex, true);
        }
 
        /**
@@ -834,19 +991,21 @@ public class Track
         */
        public DataPoint getPreviousTrackPoint(int inStartIndex)
        {
-               return getNextTrackPoint(inStartIndex, -1);
+               return getNextTrackPoint(inStartIndex, _numPoints, false);
        }
 
        /**
         * Get the next track point starting from the given index
         * @param inStartIndex index to start looking from
-        * @param inIncrement increment to add to point index, +1 for next, -1 for previous
+        * @param inEndIndex index to stop looking (inclusive)
+        * @param inCountUp true for next, false for previous
         * @return next track point, or null if end of data reached
         */
-       private DataPoint getNextTrackPoint(int inStartIndex, int inIncrement)
+       private DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex, boolean inCountUp)
        {
                // Loop forever over points
-               for (int i=inStartIndex; ; i+=inIncrement)
+               int increment = inCountUp?1:-1;
+               for (int i=inStartIndex; i<=inEndIndex; i+=increment)
                {
                        DataPoint point = getPoint(i);
                        // Exit if end of data reached - there wasn't a track point
@@ -856,6 +1015,7 @@ public class Track
                                return point;
                        }
                }
+               return null;
        }
 
        /**
index bbbdefb2336d210a35d2336824d271a058557a76..4f3ce27e8f85b79c6b3582d5af20f27593f7fabb 100644 (file)
@@ -282,15 +282,15 @@ public class TrackInfo
 
        /**
         * Interpolate extra points between two selected ones
-        * @param inStartIndex start index of interpolation
         * @param inNumPoints num points to insert
         * @return true if successful
         */
        public boolean interpolate(int inNumPoints)
        {
                boolean success = _track.interpolate(_selection.getStart(), inNumPoints);
-               if (success)
+               if (success) {
                        _selection.selectRangeEnd(_selection.getEnd() + inNumPoints);
+               }
                return success;
        }
 
index 02c37f7624d21d3726c109f32668beacad18868c..0d1ef5b7f3bd5cc4fb5c75f8210baf59c2922284 100644 (file)
@@ -77,6 +77,8 @@ public class AboutScreen extends JDialog
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext1")).append("</p>");
                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ñol, français, italiano, polski, schwiizerdüütsch").append("</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));
@@ -161,7 +163,7 @@ public class AboutScreen extends JDialog
                        new JLabel(I18nManager.getText("dialog.about.credits.translators") + " : "),
                        0, 3);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
-                       new JLabel("Ramon, Miguel, Inés, Piotr, Petrovsk"),
+                       new JLabel("Ramon, Miguel, Inés, Piotr, Petrovsk, Josatoc"),
                        1, 3);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "),
diff --git a/tim/prune/gui/CheckVersionScreen.java b/tim/prune/gui/CheckVersionScreen.java
new file mode 100644 (file)
index 0000000..e57b724
--- /dev/null
@@ -0,0 +1,97 @@
+package tim.prune.gui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Properties;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import tim.prune.GpsPruner;
+import tim.prune.I18nManager;
+import tim.prune.browser.BrowserLauncher;
+
+/**
+ * Class to check the version of Prune
+ * and show an appropriate dialog
+ */
+public abstract class CheckVersionScreen
+{
+       /**
+        * Show the check version dialog
+        * @param inParent parent frame
+        */
+       public static void show(JFrame inParent)
+       {
+               final String filePathStart = "http://activityworkshop.net/software/prune/prune_versioncheck_";
+               final String filePathEnd = ".txt";
+               String latestVer = null;
+               String nextVersion = null;
+               String releaseDate = null;
+               Properties props = new Properties();
+               try
+               {
+                       // Load properties from the url on the server
+                       InputStream inStream = new URL(filePathStart + GpsPruner.VERSION_NUMBER + filePathEnd).openStream();
+                       props.load(inStream);
+
+                       // Extract the three fields we want, ignore others
+                       latestVer = props.getProperty("prune.latestversion");
+                       nextVersion = props.getProperty("prune.nextversion");
+                       releaseDate = props.getProperty("prune.releasedate");
+               }
+               catch (IOException ioe) {
+                       System.err.println(ioe.getClass().getName() + " - " + ioe.getMessage());
+               }
+
+               if (latestVer == null) {
+                       // Couldn't get version number, show error message
+                       JOptionPane.showMessageDialog(inParent, I18nManager.getText("dialog.checkversion.error"),
+                               I18nManager.getText("dialog.checkversion.title"), JOptionPane.ERROR_MESSAGE);
+               }
+               else if (latestVer.equals(GpsPruner.VERSION_NUMBER))
+               {
+                       // Version on the server is the same as this one
+                       String displayMessage = I18nManager.getText("dialog.checkversion.uptodate");
+                       if (nextVersion != null && !nextVersion.equals(""))
+                       {
+                               displayMessage += "\n\n" + nextVersion;
+                       }
+                       // Show information message that the current version is already running
+                       JOptionPane.showMessageDialog(inParent, displayMessage,
+                               I18nManager.getText("dialog.checkversion.title"), JOptionPane.INFORMATION_MESSAGE);
+               }
+               else
+               {
+                       // A new version is available!
+                       String displayMessage = I18nManager.getText("dialog.checkversion.newversion1") + " " + latestVer
+                               + " " + I18nManager.getText("dialog.checkversion.newversion2");
+                       try
+                       {
+                               if (releaseDate != null && !releaseDate.equals("")) {
+                                       displayMessage += "\n\n" + I18nManager.getText("dialog.checkversion.releasedate1") + " "
+                                               + DateFormat.getDateInstance(DateFormat.LONG).format(new SimpleDateFormat("y-M-d").parse(releaseDate))
+                                               + " " + I18nManager.getText("dialog.checkversion.releasedate2");
+                               }
+                       }
+                       catch (ParseException pe) {
+                               System.err.println("Oops, couldn't parse date: '" + releaseDate + "'");
+                       }
+                       displayMessage += "\n\n" + I18nManager.getText("dialog.checkversion.download");
+
+                       // Show information message to download the new version
+                       Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")};
+                       if (JOptionPane.showOptionDialog(inParent, displayMessage,
+                                       I18nManager.getText("dialog.checkversion.title"), JOptionPane.YES_NO_OPTION,
+                                       JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1])
+                               == JOptionPane.YES_OPTION)
+                       {
+                               // User selected to launch home page
+                               new BrowserLauncher().launchBrowser("http://activityworkshop.net/software/prune/download.html");
+                       }
+               }
+       }
+}
index 7773062cccc25f006e4620f8c9b82edbbcc7ec99..e153a6cead5feb9dca2a776559e9f163130b3d16 100644 (file)
@@ -15,6 +15,8 @@ import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.border.EtchedBorder;
+
+import tim.prune.Config;
 import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
 import tim.prune.data.Altitude;
@@ -41,13 +43,15 @@ public class DetailsDisplay extends GenericDisplay
 
        // Range details
        private JLabel _rangeLabel = null;
-       private JLabel _distanceLabel = null, _durationLabel = null;
+       private JLabel _distanceLabel = null, _movingDistanceLabel = null;
+       private JLabel _durationLabel = null;
        private JLabel _altRangeLabel = null, _updownLabel = null;
-       private JLabel _aveSpeedLabel = null;
+       private JLabel _aveSpeedLabel = null, _aveMovingSpeedLabel = null;
 
        // Photo details
        private JLabel _photoLabel = null;
        private PhotoThumbnail _photoThumbnail = null;
+       private JLabel _photoTimestampLabel = null;
        private JLabel _photoConnectedLabel = null;
 
        // Units
@@ -57,15 +61,16 @@ public class DetailsDisplay extends GenericDisplay
        private NumberFormat _distanceFormatter = NumberFormat.getInstance();
 
        // Cached labels
-       private static final String LABEL_POINT_SELECTED1 = I18nManager.getText("details.index.selected") + ": ";
+       private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
        private static final String LABEL_POINT_LATITUDE = I18nManager.getText("fieldname.latitude") + ": ";
        private static final String LABEL_POINT_LONGITUDE = I18nManager.getText("fieldname.longitude") + ": ";
        private static final String LABEL_POINT_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": ";
        private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
        private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
-       private static final String LABEL_RANGE_SELECTED1 = I18nManager.getText("details.range.selected") + ": ";
+       private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": ";
        private static final String LABEL_RANGE_DURATION = I18nManager.getText("fieldname.duration") + ": ";
        private static final String LABEL_RANGE_DISTANCE = I18nManager.getText("fieldname.distance") + ": ";
+       private static final String LABEL_RANGE_MOVINGDISTANCE = I18nManager.getText("fieldname.movingdistance") + ": ";
        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") + ": ";
@@ -126,10 +131,14 @@ public class DetailsDisplay extends GenericDisplay
                rangeDetailsPanel.add(_rangeLabel);
                _distanceLabel = new JLabel("");
                rangeDetailsPanel.add(_distanceLabel);
+               _movingDistanceLabel = new JLabel("");
+               rangeDetailsPanel.add(_movingDistanceLabel);
                _durationLabel = new JLabel("");
                rangeDetailsPanel.add(_durationLabel);
                _aveSpeedLabel = new JLabel("");
                rangeDetailsPanel.add(_aveSpeedLabel);
+               _aveMovingSpeedLabel = new JLabel("");
+               rangeDetailsPanel.add(_aveMovingSpeedLabel);
                _altRangeLabel = new JLabel("");
                rangeDetailsPanel.add(_altRangeLabel);
                _updownLabel = new JLabel("");
@@ -147,6 +156,8 @@ public class DetailsDisplay extends GenericDisplay
                photoDetailsPanel.add(photoDetailsLabel);
                _photoLabel = new JLabel(I18nManager.getText("details.nophoto"));
                photoDetailsPanel.add(_photoLabel);
+               _photoTimestampLabel = new JLabel("");
+               photoDetailsPanel.add(_photoTimestampLabel);
                _photoConnectedLabel = new JLabel("");
                photoDetailsPanel.add(_photoConnectedLabel);
                _photoThumbnail = new PhotoThumbnail();
@@ -186,6 +197,7 @@ public class DetailsDisplay extends GenericDisplay
                lowerPanel.add(unitsLabel);
                String[] distUnits = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")};
                _distUnitsDropdown = new JComboBox(distUnits);
+               if (!Config.getUseMetricUnits()) {_distUnitsDropdown.setSelectedIndex(1);}
                _distUnitsDropdown.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
@@ -223,24 +235,24 @@ public class DetailsDisplay extends GenericDisplay
                }
                else
                {
-                       _indexLabel.setText(LABEL_POINT_SELECTED1
+                       _indexLabel.setText(LABEL_POINT_SELECTED
                                + (currentPointIndex+1) + " " + I18nManager.getText("details.index.of")
                                + " " + _track.getNumPoints());
                        _latLabel.setText(makeCoordinateLabel(LABEL_POINT_LATITUDE, currentPoint.getLatitude(), _coordFormatDropdown.getSelectedIndex()));
                        _longLabel.setText(makeCoordinateLabel(LABEL_POINT_LONGITUDE, currentPoint.getLongitude(), _coordFormatDropdown.getSelectedIndex()));
-                       _altLabel.setText(LABEL_POINT_ALTITUDE
-                               + (currentPoint.hasAltitude()?
-                                       (currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat())):
-                               ""));
+                       _altLabel.setText(currentPoint.hasAltitude()?
+                               (LABEL_POINT_ALTITUDE + currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat()))
+                               :"");
                        if (currentPoint.getTimestamp().isValid())
                        {
                                if (currentPointIndex > 0 && currentPointIndex < (_trackInfo.getTrack().getNumPoints()-1)) {
                                        DataPoint prevPoint = _trackInfo.getTrack().getPoint(currentPointIndex - 1);
                                        DataPoint nextPoint = _trackInfo.getTrack().getPoint(currentPointIndex + 1);
-                                       if (prevPoint.getTimestamp().isValid() && nextPoint.getTimestamp().isValid()) {
+                                       if (prevPoint.getTimestamp().isValid() && nextPoint.getTimestamp().isValid())
+                                       {
                                                // use total distance and total time between neighbouring points
                                                long diff = nextPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp());
-                                               if (diff < 100) {
+                                               if (diff < 1000) {
                                                        double rads = DataPoint.calculateRadiansBetween(prevPoint, currentPoint) +
                                                                DataPoint.calculateRadiansBetween(currentPoint, nextPoint);
                                                        double dist = Distance.convertRadiansToDistance(rads, distUnits);
@@ -267,24 +279,32 @@ public class DetailsDisplay extends GenericDisplay
                {
                        _rangeLabel.setText(I18nManager.getText("details.norangeselection"));
                        _distanceLabel.setText("");
+                       _movingDistanceLabel.setText("");
                        _durationLabel.setText("");
                        _altRangeLabel.setText("");
                        _updownLabel.setText("");
+                       _aveSpeedLabel.setText("");
+                       _aveMovingSpeedLabel.setText("");
                }
                else
                {
-                       _rangeLabel.setText(LABEL_RANGE_SELECTED1
+                       _rangeLabel.setText(LABEL_RANGE_SELECTED
                                + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to")
                                + " " + (selection.getEnd()+1));
                        _distanceLabel.setText(LABEL_RANGE_DISTANCE + roundedNumber(selection.getDistance(distUnits)) + " " + distUnitsStr);
-                       if (selection.getNumSeconds() > 0) {
+                       _movingDistanceLabel.setText(LABEL_RANGE_MOVINGDISTANCE + roundedNumber(selection.getMovingDistance(distUnits)) + " " + distUnitsStr);
+                       if (selection.getNumSeconds() > 0)
+                       {
                                _durationLabel.setText(LABEL_RANGE_DURATION + buildDurationString(selection.getNumSeconds()));
                                _aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": "
                                        + roundedNumber(selection.getDistance(distUnits)/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
+                               _aveMovingSpeedLabel.setText(I18nManager.getText("details.range.avemovingspeed") + ": "
+                                       + roundedNumber(selection.getMovingDistance(distUnits)/selection.getMovingSeconds()*3600.0) + " " + speedUnitsStr);
                        }
                        else {
                                _durationLabel.setText("");
                                _aveSpeedLabel.setText("");
+                               _aveMovingSpeedLabel.setText("");
                        }
                        String altUnitsLabel = getAltitudeUnitsLabel(selection.getAltitudeFormat());
                        IntegerRange altRange = selection.getAltitudeRange();
@@ -309,6 +329,7 @@ public class DetailsDisplay extends GenericDisplay
                {
                        // no photo, hide details
                        _photoLabel.setText(I18nManager.getText("details.nophoto"));
+                       _photoTimestampLabel.setText("");
                        _photoConnectedLabel.setText("");
                        _photoThumbnail.setVisible(false);
                }
@@ -316,6 +337,7 @@ public class DetailsDisplay extends GenericDisplay
                {
                        if (currentPhoto == null) {currentPhoto = currentPoint.getPhoto();}
                        _photoLabel.setText(I18nManager.getText("details.photofile") + ": " + currentPhoto.getFile().getName());
+                       _photoLabel.setText(LABEL_POINT_TIMESTAMP + currentPhoto.getTimestamp().getText());
                        _photoConnectedLabel.setText(I18nManager.getText("details.photo.connected") + ": "
                                + (currentPhoto.getCurrentStatus() == PhotoStatus.NOT_CONNECTED ?
                                        I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes")));
@@ -380,7 +402,7 @@ public class DetailsDisplay extends GenericDisplay
                if (inNumSecs < 86400L) return "" + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours")
                        + " " + ((inNumSecs / 60) % 60) + I18nManager.getText("display.range.time.mins");
                if (inNumSecs < 432000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days")
-                       + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours");
+                       + " " + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours");
                if (inNumSecs < 8640000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
                return "big";
        }
index 916c738d817c34e90defcb0f7d523e111f197ec9..71455eb7ac386cee9bdaa2ab6899bb93ba83b002 100644 (file)
@@ -19,7 +19,7 @@ public abstract class GenericChart extends GenericDisplay implements MouseListen
        protected static final int BORDER_WIDTH = 8;
 
        // Colours
-       private static final Color COLOR_BORDER_BG   = Color.GRAY;
+       private static final Color COLOR_BORDER_BG   = Color.WHITE;
        private static final Color COLOR_CHART_BG    = Color.WHITE;
        private static final Color COLOR_CHART_LINE  = Color.BLACK;
        private static final Color COLOR_NODATA_TEXT = Color.GRAY;
diff --git a/tim/prune/gui/IconManager.java b/tim/prune/gui/IconManager.java
new file mode 100644 (file)
index 0000000..8930d68
--- /dev/null
@@ -0,0 +1,65 @@
+package tim.prune.gui;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Class to manage the loading of icons
+ * for toolbars and map buttons
+ */
+public abstract class IconManager
+{
+
+       /** Icon for window */
+       public static final String WINDOW_ICON = "window_icon.png";
+
+       /** Icon for map button on main map display */
+       public static final String MAP_BUTTON = "map_icon.gif";
+       /** Icon for map button on main map display when selected */
+       public static final String MAP_BUTTON_ON = "map_icon_on.gif";
+       /** Icon for autopan button on main map display */
+       public static final String AUTOPAN_BUTTON = "autopan.gif";
+       /** Icon for autopan button on main map display when selected */
+       public static final String AUTOPAN_BUTTON_ON = "autopan_on.gif";
+       /** Icon for points connected icon on main map display */
+       public static final String POINTS_CONNECTED_BUTTON = "points_connected.gif";
+       /** Icon for points disconnected icon on main map display */
+       public static final String POINTS_DISCONNECTED_BUTTON = "points_disconnected.gif";
+       /** Icon for zoom in button on main map display */
+       public static final String ZOOM_IN_BUTTON = "zoom_in.gif";
+       /** Icon for zoom out button on main map display */
+       public static final String ZOOM_OUT_BUTTON = "zoom_out.gif";
+
+       /** Icon for open file */
+       public static final String OPEN_FILE = "add_textfile_icon.png";
+       /** Icon for add photo */
+       public static final String ADD_PHOTO = "add_photo_icon.png";
+       /** Icon for save */
+       public static final String SAVE_FILE = "save_icon.gif";
+       /** Icon for undo */
+       public static final String UNDO = "undo_icon.gif";
+       /** Icon for edit point */
+       public static final String EDIT_POINT = "edit_point_icon.gif";
+       /** Icon for delete point */
+       public static final String DELETE_POINT = "delete_point_icon.gif";
+       /** Icon for delete range */
+       public static final String DELETE_RANGE = "delete_range_icon.gif";
+       /** Icon for set range start */
+       public static final String SET_RANGE_START = "set_start_icon.png";
+       /** Icon for set range end */
+       public static final String SET_RANGE_END = "set_end_icon.png";
+       /** Icon for connect point to photo */
+       public static final String CONNECT_PHOTO = "link.gif";
+       /** Icon for cut range and move */
+       public static final String CUT_AND_MOVE = "cut_and_move.gif";
+
+
+       /**
+        * Get the specified image
+        * @param inFilename filename of image (using constants)
+        * @return ImageIcon object containing image
+        */
+       public static ImageIcon getImageIcon(String inFilename)
+       {
+               return new ImageIcon(IconManager.class.getResource("images/" + inFilename));
+       }
+}
diff --git a/tim/prune/gui/MapChart.java b/tim/prune/gui/MapChart.java
deleted file mode 100644 (file)
index 37f4366..0000000
+++ /dev/null
@@ -1,636 +0,0 @@
-package tim.prune.gui;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
-import java.awt.image.BufferedImage;
-
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-
-import tim.prune.App;
-import tim.prune.DataSubscriber;
-import tim.prune.I18nManager;
-import tim.prune.data.DataPoint;
-import tim.prune.data.TrackInfo;
-
-
-/**
- * Display component for the main map
- */
-public class MapChart extends GenericChart implements MouseWheelListener, KeyListener, MouseMotionListener
-{
-       // Constants
-       private static final int POINT_RADIUS = 4;
-       private static final int CLICK_SENSITIVITY = 10;
-       private static final double ZOOM_SCALE_FACTOR = 1.4;
-       private static final int PAN_DISTANCE = 10;
-       private static final int LIMIT_WAYPOINT_NAMES = 40;
-
-       // Colours
-       private static final Color COLOR_BG         = Color.WHITE;
-       private static final Color COLOR_POINT      = Color.BLUE;
-       private static final Color COLOR_CURR_RANGE = Color.GREEN;
-       private static final Color COLOR_CROSSHAIRS = Color.RED;
-       private static final Color COLOR_WAYPT_NAME = Color.BLACK;
-
-       // Instance variables
-       private App _app = null;
-       private BufferedImage _image = null;
-       private JPopupMenu _popup = null;
-       private JCheckBoxMenuItem _autoPanMenuItem = null;
-       private JCheckBoxMenuItem _connectPointsMenuItem = null;
-       private int _numPoints = -1;
-       private double _scale;
-       private double _offsetX, _offsetY, _zoomScale;
-       private int _lastSelectedPoint = -1;
-       private int _dragStartX = -1, _dragStartY = -1;
-       private int _zoomDragFromX = -1, _zoomDragFromY = -1;
-       private int _zoomDragToX = -1, _zoomDragToY = -1;
-       private boolean _zoomDragging = false;
-
-
-       /**
-        * Constructor
-        * @param inApp App object for callbacks
-        * @param inTrackInfo track info object
-        */
-       public MapChart(App inApp, TrackInfo inTrackInfo)
-       {
-               super(inTrackInfo);
-               _app = inApp;
-               makePopup();
-               addMouseListener(this);
-               addMouseWheelListener(this);
-               addMouseMotionListener(this);
-               setFocusable(true);
-               addKeyListener(this);
-               MINIMUM_SIZE = new Dimension(200, 250);
-               _zoomScale = 1.0;
-       }
-
-
-       /**
-        * Override track updating to refresh image
-        */
-       public void dataUpdated(byte inUpdateType)
-       {
-               // Check if number of points has changed or data has been edited
-               if (_track.getNumPoints() != _numPoints || (inUpdateType & DATA_EDITED) > 0)
-               {
-                       _image = null;
-                       _lastSelectedPoint = -1;
-                       _numPoints = _track.getNumPoints();
-               }
-               super.dataUpdated(inUpdateType);
-       }
-
-
-       /**
-        * Override paint method to draw map
-        * @param inG graphics object
-        */
-       public void paint(Graphics inG)
-       {
-               if (_track == null)
-               {
-                       super.paint(inG);
-                       return;
-               }
-
-               int width = getWidth();
-               int height = getHeight();
-               int x, y;
-
-               // Find x and y ranges, and scale to fit
-               double scaleX = (_track.getXRange().getMaximum() - _track.getXRange().getMinimum())
-                 / (width - 2 * (BORDER_WIDTH + POINT_RADIUS));
-               double scaleY = (_track.getYRange().getMaximum() - _track.getYRange().getMinimum())
-                 / (height - 2 * (BORDER_WIDTH + POINT_RADIUS));
-               _scale = scaleX;
-               if (scaleY > _scale) _scale = scaleY;
-
-               // Autopan if necessary
-               int selectedPoint = _trackInfo.getSelection().getCurrentPointIndex();
-               if (_autoPanMenuItem.isSelected() && selectedPoint >= 0 && selectedPoint != _lastSelectedPoint)
-               {
-                       // Autopan is enabled and a point is selected - work out x and y to see if it's within range
-                       x = width/2 + (int) ((_track.getX(selectedPoint) - _offsetX) / _scale * _zoomScale);
-                       y = height/2 - (int) ((_track.getY(selectedPoint) - _offsetY) / _scale * _zoomScale);
-                       if (x <= BORDER_WIDTH)
-                       {
-                               // autopan left
-                               _offsetX -= (width / 4 - x) * _scale / _zoomScale;
-                               _image = null;
-                       }
-                       else if (x >= (width - BORDER_WIDTH))
-                       {
-                               // autopan right
-                               _offsetX += (x - width * 3/4) * _scale / _zoomScale;
-                               _image = null;
-                       }
-                       if (y <= BORDER_WIDTH)
-                       {
-                               // autopan up
-                               _offsetY += (height / 4 - y) * _scale / _zoomScale;
-                               _image = null;
-                       }
-                       else if (y >= (height - BORDER_WIDTH))
-                       {
-                               // autopan down
-                               _offsetY -= (y - height * 3/4) * _scale / _zoomScale;
-                               _image = null;
-                       }
-               }
-               _lastSelectedPoint = selectedPoint;
-
-               // Create background if necessary
-               if (_image == null || width != _image.getWidth() || height != _image.getHeight())
-               {
-                       createBackgroundImage();
-               }
-               // return if image has been set to null by other thread
-               if (_image == null) {return;}
-
-               // draw buffered image onto g
-               inG.drawImage(_image, 0, 0, width, height, COLOR_BG, null);
-
-               // draw selected range, if any
-               if (_trackInfo.getSelection().hasRangeSelected() && !_zoomDragging)
-               {
-                       int rangeStart = _trackInfo.getSelection().getStart();
-                       int rangeEnd = _trackInfo.getSelection().getEnd();
-                       inG.setColor(COLOR_CURR_RANGE);
-                       for (int i=rangeStart; i<=rangeEnd; i++)
-                       {
-                               x = width/2 + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale);
-                               y = height/2 - (int) ((_track.getY(i) - _offsetY) / _scale * _zoomScale);
-                               if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH)
-                                       && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH)
-                               {
-                                       inG.drawRect(x - 2, y - 2, 4, 4);
-                               }
-                       }
-               }
-
-               // Highlight selected point
-               if (selectedPoint >= 0 && !_zoomDragging)
-               {
-                       inG.setColor(COLOR_CROSSHAIRS);
-                       x = width/2 + (int) ((_track.getX(selectedPoint) - _offsetX) / _scale * _zoomScale);
-                       y = height/2 - (int) ((_track.getY(selectedPoint) - _offsetY) / _scale * _zoomScale);
-                       if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH)
-                               && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH)
-                       {
-                               // Draw cross-hairs for current point
-                               inG.drawLine(x, BORDER_WIDTH, x, height - BORDER_WIDTH);
-                               inG.drawLine(BORDER_WIDTH, y, width - BORDER_WIDTH, y);
-
-                               // Show selected point afterwards to make sure it's on top
-                               inG.drawOval(x - 2, y - 2, 4, 4);
-                               inG.drawOval(x - 3, y - 3, 6, 6);
-                       }
-               }
-
-               // Draw rectangle for dragging zoom area
-               if (_zoomDragging)
-               {
-                       inG.setColor(COLOR_CROSSHAIRS);
-                       inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragFromX, _zoomDragToY);
-                       inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragToX, _zoomDragFromY);
-                       inG.drawLine(_zoomDragToX, _zoomDragFromY, _zoomDragToX, _zoomDragToY);
-                       inG.drawLine(_zoomDragFromX, _zoomDragToY, _zoomDragToX, _zoomDragToY);
-               }
-
-               // Attempt to grab keyboard focus if possible
-               //requestFocus();  (causes problems here)
-       }
-
-
-       /**
-        * Plot the points onto an offscreen image
-        * which doesn't have to be redrawn when the selection changes
-        */
-       private void createBackgroundImage()
-       {
-               int width = getWidth();
-               int height = getHeight();
-               int x, y;
-               int lastX = 0, lastY = 0;
-               // Initialise image
-               if (_image == null || _image.getWidth() != width || _image.getHeight() != height) {
-                       _image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-               }
-               Graphics bufferedG = _image.getGraphics();
-               super.paint(bufferedG);
-
-               // Loop and show all points
-               int numPoints = _track.getNumPoints();
-               bufferedG.setColor(COLOR_POINT);
-               int halfWidth = width/2;
-               int halfHeight = height/2;
-               boolean currPointTrackpoint = false, lastPointTrackpoint = false;
-               for (int i=0; i<numPoints; i++)
-               {
-                       x = halfWidth + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale);
-                       y = halfHeight - (int) ((_track.getY(i) - _offsetY) / _scale * _zoomScale);
-                       if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH)
-                               && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH)
-                       {
-                               // draw block for point (a bit faster than circles)
-                               bufferedG.drawRect(x - 2, y - 2, 3, 3);
-
-                               // See whether to connect the point with previous one or not
-                               DataPoint point = _track.getPoint(i);
-                               currPointTrackpoint = !point.isWaypoint() && point.getPhoto() == null;
-                               if (_connectPointsMenuItem.isSelected() && currPointTrackpoint && lastPointTrackpoint
-                                       && !point.getSegmentStart())
-                               {
-                                       bufferedG.drawLine(lastX, lastY, x, y);
-                               }
-                               lastPointTrackpoint = currPointTrackpoint;
-                       }
-                       else {
-                               lastPointTrackpoint = false;
-                       }
-                       lastX = x; lastY = y;
-               }
-
-               // Loop again and show waypoints with names
-               bufferedG.setColor(COLOR_WAYPT_NAME);
-               FontMetrics fm = bufferedG.getFontMetrics();
-               int nameHeight = fm.getHeight();
-               int numWaypointNamesShown = 0;
-               for (int i=0; i<numPoints; i++)
-               {
-                       DataPoint point = _track.getPoint(i);
-                       String waypointName = point.getWaypointName();
-                       if (waypointName != null && !waypointName.equals(""))
-                       {
-                               // escape if nothing more to do
-                               if (numWaypointNamesShown >= LIMIT_WAYPOINT_NAMES || _image == null) {break;}
-                               // calculate coordinates of point
-                               x = halfWidth + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale);
-                               y = halfHeight - (int) ((_track.getY(i) - _offsetY) / _scale * _zoomScale);
-                               if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH)
-                                               && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH)
-                               {
-                                       bufferedG.fillOval(x - 3, y - 3, 6, 6);
-                                       // Figure out where to draw name so it doesn't obscure track
-                                       int nameWidth = fm.stringWidth(waypointName);
-                                       if (nameWidth < (width - 2 * BORDER_WIDTH))
-                                       {
-                                               boolean drawnName = false;
-                                               // Make arrays for coordinates right left up down
-                                               int[] nameXs = {x + 2, x - nameWidth - 2, x - nameWidth/2, x - nameWidth/2};
-                                               int[] nameYs = {y + (nameHeight/2), y + (nameHeight/2), y - 2, y + nameHeight + 2};
-                                               for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
-                                               {
-                                                       // Shift arrays for coordinates right left up down
-                                                       nameXs[0] += 2; nameXs[1] -= 2;
-                                                       nameYs[2] -= 2; nameYs[3] += 2;
-                                                       // Check each direction in turn right left up down
-                                                       for (int a=0; a<4; a++)
-                                                       {
-                                                               if (nameXs[a] > BORDER_WIDTH && (nameXs[a] + nameWidth) < (width - BORDER_WIDTH)
-                                                                       && nameYs[a] < (height - BORDER_WIDTH) && (nameYs[a] - nameHeight) > BORDER_WIDTH
-                                                                       && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight))
-                                                               {
-                                                                       // Found a rectangle to fit - draw name here and quit
-                                                                       bufferedG.drawString(waypointName, nameXs[a], nameYs[a]);
-                                                                       drawnName = true;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-               bufferedG.dispose();
-       }
-
-
-       /**
-        * Tests whether there are any data points within the specified x,y rectangle
-        * @param inX left X coordinate
-        * @param inY bottom Y coordinate
-        * @param inWidth width of rectangle
-        * @param inHeight height of rectangle
-        * @return true if there's at least one data point in the rectangle
-        */
-       private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
-       {
-               try
-               {
-                       // loop over x coordinate of rectangle
-                       for (int x=0; x<inWidth; x++)
-                       {
-                               // loop over y coordinate of rectangle
-                               for (int y=0; y<inHeight; y++)
-                               {
-                                       int pixelColor = _image.getRGB(inX + x, inY - y);
-                                       if (pixelColor != -1) return true;
-                               }
-                       }
-               }
-               catch (NullPointerException e) {
-                       // ignore null pointers, just return false
-               }
-               return false;
-       }
-
-
-       /**
-        * Make the popup menu for right-clicking the map
-        */
-       private void makePopup()
-       {
-               _popup = new JPopupMenu();
-               JMenuItem zoomIn = new JMenuItem(I18nManager.getText("menu.map.zoomin"));
-               zoomIn.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               zoomMap(true);
-                       }});
-               zoomIn.setEnabled(true);
-               _popup.add(zoomIn);
-               JMenuItem zoomOut = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
-               zoomOut.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               zoomMap(false);
-                       }});
-               zoomOut.setEnabled(true);
-               _popup.add(zoomOut);
-               JMenuItem zoomFull = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
-               zoomFull.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               zoomToFullScale();
-                       }});
-               zoomFull.setEnabled(true);
-               _popup.add(zoomFull);
-               _connectPointsMenuItem = new JCheckBoxMenuItem(I18nManager.getText("menu.map.connect"));
-               _connectPointsMenuItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               // redraw map
-                               dataUpdated(DataSubscriber.ALL);
-                       }
-               });
-               _connectPointsMenuItem.setSelected(true);
-               _popup.add(_connectPointsMenuItem);
-               _autoPanMenuItem = new JCheckBoxMenuItem(I18nManager.getText("menu.map.autopan"));
-               _autoPanMenuItem.setSelected(true);
-               _popup.add(_autoPanMenuItem);
-       }
-
-
-       /**
-        * Zoom map to full scale
-        */
-       private void zoomToFullScale()
-       {
-               _zoomScale = 1.0;
-               _offsetX = 0.0;
-               _offsetY = 0.0;
-               _numPoints = 0;
-               dataUpdated(DataSubscriber.ALL);
-       }
-
-
-       /**
-        * Zoom map either in or out by one step
-        * @param inZoomIn true to zoom in, false for out
-        */
-       private void zoomMap(boolean inZoomIn)
-       {
-               if (inZoomIn)
-               {
-                       // Zoom in
-                       _zoomScale *= ZOOM_SCALE_FACTOR;
-               }
-               else
-               {
-                       // Zoom out
-                       _zoomScale /= ZOOM_SCALE_FACTOR;
-                       if (_zoomScale < 0.5) _zoomScale = 0.5;
-               }
-               _numPoints = 0;
-               dataUpdated(DataSubscriber.ALL);
-       }
-
-
-       /**
-        * Pan the map by the specified amounts
-        * @param inUp upwards pan
-        * @param inRight rightwards pan
-        */
-       private void panMap(int inUp, int inRight)
-       {
-               double panFactor = _scale / _zoomScale;
-               _offsetY = _offsetY + (inUp * panFactor);
-               _offsetX = _offsetX - (inRight * panFactor);
-               // Limit pan to sensible range??
-               _numPoints = 0;
-               _image = null;
-               repaint();
-       }
-
-
-       /**
-        * React to click on map display
-        * @param inE mouse event
-        */
-       public void mouseClicked(MouseEvent inE)
-       {
-               this.requestFocus();
-               if (_track != null)
-               {
-                       int xClick = inE.getX();
-                       int yClick = inE.getY();
-                       // Check click is within main area (not in border)
-                       if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH)
-                               && yClick < (getHeight() - BORDER_WIDTH))
-                       {
-                               // Check left click or right click
-                               if (inE.isMetaDown())
-                               {
-                                       // Only show popup if track has data
-                                       if (_track != null && _track.getNumPoints() > 0)
-                                               _popup.show(this, xClick, yClick);
-                               }
-                               else
-                               {
-                                       // Find point within range of click point
-                                       double pointX = (xClick - getWidth()/2) * _scale / _zoomScale + _offsetX;
-                                       double pointY = (getHeight()/2 - yClick) * _scale / _zoomScale + _offsetY;
-                                       int selectedPointIndex = _track.getNearestPointIndex(
-                                               pointX, pointY, CLICK_SENSITIVITY * _scale, false);
-                                       // Select the given point (or deselect if no point was found)
-                                       _trackInfo.getSelection().selectPoint(selectedPointIndex);
-                               }
-                       }
-               }
-       }
-
-
-       /**
-        * Respond to mouse released to reset dragging
-        * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
-        */
-       public void mouseReleased(MouseEvent e)
-       {
-               _dragStartX = _dragStartY = -1;
-               if (e.isMetaDown())
-               {
-                       if (_zoomDragFromX >= 0 || _zoomDragFromY >= 0)
-                       {
-                               // zoom area marked out - calculate offset and zoom
-                               int xPan = (getWidth() - _zoomDragFromX - e.getX()) / 2;
-                               int yPan = (getHeight() - _zoomDragFromY - e.getY()) / 2;
-                               double xZoom = Math.abs(getWidth() * 1.0 / (e.getX() - _zoomDragFromX));
-                               double yZoom = Math.abs(getHeight() * 1.0 / (e.getY() - _zoomDragFromY));
-                               double extraZoom = (xZoom>yZoom?yZoom:xZoom);
-                               // deselect point if selected (to stop autopan)
-                               _trackInfo.getSelection().selectPoint(-1);
-                               // Pan first to ensure pan occurs with correct scale
-                               panMap(yPan, xPan);
-                               // Then zoom in and request repaint
-                               _zoomScale = _zoomScale * extraZoom;
-                               _image = null;
-                               repaint();
-                       }
-                       _zoomDragFromX = _zoomDragFromY = -1;
-                       _zoomDragging = false;
-               }
-       }
-
-
-       /**
-        * Respond to mouse wheel events to zoom the map
-        * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
-        */
-       public void mouseWheelMoved(MouseWheelEvent e)
-       {
-               zoomMap(e.getWheelRotation() < 0);
-       }
-
-
-       /**
-        * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
-        */
-       public void keyPressed(KeyEvent e)
-       {
-               int code = e.getKeyCode();
-               // Check for meta key
-               if (e.isControlDown())
-               {
-                       // Check for arrow keys to zoom in and out
-                       if (code == KeyEvent.VK_UP)
-                               zoomMap(true);
-                       else if (code == KeyEvent.VK_DOWN)
-                               zoomMap(false);
-                       // Key nav for next/prev point
-                       else if (code == KeyEvent.VK_LEFT)
-                               _trackInfo.getSelection().selectPreviousPoint();
-                       else if (code == KeyEvent.VK_RIGHT)
-                               _trackInfo.getSelection().selectNextPoint();
-               }
-               else
-               {
-                       // Check for arrow keys to pan
-                       int upwardsPan = 0;
-                       if (code == KeyEvent.VK_UP)
-                               upwardsPan = PAN_DISTANCE;
-                       else if (code == KeyEvent.VK_DOWN)
-                               upwardsPan = -PAN_DISTANCE;
-                       int rightwardsPan = 0;
-                       if (code == KeyEvent.VK_RIGHT)
-                               rightwardsPan = -PAN_DISTANCE;
-                       else if (code == KeyEvent.VK_LEFT)
-                               rightwardsPan = PAN_DISTANCE;
-                       panMap(upwardsPan, rightwardsPan);
-                       // Check for delete key to delete current point
-                       if (code == KeyEvent.VK_DELETE && _trackInfo.getSelection().getCurrentPointIndex() >= 0)
-                       {
-                               _app.deleteCurrentPoint();
-                               // reset last selected point to trigger autopan
-                               _lastSelectedPoint = -1;
-                       }
-               }
-       }
-
-
-       /**
-        * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
-        */
-       public void keyReleased(KeyEvent e)
-       {
-               // ignore
-       }
-
-
-       /**
-        * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
-        */
-       public void keyTyped(KeyEvent e)
-       {
-               // ignore
-       }
-
-
-       /**
-        * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
-        */
-       public void mouseDragged(MouseEvent e)
-       {
-               if (!e.isMetaDown())
-               {
-                       if (_dragStartX > 0)
-                       {
-                               int xShift = e.getX() - _dragStartX;
-                               int yShift = e.getY() - _dragStartY;
-                               panMap(yShift, xShift);
-                       }
-                       _dragStartX = e.getX();
-                       _dragStartY = e.getY();
-               }
-               else
-               {
-                       // Right click-and-drag for zoom
-                       if (_zoomDragFromX < 0 || _zoomDragFromY < 0)
-                       {
-                               _zoomDragFromX = e.getX();
-                               _zoomDragFromY = e.getY();
-                       }
-                       else
-                       {
-                               _zoomDragToX = e.getX();
-                               _zoomDragToY = e.getY();
-                               _zoomDragging = true;
-                       }
-                       repaint();
-               }
-       }
-
-
-       /**
-        * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
-        */
-       public void mouseMoved(MouseEvent e)
-       {
-               // ignore
-       }
-}
index 4b752681244e03b80bd845f7a908c493a6357f57..e1c8cf0da6470dff921d1329621eaf3cc4807734 100644 (file)
@@ -4,8 +4,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
-
-import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 import javax.swing.JMenu;
@@ -54,10 +52,11 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _selectStartItem = null;
        private JMenuItem _selectEndItem = null;
        private JMenuItem _reverseItem = null;
+       private JMenuItem _addTimeOffsetItem = null;
        private JMenuItem _mergeSegmentsItem = null;
        private JMenu     _rearrangeMenu = null;
+       private JMenuItem _cutAndMoveItem = null;
        private JMenuItem _show3dItem = null;
-       private JMenuItem _showOsmMapItem = null;
        private JMenu     _browserMapMenu = null;
        private JMenuItem _saveExifItem = null;
        private JMenuItem _connectPhotoItem = null;
@@ -71,6 +70,8 @@ public class MenuManager implements DataSubscriber
        private ActionListener _saveAction = null;
        private ActionListener _undoAction = null;
        private ActionListener _editPointAction = null;
+       private ActionListener _deletePointAction = null;
+       private ActionListener _deleteRangeAction = null;
        private ActionListener _selectStartAction = null;
        private ActionListener _selectEndAction = null;
        private ActionListener _connectPhotoAction = null;
@@ -79,6 +80,8 @@ public class MenuManager implements DataSubscriber
        private JButton _saveButton = null;
        private JButton _undoButton = null;
        private JButton _editPointButton = null;
+       private JButton _deletePointButton = null;
+       private JButton _deleteRangeButton = null;
        private JButton _selectStartButton = null;
        private JButton _selectEndButton = null;
        private JButton _connectPhotoButton = null;
@@ -129,6 +132,16 @@ public class MenuManager implements DataSubscriber
                };
                addPhotosMenuItem.addActionListener(_addPhotoAction);
                fileMenu.add(addPhotosMenuItem);
+               // Add photos
+               JMenuItem loadFromGpsMenuItem = new JMenuItem(I18nManager.getText("menu.file.loadfromgps"));
+               loadFromGpsMenuItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _app.beginLoadFromGps();
+                       }
+               });
+               fileMenu.add(loadFromGpsMenuItem);
+               fileMenu.addSeparator();
                // Save
                _saveItem = new JMenuItem(I18nManager.getText("menu.file.save"), KeyEvent.VK_S);
                _saveAction = new ActionListener() {
@@ -180,6 +193,7 @@ public class MenuManager implements DataSubscriber
                });
                fileMenu.add(exitMenuItem);
                menubar.add(fileMenu);
+               // Edit menu
                JMenu editMenu = new JMenu(I18nManager.getText("menu.edit"));
                editMenu.setMnemonic(KeyEvent.VK_E);
                _undoItem = new JMenuItem(I18nManager.getText("menu.edit.undo"));
@@ -222,21 +236,23 @@ public class MenuManager implements DataSubscriber
                _editWaypointNameItem.setEnabled(false);
                editMenu.add(_editWaypointNameItem);
                _deletePointItem = new JMenuItem(I18nManager.getText("menu.edit.deletepoint"));
-               _deletePointItem.addActionListener(new ActionListener() {
+               _deletePointAction = new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                                _app.deleteCurrentPoint();
                        }
-               });
+               };
+               _deletePointItem.addActionListener(_deletePointAction);
                _deletePointItem.setEnabled(false);
                editMenu.add(_deletePointItem);
                _deleteRangeItem = new JMenuItem(I18nManager.getText("menu.edit.deleterange"));
-               _deleteRangeItem.addActionListener(new ActionListener() {
+               _deleteRangeAction = new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                                _app.deleteSelectedRange();
                        }
-               });
+               };
+               _deleteRangeItem.addActionListener(_deleteRangeAction);
                _deleteRangeItem.setEnabled(false);
                editMenu.add(_deleteRangeItem);
                _deleteDuplicatesItem = new JMenuItem(I18nManager.getText("menu.edit.deleteduplicates"));
@@ -276,6 +292,15 @@ public class MenuManager implements DataSubscriber
                });
                _reverseItem.setEnabled(false);
                editMenu.add(_reverseItem);
+               _addTimeOffsetItem = new JMenuItem(I18nManager.getText("menu.edit.addtimeoffset"));
+               _addTimeOffsetItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _app.beginAddTimeOffset();
+                       }
+               });
+               _addTimeOffsetItem.setEnabled(false);
+               editMenu.add(_addTimeOffsetItem);
                _mergeSegmentsItem = new JMenuItem(I18nManager.getText("menu.edit.mergetracksegments"));
                _mergeSegmentsItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
@@ -316,6 +341,15 @@ public class MenuManager implements DataSubscriber
                rearrangeNearestItem.setEnabled(true);
                _rearrangeMenu.add(rearrangeNearestItem);
                editMenu.add(_rearrangeMenu);
+               _cutAndMoveItem = new JMenuItem(I18nManager.getText("menu.edit.cutandmove"));
+               _cutAndMoveItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _app.cutAndMoveSelection();
+                       }
+               });
+               _cutAndMoveItem.setEnabled(false);
+               editMenu.add(_cutAndMoveItem);
                menubar.add(editMenu);
 
                // Select menu
@@ -372,16 +406,6 @@ public class MenuManager implements DataSubscriber
                });
                _show3dItem.setEnabled(false);
                viewMenu.add(_show3dItem);
-               // Show OSM map
-               _showOsmMapItem = new JMenuItem(I18nManager.getText("menu.view.showmap"));
-               _showOsmMapItem.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _app.showOsmMap();
-                       }
-               });
-               _showOsmMapItem.setEnabled(false);
-               viewMenu.add(_showOsmMapItem);
                // browser submenu
                _browserMapMenu = new JMenu(I18nManager.getText("menu.view.browser"));
                _browserMapMenu.setEnabled(false);
@@ -479,6 +503,14 @@ public class MenuManager implements DataSubscriber
                        }
                });
                helpMenu.add(aboutItem);
+               JMenuItem checkVersionItem = new JMenuItem(I18nManager.getText("menu.help.checkversion"));
+               checkVersionItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               CheckVersionScreen.show(_parent);
+                       }
+               });
+               helpMenu.add(checkVersionItem);
                menubar.add(helpMenu);
 
                return menubar;
@@ -493,45 +525,57 @@ public class MenuManager implements DataSubscriber
        {
                JToolBar toolbar = new JToolBar();
                // Add text file
-               JButton openFileButton = new JButton(new ImageIcon(getClass().getResource("images/add_textfile_icon.png")));
+               JButton openFileButton = new JButton(IconManager.getImageIcon(IconManager.OPEN_FILE));
                openFileButton.setToolTipText(I18nManager.getText("menu.file.open"));
                openFileButton.addActionListener(_openFileAction);
                toolbar.add(openFileButton);
                // Add photo
-               JButton addPhotoButton = new JButton(new ImageIcon(getClass().getResource("images/add_photo_icon.png")));
+               JButton addPhotoButton = new JButton(IconManager.getImageIcon(IconManager.ADD_PHOTO));
                addPhotoButton.setToolTipText(I18nManager.getText("menu.file.addphotos"));
                addPhotoButton.addActionListener(_addPhotoAction);
                toolbar.add(addPhotoButton);
                // Save
-               _saveButton = new JButton(new ImageIcon(getClass().getResource("images/save_icon.gif")));
+               _saveButton = new JButton(IconManager.getImageIcon(IconManager.SAVE_FILE));
                _saveButton.setToolTipText(I18nManager.getText("menu.file.save"));
                _saveButton.addActionListener(_saveAction);
                _saveButton.setEnabled(false);
                toolbar.add(_saveButton);
                // Undo
-               _undoButton = new JButton(new ImageIcon(getClass().getResource("images/undo_icon.gif")));
+               _undoButton = new JButton(IconManager.getImageIcon(IconManager.UNDO));
                _undoButton.setToolTipText(I18nManager.getText("menu.edit.undo"));
                _undoButton.addActionListener(_undoAction);
                _undoButton.setEnabled(false);
                toolbar.add(_undoButton);
                // Edit point
-               _editPointButton = new JButton(new ImageIcon(getClass().getResource("images/edit_point_icon.gif")));
+               _editPointButton = new JButton(IconManager.getImageIcon(IconManager.EDIT_POINT));
                _editPointButton.setToolTipText(I18nManager.getText("menu.edit.editpoint"));
                _editPointButton.addActionListener(_editPointAction);
                _editPointButton.setEnabled(false);
                toolbar.add(_editPointButton);
+               // Delete point
+               _deletePointButton = new JButton(IconManager.getImageIcon(IconManager.DELETE_POINT));
+               _deletePointButton.setToolTipText(I18nManager.getText("menu.edit.deletepoint"));
+               _deletePointButton.addActionListener(_deletePointAction);
+               _deletePointButton.setEnabled(false);
+               toolbar.add(_deletePointButton);
+               // Delete range
+               _deleteRangeButton = new JButton(IconManager.getImageIcon(IconManager.DELETE_RANGE));
+               _deleteRangeButton.setToolTipText(I18nManager.getText("menu.edit.deleterange"));
+               _deleteRangeButton.addActionListener(_deleteRangeAction);
+               _deleteRangeButton.setEnabled(false);
+               toolbar.add(_deleteRangeButton);
                // Select start, end
-               _selectStartButton = new JButton(new ImageIcon(getClass().getResource("images/set_start_icon.png")));
+               _selectStartButton = new JButton(IconManager.getImageIcon(IconManager.SET_RANGE_START));
                _selectStartButton.setToolTipText(I18nManager.getText("menu.select.start"));
                _selectStartButton.addActionListener(_selectStartAction);
                _selectStartButton.setEnabled(false);
                toolbar.add(_selectStartButton);
-               _selectEndButton = new JButton(new ImageIcon(getClass().getResource("images/set_end_icon.png")));
+               _selectEndButton = new JButton(IconManager.getImageIcon(IconManager.SET_RANGE_END));
                _selectEndButton.setToolTipText(I18nManager.getText("menu.select.end"));
                _selectEndButton.addActionListener(_selectEndAction);
                _selectEndButton.setEnabled(false);
                toolbar.add(_selectEndButton);
-               _connectPhotoButton = new JButton(new ImageIcon(getClass().getResource("images/connect_photo_icon.png")));
+               _connectPhotoButton = new JButton(IconManager.getImageIcon(IconManager.CONNECT_PHOTO));
                _connectPhotoButton.setToolTipText(I18nManager.getText("menu.photo.connect"));
                _connectPhotoButton.addActionListener(_connectPhotoAction);
                _connectPhotoButton.setEnabled(false);
@@ -574,7 +618,6 @@ public class MenuManager implements DataSubscriber
                _selectNoneItem.setEnabled(hasData);
                if (_show3dItem != null)
                        _show3dItem.setEnabled(hasData);
-               _showOsmMapItem.setEnabled(hasData);
                _browserMapMenu.setEnabled(hasData);
                // is undo available?
                boolean hasUndo = !_app.getUndoStack().isEmpty();
@@ -587,6 +630,7 @@ public class MenuManager implements DataSubscriber
                _editPointButton.setEnabled(hasPoint);
                _editWaypointNameItem.setEnabled(hasPoint);
                _deletePointItem.setEnabled(hasPoint);
+               _deletePointButton.setEnabled(hasPoint);
                _selectStartItem.setEnabled(hasPoint);
                _selectStartButton.setEnabled(hasPoint);
                _selectEndItem.setEnabled(hasPoint);
@@ -596,9 +640,9 @@ public class MenuManager implements DataSubscriber
                _saveExifItem.setEnabled(anyPhotos);
                // is there a current photo?
                boolean hasPhoto = anyPhotos && _selection.getCurrentPhotoIndex() >= 0;
-               // connect is only available when current photo is not connected to current point
-               boolean connectAvailable = hasPhoto && hasPoint
-                       && _track.getPoint(_selection.getCurrentPointIndex()).getPhoto() == null;
+               // connect is available if photo and point selected, and photo has no point
+               boolean connectAvailable = hasPhoto && hasPoint && _photos.getPhoto(_selection.getCurrentPhotoIndex()) != null
+                       && _photos.getPhoto(_selection.getCurrentPhotoIndex()).getDataPoint() == null;
                _connectPhotoItem.setEnabled(connectAvailable);
                _connectPhotoButton.setEnabled(connectAvailable);
                _disconnectPhotoItem.setEnabled(hasPhoto && _photos.getPhoto(_selection.getCurrentPhotoIndex()) != null
@@ -608,10 +652,16 @@ public class MenuManager implements DataSubscriber
                // is there a current range?
                boolean hasRange = (hasData && _selection.hasRangeSelected());
                _deleteRangeItem.setEnabled(hasRange);
+               _deleteRangeButton.setEnabled(hasRange);
                _interpolateItem.setEnabled(hasRange
                        && (_selection.getEnd() - _selection.getStart()) == 1);
                _mergeSegmentsItem.setEnabled(hasRange);
                _reverseItem.setEnabled(hasRange);
+               _addTimeOffsetItem.setEnabled(hasRange);
+               // Is the currently selected point outside the current range?
+               _cutAndMoveItem.setEnabled(hasRange && hasPoint &&
+                       (_selection.getCurrentPointIndex() < _selection.getStart()
+                               || _selection.getCurrentPointIndex() > (_selection.getEnd()+1)));
        }
 
 
index 203db4527412f79a5110c8d46905cddc1afc52aa..28e0a95b14a2dc6e3915a6f1f62d9e50d422a7f7 100644 (file)
@@ -239,6 +239,7 @@ public class SelectorDisplay extends GenericDisplay
                if (_waypointList.getSelectedIndex() >= 0)
                {
                        if (_trackInfo.getCurrentPoint() == null
+                        || _waypointList.getSelectedIndex() >= _waypointListModel.getSize()
                         || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint()))
                        {
                                // point is selected in list but different from current point - deselect
index 75810db260420e1557929ead9dd8d93431e6d74c..a5462ed1bd078db252167b18aab5116bd9c78d29 100644 (file)
@@ -63,7 +63,6 @@ public class StatusBar extends JPanel implements Runnable, DataSubscriber
                        _thread = new Thread(this);
                        _thread.start();
                }
-               // TODO: Emphasize status bar when text set, eg change colour, make bold or something
        }
 
        /**
diff --git a/tim/prune/gui/TimeOffsetDialog.java b/tim/prune/gui/TimeOffsetDialog.java
new file mode 100644 (file)
index 0000000..11524fe
--- /dev/null
@@ -0,0 +1,162 @@
+package tim.prune.gui;
+
+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 javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.SwingConstants;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+
+/**
+ * Class to show a dialog for adding a time offset to a track range
+ */
+public class TimeOffsetDialog
+{
+       private App _app = null;
+       private JFrame _parentFrame = null;
+       private JDialog _dialog = null;
+       private JRadioButton _addRadio = null, _subtractRadio = null;
+       private WholeNumberField _dayField = null, _hourField = null;
+       private WholeNumberField _minuteField = null;
+
+
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        * @param inParentFrame parent frame
+        */
+       public TimeOffsetDialog(App inApp, JFrame inParentFrame)
+       {
+               _app = inApp;
+               _parentFrame = inParentFrame;
+       }
+
+
+       /**
+        * Show the dialog to select options and export file
+        */
+       public void showDialog()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.addtimeoffset.title"), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               _dialog.show();
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+               // Make a panel for the two radio buttons
+               JPanel radioPanel = new JPanel();
+               _addRadio = new JRadioButton(I18nManager.getText("dialog.addtimeoffset.add"));
+               _addRadio.setSelected(true);
+               radioPanel.add(_addRadio);
+               _subtractRadio = new JRadioButton(I18nManager.getText("dialog.addtimeoffset.subtract"));
+               _subtractRadio.setSelected(false);
+               radioPanel.add(_subtractRadio);
+               ButtonGroup radioGroup = new ButtonGroup();
+               radioGroup.add(_addRadio);
+               radioGroup.add(_subtractRadio);
+               mainPanel.add(radioPanel);
+
+               // Make a listener to validate the text boxes during typing (to en/disable OK button)
+
+               // Make a central panel with the text boxes
+               JPanel descPanel = new JPanel();
+               descPanel.setLayout(new GridLayout(0, 2));
+               descPanel.add(makeRightLabel("dialog.addtimeoffset.days"));
+               _dayField = new WholeNumberField(3);
+               descPanel.add(_dayField);
+               descPanel.add(makeRightLabel("dialog.addtimeoffset.hours"));
+               _hourField = new WholeNumberField(3);
+               descPanel.add(_hourField);
+               descPanel.add(makeRightLabel("dialog.addtimeoffset.minutes"));
+               _minuteField = new WholeNumberField(3);
+               descPanel.add(_minuteField);
+               mainPanel.add(descPanel);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton 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)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+
+       /**
+        * @param inKey text key
+        * @return right-aligned label
+        */
+       private static final JLabel makeRightLabel(String inKey)
+       {
+               JLabel label = new JLabel(I18nManager.getText(inKey) + " : ");
+               label.setHorizontalAlignment(SwingConstants.RIGHT);
+               return label;
+       }
+
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               // Calculate offset to add or subtract
+               long offsetSecs = _minuteField.getValue() * 60L
+                       + _hourField.getValue() * 60L * 60L
+                       + _dayField.getValue() * 60L * 60L * 24L;
+               if (_subtractRadio.isSelected()) {offsetSecs = -offsetSecs;}
+               if (offsetSecs != 0L)
+               {
+                       // Pass offset back to app and close dialog
+                       _app.finishAddTimeOffset(offsetSecs);
+                       _dialog.dispose();
+               }
+       }
+}
diff --git a/tim/prune/gui/WholeNumberField.java b/tim/prune/gui/WholeNumberField.java
new file mode 100644 (file)
index 0000000..dcf6740
--- /dev/null
@@ -0,0 +1,88 @@
+package tim.prune.gui;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * text field for holding a single integer with validation
+ */
+public class WholeNumberField extends JTextField
+{
+       /**
+        * Inner class to act as document for validation
+        */
+       protected class WholeNumberDocument extends PlainDocument
+       {
+               /** Num digits to allow */
+               private int _maxDigits = 0;
+
+               /**
+                * Constructor
+                * @param inMaxDigits max digits to allow
+                */
+               public WholeNumberDocument(int inMaxDigits)
+               {
+                       _maxDigits = inMaxDigits;
+               }
+
+
+               /**
+                * Override the insert string method
+                * @param offs offset
+                * @param str string
+                * @param a attributes
+                * @throws BadLocationException on insert failure
+                */
+               public void insertString(int offs, String str, AttributeSet a)
+                       throws BadLocationException
+               {
+                       if (getLength() >= _maxDigits) return;
+                       char[] source = str.toCharArray();
+                       char[] result = new char[source.length];
+                       int j = 0;
+                       for (int i = 0; i < result.length && j < _maxDigits; i++) {
+                               if (Character.isDigit(source[i]))
+                                       result[j++] = source[i];
+                       }
+                       super.insertString(offs, new String(result, 0, j), a);
+               }
+       }
+
+
+       /**
+        * Constructor
+        * @param inMaxDigits max digits to allow
+        */
+       public WholeNumberField(int inMaxDigits)
+       {
+               super("0");
+               setDocument(new WholeNumberDocument(inMaxDigits));
+       }
+
+       /**
+        * @return integer value
+        */
+       public int getValue()
+       {
+               return parseValue(getText());
+       }
+
+       /**
+        * @param inText text to parse
+        * @return value as integer
+        */
+       private static int parseValue(String inText)
+       {
+               int value = 0;
+               try {
+                       value = Integer.parseInt(inText);
+               }
+               catch (NumberFormatException nfe) {}
+               if (value < 0) {
+                       value = 0;
+               }
+               return value;
+       }
+}
diff --git a/tim/prune/gui/images/autopan.gif b/tim/prune/gui/images/autopan.gif
new file mode 100644 (file)
index 0000000..e80306b
Binary files /dev/null and b/tim/prune/gui/images/autopan.gif differ
diff --git a/tim/prune/gui/images/autopan_on.gif b/tim/prune/gui/images/autopan_on.gif
new file mode 100644 (file)
index 0000000..1505eed
Binary files /dev/null and b/tim/prune/gui/images/autopan_on.gif differ
diff --git a/tim/prune/gui/images/cut_and_move.gif b/tim/prune/gui/images/cut_and_move.gif
new file mode 100644 (file)
index 0000000..2249560
Binary files /dev/null and b/tim/prune/gui/images/cut_and_move.gif differ
diff --git a/tim/prune/gui/images/delete_point_icon.gif b/tim/prune/gui/images/delete_point_icon.gif
new file mode 100644 (file)
index 0000000..c860af2
Binary files /dev/null and b/tim/prune/gui/images/delete_point_icon.gif differ
diff --git a/tim/prune/gui/images/delete_range_icon.gif b/tim/prune/gui/images/delete_range_icon.gif
new file mode 100644 (file)
index 0000000..aa83ef0
Binary files /dev/null and b/tim/prune/gui/images/delete_range_icon.gif differ
diff --git a/tim/prune/gui/images/link.gif b/tim/prune/gui/images/link.gif
new file mode 100644 (file)
index 0000000..1c397ec
Binary files /dev/null and b/tim/prune/gui/images/link.gif differ
diff --git a/tim/prune/gui/images/map_icon.gif b/tim/prune/gui/images/map_icon.gif
new file mode 100644 (file)
index 0000000..ec6cca4
Binary files /dev/null and b/tim/prune/gui/images/map_icon.gif differ
diff --git a/tim/prune/gui/images/map_icon_on.gif b/tim/prune/gui/images/map_icon_on.gif
new file mode 100644 (file)
index 0000000..c13935d
Binary files /dev/null and b/tim/prune/gui/images/map_icon_on.gif differ
diff --git a/tim/prune/gui/images/points_connected.gif b/tim/prune/gui/images/points_connected.gif
new file mode 100644 (file)
index 0000000..e6091c1
Binary files /dev/null and b/tim/prune/gui/images/points_connected.gif differ
diff --git a/tim/prune/gui/images/points_disconnected.gif b/tim/prune/gui/images/points_disconnected.gif
new file mode 100644 (file)
index 0000000..1015169
Binary files /dev/null and b/tim/prune/gui/images/points_disconnected.gif differ
diff --git a/tim/prune/gui/images/zoom_in.gif b/tim/prune/gui/images/zoom_in.gif
new file mode 100644 (file)
index 0000000..4251856
Binary files /dev/null and b/tim/prune/gui/images/zoom_in.gif differ
diff --git a/tim/prune/gui/images/zoom_out.gif b/tim/prune/gui/images/zoom_out.gif
new file mode 100644 (file)
index 0000000..2aa49f9
Binary files /dev/null and b/tim/prune/gui/images/zoom_out.gif differ
index d306a094d7a1b0c6a24e3cef6d3281ea31de8b14..e6236cde92a88dcba239635428519de6ba6e02a6 100644 (file)
 package tim.prune.gui.map;
 
+import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
 import java.awt.Graphics;
-import java.awt.MediaTracker;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
 import java.awt.image.BufferedImage;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.awt.image.RescaleOp;
 
-import javax.swing.ImageIcon;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 
+import tim.prune.App;
+import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
 import tim.prune.data.DoubleRange;
+import tim.prune.data.Selection;
 import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.IconManager;
 
 /**
  * Class for the map canvas, to display a background map and draw on it
  */
-public class MapCanvas extends JPanel
+public class MapCanvas extends JPanel implements MouseListener, MouseMotionListener, DataSubscriber,
+       KeyListener, MouseWheelListener
 {
-
-       private BufferedImage _mapImage = null;
+       /** App object for callbacks */
+       private App _app = null;
+       /** Track object */
        private Track _track = null;
+       /** Selection object */
+       private Selection _selection = null;
+       /** Previously selected point */
+       private int _prevSelectedPoint = -1;
+       /** Tile cacher */
+       private MapTileCacher _tileCacher = new MapTileCacher(this);
+       /** Image to display */
+       private BufferedImage _mapImage = null;
+       /** Slider for transparency */
+       private JSlider _transparencySlider = null;
+       /** Checkbox for maps */
+       private JCheckBox _mapCheckBox = null;
+       /** Checkbox for autopan */
+       private JCheckBox _autopanCheckBox = null;
+       /** Checkbox for connecting track points */
+       private JCheckBox _connectCheckBox = null;
+       /** Right-click popup menu */
+       private JPopupMenu _popup = null;
+       /** Top component panel */
+       private JPanel _topPanel = null;
+       /** Side component panel */
+       private JPanel _sidePanel = null;
+       /* Data */
        private DoubleRange _latRange = null, _lonRange = null;
        private DoubleRange _xRange = null, _yRange = null;
-       private boolean _gettingTiles = false;
-       /** Current zoom level */
-       private int _currZoom = 0;
-       /** Maximum zoom level (to avoid panning) */
-       private int _maxZoom = 0;
+       private boolean _recalculate = false;
+       /** Flag to check bounds on next paint */
+       private boolean _checkBounds = false;
+       /** Map position */
+       private MapPosition _mapPosition = null;
+       /** x coordinate of drag from point */
+       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 */
+       private int _dragToY = -1;
+       /** x coordinate of popup menu */
+       private int _popupMenuX = -1;
+       /** y coordinate of popup menu */
+       private int _popupMenuY = -1;
+       /** Flag to prevent showing too often the error message about loading maps */
+       private boolean _shownOsmErrorAlready = false;
+
+       /** Constant for click sensitivity when selecting nearest point */
+       private static final int CLICK_SENSITIVITY = 10;
+       /** Constant for pan distance from key presses */
+       private static final int PAN_DISTANCE = 20;
+       /** Constant for pan distance from autopan */
+       private static final int AUTOPAN_DISTANCE = 75;
+
+       // Colours
+       private static final Color COLOR_BG         = Color.WHITE;
+       private static final Color COLOR_POINT      = Color.BLUE;
+       private static final Color COLOR_CURR_RANGE = Color.GREEN;
+       private static final Color COLOR_CROSSHAIRS = Color.RED;
+       private static final Color COLOR_WAYPT_NAME = Color.BLACK;
+       private static final Color COLOR_PHOTO_PT   = Color.ORANGE;
 
 
        /**
         * Constructor
-        * @param inTrack track object
+        * @param inApp App object for callbacks
+        * @param inTrackInfo track info object
         */
-       public MapCanvas(Track inTrack)
+       public MapCanvas(App inApp, TrackInfo inTrackInfo)
        {
-               _track = inTrack;
-               _latRange = inTrack.getLatRange();
-               _lonRange = inTrack.getLonRange();
-               _xRange = new DoubleRange(transformX(_lonRange.getMinimum()), transformX(_lonRange.getMaximum()));
-               _yRange = new DoubleRange(transformY(_latRange.getMinimum()), transformY(_latRange.getMaximum()));
+               _app = inApp;
+               _track = inTrackInfo.getTrack();
+               _selection = inTrackInfo.getSelection();
+               _mapPosition = new MapPosition();
+               addMouseListener(this);
+               addMouseMotionListener(this);
+               addMouseWheelListener(this);
+               addKeyListener(this);
+
+               // Make listener for changes to controls
+               ItemListener itemListener = new ItemListener() {
+                       public void itemStateChanged(ItemEvent e)
+                       {
+                               _recalculate = true;
+                               repaint();
+                       }
+               };
+               // Make special listener for changes to map checkbox
+               ItemListener mapCheckListener = new ItemListener() {
+                       public void itemStateChanged(ItemEvent e)
+                       {
+                               _tileCacher.clearAll();
+                               _recalculate = true;
+                               repaint();
+                       }
+               };
+               _topPanel = new JPanel();
+               _topPanel.setLayout(new FlowLayout());
+               _topPanel.setOpaque(false);
+               // Make slider for transparency
+               _transparencySlider = new JSlider(0, 5, 0);
+               _transparencySlider.setPreferredSize(new Dimension(100, 20));
+               _transparencySlider.setMajorTickSpacing(1);
+               _transparencySlider.setSnapToTicks(true);
+               _transparencySlider.setOpaque(false);
+               _transparencySlider.addChangeListener(new ChangeListener() {
+                       public void stateChanged(ChangeEvent e)
+                       {
+                               _recalculate = true;
+                               repaint();
+                       }
+               });
+               _transparencySlider.setFocusable(false); // stop slider from stealing keyboard focus
+               _topPanel.add(_transparencySlider);
+               // Add checkbox button for enabling maps or not
+               _mapCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.MAP_BUTTON), false);
+               _mapCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.MAP_BUTTON_ON));
+               _mapCheckBox.setOpaque(false);
+               _mapCheckBox.setToolTipText(I18nManager.getText("menu.map.showmap"));
+               _mapCheckBox.addItemListener(mapCheckListener);
+               _mapCheckBox.setFocusable(false); // stop button from stealing keyboard focus
+               _topPanel.add(_mapCheckBox);
+               // Add checkbox button for enabling autopan or not
+               _autopanCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON), true);
+               _autopanCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON_ON));
+               _autopanCheckBox.setOpaque(false);
+               _autopanCheckBox.setToolTipText(I18nManager.getText("menu.map.autopan"));
+               _autopanCheckBox.addItemListener(itemListener);
+               _autopanCheckBox.setFocusable(false); // stop button from stealing keyboard focus
+               _topPanel.add(_autopanCheckBox);
+               // Add checkbox button for connecting points or not
+               _connectCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON), true);
+               _connectCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
+               _connectCheckBox.setOpaque(false);
+               _connectCheckBox.setToolTipText(I18nManager.getText("menu.map.connect"));
+               _connectCheckBox.addItemListener(itemListener);
+               _connectCheckBox.setFocusable(false); // stop button from stealing keyboard focus
+               _topPanel.add(_connectCheckBox);
+
+               // Add zoom in, zoom out buttons
+               _sidePanel = new JPanel();
+               _sidePanel.setLayout(new BoxLayout(_sidePanel, BoxLayout.Y_AXIS));
+               _sidePanel.setOpaque(false);
+               JButton zoomInButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_IN_BUTTON));
+               zoomInButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+               zoomInButton.setContentAreaFilled(false);
+               zoomInButton.setToolTipText(I18nManager.getText("menu.map.zoomin"));
+               zoomInButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               zoomIn();
+                       }
+               });
+               zoomInButton.setFocusable(false); // stop button from stealing keyboard focus
+               _sidePanel.add(zoomInButton);
+               JButton zoomOutButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_OUT_BUTTON));
+               zoomOutButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+               zoomOutButton.setContentAreaFilled(false);
+               zoomOutButton.setToolTipText(I18nManager.getText("menu.map.zoomout"));
+               zoomOutButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               zoomOut();
+                       }
+               });
+               zoomOutButton.setFocusable(false); // stop button from stealing keyboard focus
+               _sidePanel.add(zoomOutButton);
+
+               // add control panels to this one
+               setLayout(new BorderLayout());
+               _topPanel.setVisible(false);
+               _sidePanel.setVisible(false);
+               add(_topPanel, BorderLayout.NORTH);
+               add(_sidePanel, BorderLayout.WEST);
+               // Make popup menu
+               makePopup();
        }
 
+
+       /**
+        * Make the popup menu for right-clicking the map
+        */
+       private void makePopup()
+       {
+               _popup = new JPopupMenu();
+               JMenuItem zoomInItem = new JMenuItem(I18nManager.getText("menu.map.zoomin"));
+               zoomInItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               zoomIn();
+                       }});
+               zoomInItem.setEnabled(true);
+               _popup.add(zoomInItem);
+               JMenuItem zoomOutItem = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
+               zoomOutItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               zoomOut();
+                       }});
+               zoomOutItem.setEnabled(true);
+               _popup.add(zoomOutItem);
+               JMenuItem zoomFullItem = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
+               zoomFullItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               zoomToFit();
+                               _recalculate = true;
+                               repaint();
+                       }});
+               zoomFullItem.setEnabled(true);
+               _popup.add(zoomFullItem);
+               // new point option
+               JMenuItem newPointItem = new JMenuItem(I18nManager.getText("menu.map.newpoint"));
+               newPointItem.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _app.createPoint(MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight())),
+                                       MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth())));
+                       }});
+               newPointItem.setEnabled(true);
+               _popup.add(newPointItem);
+       }
+
+
+       /**
+        * Zoom to fit the current data area
+        */
+       private void zoomToFit()
+       {
+               _latRange = _track.getLatRange();
+               _lonRange = _track.getLonRange();
+               _xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
+                       MapUtils.getXFromLongitude(_lonRange.getMaximum()));
+               _yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
+                       MapUtils.getYFromLatitude(_latRange.getMaximum()));
+               _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
+                               getWidth(), getHeight());
+       }
+
+
        /**
         * Paint method
         * @see java.awt.Canvas#paint(java.awt.Graphics)
         */
-       public void paint(Graphics g)
+       public void paint(Graphics inG)
        {
-               super.paint(g);
-               if (_mapImage == null && !_gettingTiles) {
-                       _gettingTiles = true;
-                       new Thread(new Runnable() {
-                               public void run()
+               super.paint(inG);
+               if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
+                       _mapImage = null;
+               }
+               if (_track.getNumPoints() > 0)
+               {
+                       // Check for autopan if enabled / necessary
+                       if (_autopanCheckBox.isSelected())
+                       {
+                               int selectedPoint = _selection.getCurrentPointIndex();
+                               if (selectedPoint > 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
                                {
-                                       getMapTiles();
+                                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(selectedPoint));
+                                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(selectedPoint));
+                                       int panX = 0;
+                                       int panY = 0;
+                                       if (px < PAN_DISTANCE) {
+                                               panX = px - AUTOPAN_DISTANCE;
+                                       }
+                                       else if (px > (getWidth()-PAN_DISTANCE)) {
+                                               panX = AUTOPAN_DISTANCE + px - getWidth();
+                                       }
+                                       if (py < PAN_DISTANCE) {
+                                               panY = py - AUTOPAN_DISTANCE;
+                                       }
+                                       if (py > (getHeight()-PAN_DISTANCE)) {
+                                               panY = AUTOPAN_DISTANCE + py - getHeight();
+                                       }
+                                       if (panX != 0 || panY != 0) {
+                                               _mapPosition.pan(panX, panY);
+                                       }
                                }
-                       }).start();
+                               _prevSelectedPoint = selectedPoint;
+                       }
+
+                       // Draw the mapImage if necessary
+                       if ((_mapImage == null || _recalculate)) {
+                               getMapTiles();
+                       }
+                       // Draw the prepared image onto the panel
+                       if (_mapImage != null) {
+                               inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
+                       }
+                       // Draw the zoom rectangle if necessary
+                       if (_zoomDragging)
+                       {
+                               inG.setColor(Color.RED);
+                               inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
+                               inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
+                               inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
+                               inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
+                       }
                }
-               if (_mapImage != null) {
-                       g.drawImage(_mapImage, 0, 0, 512, 512, null);
+               else
+               {
+                       inG.setColor(COLOR_BG);
+                       inG.fillRect(0, 0, getWidth(), getHeight());
+                       inG.setColor(Color.GRAY);
+                       inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
                }
+               // Draw slider etc on top
+               paintChildren(inG);
        }
 
+
        /**
-        * Get the map tiles for the specified track range
+        * Get the map tiles for the current zoom level and given tile parameters
         */
        private void getMapTiles()
        {
-               _mapImage = new BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB);
-               // zoom out until mins and maxes all on same group of four tiles
-               for (int zoom=15; zoom>1; zoom--)
+               if (_mapImage == null || _mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())
+               {
+                       _mapImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
+               }
+
+               // Clear map
+               Graphics g = _mapImage.getGraphics();
+               // Clear to white
+               g.setColor(COLOR_BG);
+               g.fillRect(0, 0, getWidth(), getHeight());
+
+               // reset error message
+               if (!_mapCheckBox.isSelected()) {_shownOsmErrorAlready = false;}
+               // Only get map tiles if selected
+               if (_mapCheckBox.isSelected())
+               {
+                       // init tile cacher
+                       _tileCacher.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
+
+                       boolean loadingFailed = false;
+                       if (_mapImage == null) return;
+
+                       // Loop over tiles drawing each one
+                       int[] tileIndices = _mapPosition.getTileIndices(getWidth(), getHeight());
+                       int[] pixelOffsets = _mapPosition.getDisplayOffsets(getWidth(), getHeight());
+                       for (int tileX = tileIndices[0]; tileX <= tileIndices[1] && !loadingFailed; tileX++)
+                       {
+                               int x = (tileX - tileIndices[0]) * 256 - pixelOffsets[0];
+                               for (int tileY = tileIndices[2]; tileY <= tileIndices[3]; tileY++)
+                               {
+                                       int y = (tileY - tileIndices[2]) * 256 - pixelOffsets[1];
+                                       Image image = _tileCacher.getTile(tileX, tileY);
+                                       if (image != null) {
+                                               g.drawImage(image, x, y, 256, 256, null);
+                                       }
+                               }
+                       }
+
+                       // Make maps brighter / fainter
+                       float[] scaleFactors = {1.0f, 1.05f, 1.1f, 1.2f, 1.6f, 2.0f};
+                       float scaleFactor = scaleFactors[_transparencySlider.getValue()];
+                       if (scaleFactor > 1.0f) {
+                               RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
+                               hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+                               RescaleOp op = new RescaleOp(scaleFactor, 0, hints);
+                               op.filter(_mapImage, _mapImage);
+                       }
+               }
+
+               int pointsPainted = 0;
+               // draw track points
+               g.setColor(COLOR_POINT);
+               int prevX = -1, prevY = -1;
+               boolean connectPoints = _connectCheckBox.isSelected();
+               for (int i=0; i<_track.getNumPoints(); i++)
                {
-                       int tx1 = (int) Math.floor(_xRange.getMinimum() * (1<<zoom));
-                       int tx2 = (int) Math.floor(_xRange.getMaximum() * (1<<zoom));
-                       int ty1 = (int) Math.floor(_yRange.getMinimum() * (1<<zoom));
-                       int ty2 = (int) Math.floor(_yRange.getMaximum() * (1<<zoom));
+                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
+                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
+                       if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+                       {
+                               if (!_track.getPoint(i).isWaypoint())
+                               {
+                                       g.drawRect(px-2, py-2, 3, 3);
+                                       // Connect track points
+                                       if (connectPoints && prevX != -1 && prevY != -1 && !_track.getPoint(i).getSegmentStart()) {
+                                               g.drawLine(prevX, prevY, px, py);
+                                       }
+                                       pointsPainted++;
+                                       prevX = px; prevY = py;
+                               }
+                       }
+                       else {
+                               prevX = -1; prevY = -1;
+                       }
+               }
 
-                       // Stop if reached a block of four adjacent tiles
-                       if (tx2 <= (tx1+1) && ty1 >= (ty2-1))
+               // Loop over points, just drawing blobs for waypoints
+               g.setColor(COLOR_WAYPT_NAME);
+               FontMetrics fm = g.getFontMetrics();
+               int nameHeight = fm.getHeight();
+               int width = getWidth();
+               int height = getHeight();
+               for (int i=0; i<_track.getNumPoints(); i++)
+               {
+                       if (_track.getPoint(i).isWaypoint())
                        {
-                               _currZoom = zoom;
-                               _maxZoom = zoom;
-                               getMapTiles(tx1, ty1);
-                               break;
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
+                               if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+                               {
+                                       g.fillRect(px-3, py-3, 6, 6);
+                                       pointsPainted++;
+                               }
                        }
                }
-               _gettingTiles = false;
-               repaint();
+               // Loop over points again, now draw names for waypoints
+               for (int i=0; i<_track.getNumPoints(); i++)
+               {
+                       if (_track.getPoint(i).isWaypoint())
+                       {
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
+                               if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+                               {
+                                       // Figure out where to draw waypoint name so it doesn't obscure track
+                                       String waypointName = _track.getPoint(i).getWaypointName();
+                                       int nameWidth = fm.stringWidth(waypointName);
+                                       boolean drawnName = false;
+                                       // Make arrays for coordinates right left up down
+                                       int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
+                                       int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
+                                       for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+                                       {
+                                               // Shift arrays for coordinates right left up down
+                                               nameXs[0] += 2; nameXs[1] -= 2;
+                                               nameYs[2] -= 2; nameYs[3] += 2;
+                                               // Check each direction in turn right left up down
+                                               for (int a=0; a<4; a++)
+                                               {
+                                                       if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < width
+                                                               && nameYs[a] < height && (nameYs[a] - nameHeight) > 0
+                                                               && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight))
+                                                       {
+                                                               // Found a rectangle to fit - draw name here and quit
+                                                               g.drawString(waypointName, nameXs[a], nameYs[a]);
+                                                               drawnName = true;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               // Loop over points, drawing blobs for photo points
+               g.setColor(COLOR_PHOTO_PT);
+               for (int i=0; i<_track.getNumPoints(); i++)
+               {
+                       if (_track.getPoint(i).getPhoto() != null)
+                       {
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
+                               if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+                               {
+                                       g.drawRect(px-1, py-1, 2, 2);
+                                       g.drawRect(px-2, py-2, 4, 4);
+                                       pointsPainted++;
+                               }
+                       }
+               }
+
+               // Draw selected range
+               if (_selection.hasRangeSelected())
+               {
+                       g.setColor(COLOR_CURR_RANGE);
+                       for (int i=_selection.getStart(); i<=_selection.getEnd(); i++)
+                       {
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
+                               g.drawRect(px-1, py-1, 2, 2);
+                       }
+               }
+
+               // Draw selected point, crosshairs
+               int selectedPoint = _selection.getCurrentPointIndex();
+               if (selectedPoint >= 0)
+               {
+                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(selectedPoint));
+                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(selectedPoint));
+                       g.setColor(COLOR_CROSSHAIRS);
+                       // crosshairs
+                       g.drawLine(px, 0, px, getHeight());
+                       g.drawLine(0, py, getWidth(), py);
+                       // oval
+                       g.drawOval(px - 2, py - 2, 4, 4);
+                       g.drawOval(px - 3, py - 3, 6, 6);
+               }
+
+               // free g
+               g.dispose();
+
+               _recalculate = false;
+               // Zoom to fit if no points found
+               if (pointsPainted <= 0 && _checkBounds) {
+                       zoomToFit();
+                       _recalculate = true;
+                       repaint();
+               }
+               _checkBounds = false;
+               // enable / disable transparency slider
+               _transparencySlider.setEnabled(_mapCheckBox.isSelected());
        }
 
+
        /**
-        * Get the map tiles for the current zoom level and given tile parameters
-        * @param inTileX x index of leftmost tile
-        * @param inTileY y index of lower tile
+        * Tests whether there are any dark pixels within the specified x,y rectangle
+        * @param inX left X coordinate
+        * @param inY bottom Y coordinate
+        * @param inWidth width of rectangle
+        * @param inHeight height of rectangle
+        * @return true if there's at least one data point in the rectangle
         */
-       private void getMapTiles(int inTileX, int inTileY)
+       private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
        {
-               // Check if tile parameters were given
-               if (inTileX == -1 || inTileY == -1) {
-                       double tileX = _xRange.getMinimum() * (1<<_currZoom);
-                       double tileY = _yRange.getMinimum() * (1<<_currZoom);
-                       inTileX = (int) Math.floor(tileX);
-                       inTileY = (int) Math.floor(tileY);
-                       // see if should be shifted by 1 to make more central
-                       if (_currZoom != _maxZoom) {
-                               if ((tileX - inTileX) < 0.5) inTileX--; // don't squash to left
-                               if ((tileY - inTileY) < 0.5) inTileY--; // don't squash too high
-                       }
-               }
+               // each of the colour channels must be brighter than this to count as empty
+               final int BRIGHTNESS_LIMIT = 210;
                try
                {
-                       ImageIcon[] icons = new ImageIcon[4];
-                       boolean loadingFailed = false;
-                       // Clear map
-                       Graphics g = _mapImage.getGraphics();
-                       g.clearRect(0, 0, 512, 512);
-                       for (int i=0; i<4 && !loadingFailed; i++)
-                       {
-                               String url = "http://tile.openstreetmap.org/" + _currZoom + "/" + (inTileX + i%2) + "/" + (inTileY + i/2) + ".png";
-                               icons[i] = new ImageIcon(new URL(url));
-                               if (icons[i] == null || icons[i].getImage() == null || icons[i].getImageLoadStatus() == MediaTracker.ERRORED)
+                       // loop over x coordinate of rectangle
+                       for (int x=0; x<inWidth; x++)
+                       {
+                               // loop over y coordinate of rectangle
+                               for (int y=0; y<inHeight; y++)
                                {
-                                       loadingFailed = true;
+                                       int pixelColor = _mapImage.getRGB(inX + x, inY - y);
+                                       // split into four components rgba
+                                       int lowestBit = pixelColor & 255;
+                                       int secondBit = (pixelColor >> 8) & 255;
+                                       int thirdBit = (pixelColor >> 16) & 255;
+                                       //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
+                                       if (lowestBit < BRIGHTNESS_LIMIT || secondBit < BRIGHTNESS_LIMIT || thirdBit < BRIGHTNESS_LIMIT) return true;
                                }
-                               g.drawImage(icons[i].getImage(), 256*(i%2), 256*(i/2), 256, 256, null);
-                       }
-                       // show message if loading failed
-                       if (loadingFailed) {
-                               JOptionPane.showMessageDialog(this,
-                                       I18nManager.getText("error.osmimage.failed"),
-                                       I18nManager.getText("error.osmimage.dialogtitle"),
-                                       JOptionPane.ERROR_MESSAGE);
-                       }
-                       // red rectangle
-                       int rectX1 = (int) (256 * ((_xRange.getMinimum() * (1<<_currZoom)) - inTileX));
-                       int rectX2 = (int) (256 * ((_xRange.getMaximum() * (1<<_currZoom)) - inTileX));
-                       int rectY1 = (int) (256 * ((_yRange.getMinimum() * (1<<_currZoom)) - inTileY));
-                       int rectY2 = (int) (256 * ((_yRange.getMaximum() * (1<<_currZoom)) - inTileY));
-                       g.setColor(Color.RED);
-                       g.drawRect(rectX1, rectY1, rectX2-rectX1, rectY2-rectY1);
-                       // draw points
-                       g.setColor(Color.BLUE);
-                       for (int i=0; i<_track.getNumPoints(); i++)
-                       {
-                               int px = (int) (256 * ((transformX(_track.getPoint(i).getLongitude().getDouble()) * (1<<_currZoom)) - inTileX));
-                               int py = (int) (256 * ((transformY(_track.getPoint(i).getLatitude().getDouble()) * (1<<_currZoom)) - inTileY));
-                               g.drawRect(px, py, 2, 2);
-                       }
-               }
-               catch (MalformedURLException urle) {
-                       _mapImage = null;
+                       }
+               }
+               catch (NullPointerException e) {
+                       // ignore null pointers, just return false
                }
+               return false;
        }
 
+
        /**
-        * Zoom out, if not already at minimum zoom
+        * Inform that tiles have been updated and the map can be repainted
+        * @param isOK true if data loaded ok, false for error
         */
-       public void zoomOut()
+       public synchronized void tilesUpdated(boolean inIsOk)
        {
-               if (_currZoom >= 2)
+               // Show message if loading failed (but not too many times)
+               if (!inIsOk && !_shownOsmErrorAlready)
                {
-                       _currZoom--;
-                       getMapTiles(-1, -1);
-                       repaint();
+                       _shownOsmErrorAlready = true;
+                       // use separate thread to show message about failing to load osm images
+                       new Thread(new Runnable() {
+                               public void run() {
+                                       try {Thread.sleep(500);} catch (InterruptedException ie) {}
+                                       JOptionPane.showMessageDialog(MapCanvas.this,
+                                               I18nManager.getText("error.osmimage.failed"),
+                                               I18nManager.getText("error.osmimage.dialogtitle"),
+                                               JOptionPane.ERROR_MESSAGE);
+                               }
+                       }).start();
                }
+               _recalculate = true;
+               repaint();
        }
 
        /**
-        * Zoom in, if not already at maximum zoom
+        * Zoom out, if not already at minimum zoom
         */
-       public void zoomIn()
+       public void zoomOut()
        {
-               if (_currZoom < _maxZoom)
-               {
-                       _currZoom++;
-                       getMapTiles(-1, -1);
-                       repaint();
-               }
+               _mapPosition.zoomOut();
+               _recalculate = true;
+               repaint();
        }
 
        /**
-        * Transform a longitude into an x coordinate
-        * @param inLon longitude in degrees
-        * @return scaled X value from 0 to 1
+        * Zoom in, if not already at maximum zoom
         */
-       private static double transformX(double inLon)
+       public void zoomIn()
        {
-               return (inLon + 180.0) / 360.0;
+               _mapPosition.zoomIn();
+               _recalculate = true;
+               repaint();
        }
 
        /**
-        * Transform a latitude into a y coordinate
-        * @param inLat latitude in degrees
-        * @return scaled Y value from 0 to 1
+        * Pan map
+        * @param inDeltaX x shift
+        * @param inDeltaY y shift
         */
-       private static double transformY(double inLat)
+       public void panMap(int inDeltaX, int inDeltaY)
        {
-               return (1 - Math.log(Math.tan(inLat * Math.PI / 180) + 1 / Math.cos(inLat * Math.PI / 180)) / Math.PI) / 2;
+               _mapPosition.pan(inDeltaX, inDeltaY);
+               _recalculate = true;
+               repaint();
        }
 
        /**
@@ -209,7 +661,7 @@ public class MapCanvas extends JPanel
         */
        public Dimension getMinimumSize()
        {
-               final Dimension minSize = new Dimension(512, 512);
+               final Dimension minSize = new Dimension(512, 300);
                return minSize;
        }
 
@@ -220,4 +672,217 @@ public class MapCanvas extends JPanel
        {
                return getMinimumSize();
        }
+
+
+       /**
+        * Respond to mouse click events
+        * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+        */
+       public void mouseClicked(MouseEvent inE)
+       {
+               if (_track != null && _track.getNumPoints() > 0)
+               {
+                        // select point if it's a left-click
+                       if (!inE.isMetaDown())
+                       {
+                               int pointIndex = _track.getNearestPointIndexNew(
+                                        _mapPosition.getXFromPixels(inE.getX(), getWidth()),
+                                        _mapPosition.getYFromPixels(inE.getY(), getHeight()),
+                                        _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
+                               _selection.selectPoint(pointIndex);
+                       }
+                       else
+                       {
+                               // show the popup menu for right-clicks
+                               _popupMenuX = inE.getX();
+                               _popupMenuY = inE.getY();
+                               _popup.show(this, _popupMenuX, _popupMenuY);
+                       }
+               }
+       }
+
+       /**
+        * Ignore mouse enter events
+        * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+        */
+       public void mouseEntered(MouseEvent inE)
+       {
+               // ignore
+       }
+
+       /**
+        * Ignore mouse exited events
+        * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+        */
+       public void mouseExited(MouseEvent inE)
+       {
+               // ignore
+       }
+
+       /**
+        * Ignore mouse pressed events
+        * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+        */
+       public void mousePressed(MouseEvent inE)
+       {
+               // ignore
+       }
+
+       /**
+        * Respond to mouse released events
+        * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+        */
+       public void mouseReleased(MouseEvent inE)
+       {
+               _recalculate = true;
+               if (_zoomDragging && 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());
+               }
+               _dragFromX = _dragFromY = -1;
+               _zoomDragging = false;
+               repaint();
+       }
+
+       /**
+        * Respond to mouse drag events
+        * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
+        */
+       public void mouseDragged(MouseEvent inE)
+       {
+               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();
+               }
+               else
+               {
+                       // Right-click and drag - draw rectangle and control zoom
+                       _zoomDragging = true;
+                       if (_dragFromX == -1) {
+                               _dragFromX = inE.getX();
+                               _dragFromY = inE.getY();
+                       }
+                       _dragToX = inE.getX();
+                       _dragToY = inE.getY();
+                       repaint();
+               }
+       }
+
+       /**
+        * Respond to mouse move events without button pressed
+        * @param inEvent ignored
+        */
+       public void mouseMoved(MouseEvent inEvent)
+       {
+               // ignore
+       }
+
+       /**
+        * Respond to status bar message from broker
+        * @param inMessage message, ignored
+        */
+       public void actionCompleted(String inMessage)
+       {
+               // ignore
+       }
+
+       /**
+        * Respond to data updated message from broker
+        * @param inUpdateType type of update
+        */
+       public void dataUpdated(byte inUpdateType)
+       {
+               _recalculate = true;
+               if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) {
+                       _checkBounds = true;
+               }
+               repaint();
+               // enable or disable components
+               boolean hasData = _track.getNumPoints() > 0;
+               _topPanel.setVisible(hasData);
+               _sidePanel.setVisible(hasData);
+               // grab focus for the key presses
+               this.requestFocus();
+       }
+
+       /**
+        * Respond to key presses on the map canvas
+        * @param inE key event
+        */
+       public void keyPressed(KeyEvent inE)
+       {
+               int code = inE.getKeyCode();
+               // Check for meta key
+               if (inE.isControlDown())
+               {
+                       // Check for arrow keys to zoom in and out
+                       if (code == KeyEvent.VK_UP)
+                               zoomIn();
+                       else if (code == KeyEvent.VK_DOWN)
+                               zoomOut();
+                       // Key nav for next/prev point
+                       else if (code == KeyEvent.VK_LEFT)
+                               _selection.selectPreviousPoint();
+                       else if (code == KeyEvent.VK_RIGHT)
+                               _selection.selectNextPoint();
+               }
+               else
+               {
+                       // Check for arrow keys to pan
+                       int upwardsPan = 0;
+                       if (code == KeyEvent.VK_UP)
+                               upwardsPan = -PAN_DISTANCE;
+                       else if (code == KeyEvent.VK_DOWN)
+                               upwardsPan = PAN_DISTANCE;
+                       int rightwardsPan = 0;
+                       if (code == KeyEvent.VK_RIGHT)
+                               rightwardsPan = PAN_DISTANCE;
+                       else if (code == KeyEvent.VK_LEFT)
+                               rightwardsPan = -PAN_DISTANCE;
+                       panMap(rightwardsPan, upwardsPan);
+                       // Check for delete key to delete current point
+                       if (code == KeyEvent.VK_DELETE && _selection.getCurrentPointIndex() >= 0)
+                       {
+                               _app.deleteCurrentPoint();
+                       }
+               }
+       }
+
+       /**
+        * @param inE key released event, ignored
+        */
+       public void keyReleased(KeyEvent e)
+       {
+               // ignore
+       }
+
+       /**
+        * @param inE key typed event, ignored
+        */
+       public void keyTyped(KeyEvent inE)
+       {
+               // ignore
+       }
+
+       /**
+        * @param inE mouse wheel event indicating scroll direction
+        */
+       public void mouseWheelMoved(MouseWheelEvent inE)
+       {
+               int clicks = inE.getWheelRotation();
+               if (clicks < 0)
+                       zoomIn();
+               else if (clicks > 0)
+                       zoomOut();
+       }
 }
diff --git a/tim/prune/gui/map/MapPosition.java b/tim/prune/gui/map/MapPosition.java
new file mode 100644 (file)
index 0000000..73c7ed9
--- /dev/null
@@ -0,0 +1,272 @@
+package tim.prune.gui.map;
+
+/**
+ * Class to hold the current position of the map
+ */
+public class MapPosition
+{
+       /** Width and height of each tile of map */
+       private static final int MAP_TILE_SIZE = 256;
+
+       /** x position (scale depends on zoom) */
+       private long _xPosition = 0L;
+       /** y position (scale depends on zoom) */
+       private long _yPosition = 0L;
+
+       /** Zoom level, from 2 to 15 */
+       private int _zoom = 12;
+       /** Factor to zoom by, 2 to the power of zoom */
+       private int _zoomFactor = 1 << _zoom;
+
+
+       /**
+        * Zoom and pan to show the selected area
+        * @param inMinX minimum transformed X
+        * @param inMaxX maximum transformed X
+        * @param inMinY minimum transformed Y
+        * @param inMaxY maximum transformed Y
+        * @param inWidth width of display
+        * @param inHeight height of display
+        */
+       public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight)
+       {
+               // System.out.println("Zooming to " + inMinX + ", " + inMaxX + ", " + inMinY + ", " + inMaxY + "; width=" + inWidth + ", height=" + inHeight);
+               double diffX = Math.abs(inMaxX - inMinX);
+               double diffY = Math.abs(inMaxY - inMinY);
+               // Find out what zoom level to go to
+               int requiredZoom = -1;
+               for (int currZoom = 15; currZoom >= 2; currZoom--)
+               {
+                       if (transformToPixels(diffX, currZoom) < inWidth
+                               && transformToPixels(diffY, currZoom) < inHeight)
+                       {
+                               requiredZoom = currZoom;
+                               break;
+                       }
+               }
+               if (requiredZoom < 2) requiredZoom = 2;
+               // Set position
+               _zoom = requiredZoom;
+               _zoomFactor = 1 << _zoom;
+               _xPosition = transformToPixels((inMinX + inMaxX) / 2.0);
+               _yPosition = transformToPixels((inMinY + inMaxY) / 2.0);
+       }
+
+       /**
+        * Zoom and pan to show the selected area
+        * @param inMinX minimum pixels X
+        * @param inMaxX maximum pixels X
+        * @param inMinY minimum pixels Y
+        * @param inMaxY maximum pixels Y
+        * @param inWidth width of display
+        * @param inHeight height of display
+        */
+       public void zoomToPixels(int inMinX, int inMaxX, int inMinY, int inMaxY, int inWidth, int inHeight)
+       {
+               // System.out.println("Current position is " + _xPosition + ", " + _yPosition);
+               int diffX = Math.abs(inMaxX - inMinX);
+               int diffY = Math.abs(inMaxY - inMinY);
+               // Find out what zoom level to go to
+               int requiredZoom = -1;
+               int multFactor = 0;
+               for (int currZoom = 16; currZoom >= _zoom; currZoom--)
+               {
+                       multFactor = 1 << (currZoom - _zoom);
+                       if ((diffX * multFactor) < inWidth && (diffY * multFactor) < inHeight)
+                       {
+                               requiredZoom = currZoom;
+                               break;
+                       }
+               }
+               _zoom = requiredZoom;
+               _zoomFactor = 1 << _zoom;
+               // Set position
+               _xPosition = (_xPosition - inWidth/2 + (inMinX + inMaxX) / 2) * multFactor;
+               _yPosition = (_yPosition - inHeight/2 + (inMinY + inMaxY) / 2) * multFactor;
+       }
+
+       /**
+        * Transform a given coordinate into pixels using the current zoom value
+        * @param inValue value to transform
+        * @return pixels
+        */
+       private long transformToPixels(double inValue)
+       {
+               return transformToPixels(inValue, _zoom);
+       }
+
+       /**
+        * Transform a given coordinate into pixels using the specified zoom value
+        * @param inValue value to transform
+        * @param inZoom zoom value to use
+        * @return pixels
+        */
+       private static long transformToPixels(double inValue, int inZoom)
+       {
+               return (long) (inValue * MAP_TILE_SIZE * (1 << inZoom));
+       }
+
+       /**
+        * Convert pixels back into x coordinates
+        * @param inPixelX x coordinate on screen
+        * @param inWidth current width of window
+        * @return x coordinate
+        */
+       public double getXFromPixels(int inPixelX, int inWidth)
+       {
+               return ((inPixelX - inWidth/2) + _xPosition) * 1.0 / MAP_TILE_SIZE / _zoomFactor;
+       }
+
+       /**
+        * Convert pixels back into y coordinates
+        * @param inPixelY y coordinate on screen
+        * @param inHeight current height of window
+        * @return y coordinate
+        */
+       public double getYFromPixels(int inPixelY, int inHeight)
+       {
+               return ((inPixelY - inHeight/2) + _yPosition) * 1.0 / MAP_TILE_SIZE / _zoomFactor;
+       }
+
+       /**
+        * Get the horizontal offset from the centre
+        * @param inValue value to transform
+        * @return number of pixels right (+ve) or left (-ve) from the centre
+        */
+       public int getXFromCentre(double inValue)
+       {
+               return (int) (transformToPixels(inValue) - _xPosition);
+       }
+
+       /**
+        * Get the vertical offset from the centre
+        * @param inValue value to transform
+        * @return number of pixels up (+ve) or down (-ve) from the centre
+        */
+       public int getYFromCentre(double inValue)
+       {
+               return (int) (transformToPixels(inValue) - _yPosition);
+       }
+
+       /**
+        * Convert a pixel value into a bounds value for sensitivity
+        * @param inPixels number of pixels
+        * @return bounds value to use for x,y checking
+        */
+       public double getBoundsFromPixels(int inPixels) {
+               return (inPixels * 1.0 / MAP_TILE_SIZE / _zoomFactor);
+       }
+
+       /**
+        * Get the leftmost, rightmost, upper and lower index boundaries for the tiles to display
+        * @param inWidth width of window
+        * @param inHeight height of window
+        * @return tile indices as array left, right, up, down
+        */
+       public int[] getTileIndices(int inWidth, int inHeight)
+       {
+               int[] result = new int[4];
+               result[0] = getTileIndex(_xPosition - inWidth/2);
+               result[1] = getTileIndex(_xPosition + inWidth/2);
+               result[2] = getTileIndex(_yPosition - inHeight/2);
+               result[3] = getTileIndex(_yPosition + inHeight/2);
+               return result;
+       }
+
+       /**
+        * Get the pixel offsets for the display
+        * @param inWidth width of window
+        * @param inHeight height of window
+        * @return offsets as x, y
+        */
+       public int[] getDisplayOffsets(int inWidth, int inHeight)
+       {
+               int[] result = new int[2];
+               result[0] = getDisplayOffset(_xPosition - inWidth/2);
+               result[1] = getDisplayOffset(_yPosition - inHeight/2);
+               return result;
+       }
+
+       /**
+        * @return x index of the centre tile
+        */
+       public int getCentreTileX()
+       {
+               return getTileIndex(_xPosition);
+       }
+
+       /**
+        * @return y index of the centre tile
+        */
+       public int getCentreTileY()
+       {
+               return getTileIndex(_yPosition);
+       }
+
+       /**
+        * @param inPosition position of point
+        * @return tile index for that point
+        */
+       private int getTileIndex(long inPosition)
+       {
+               return (int) (inPosition / MAP_TILE_SIZE);
+       }
+
+       /**
+        * @param inPosition position of point
+        * @return pixel offset for that point
+        */
+       private int getDisplayOffset(long inPosition)
+       {
+               return (int) (inPosition % MAP_TILE_SIZE);
+               // Maybe >> 8 would be slightly faster?
+       }
+
+       /**
+        * Zoom in one level
+        */
+       public void zoomIn()
+       {
+               if (_zoom < 16)
+               {
+                       _zoom++;
+                       _zoomFactor = 1 << _zoom;
+                       _xPosition *= 2;
+                       _yPosition *= 2;
+               }
+       }
+
+       /**
+        * Zoom out one level
+        */
+       public void zoomOut()
+       {
+               if (_zoom >= 2)
+               {
+                       _zoom--;
+                       _zoomFactor = 1 << _zoom;
+                       _xPosition /= 2;
+                       _yPosition /= 2;
+               }
+       }
+
+       /**
+        * @return current zoom level
+        */
+       public int getZoom()
+       {
+               return _zoom;
+       }
+
+       /**
+        * Pan map by the specified amount
+        * @param inDeltaX amount to pan right
+        * @param inDeltaY amount to pan down
+        */
+       public void pan(int inDeltaX, int inDeltaY)
+       {
+               // TODO: Check bounds?
+               _xPosition += inDeltaX;
+               _yPosition += inDeltaY;
+       }
+}
diff --git a/tim/prune/gui/map/MapTileCacher.java b/tim/prune/gui/map/MapTileCacher.java
new file mode 100644 (file)
index 0000000..50387dd
--- /dev/null
@@ -0,0 +1,196 @@
+package tim.prune.gui.map;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ * Class to handle the caching of map tiles from openstreetmap
+ */
+public class MapTileCacher implements ImageObserver
+{
+       /** Parent to be informed of updates */
+       private MapCanvas _parent = null;
+       /** Default grid size */
+       private static final int GRID_SIZE = 11;
+       /** Array of images to hold tiles */
+       private Image[] _tiles = new Image[GRID_SIZE * GRID_SIZE];
+       /** Current zoom level */
+       private int _zoom = -1;
+       /** X coordinate of central tile */
+       private int _tileX = -1;
+       /** Y coordinate of central tile */
+       private int _tileY = -1;
+       /** X coord of grid centre */
+       private int _gridCentreX = 0;
+       /** Y coord of grid centre */
+       private int _gridCentreY = 0;
+
+
+       /**
+        * Constructor
+        * @param inParent parent canvas to be informed of updates
+        */
+       public MapTileCacher(MapCanvas inParent)
+       {
+               _parent = inParent;
+       }
+
+       /**
+        * Recentre the map and clear the cache
+        * @param inZoom zoom level
+        * @param inTileX x coord of central tile
+        * @param inTileY y coord of central tile
+        */
+       public void centreMap(int inZoom, int inTileX, int inTileY)
+       {
+               if (inZoom != _zoom)
+               {
+                       _zoom = inZoom;
+                       clearAll();
+               }
+               _gridCentreX = getCacheCoordinate(_gridCentreX + inTileX - _tileX);
+               _gridCentreY = getCacheCoordinate(_gridCentreY + inTileY - _tileY);
+               _tileX = inTileX;
+               _tileY = inTileY;
+               // Mark boundaries as invalid
+               for (int i=0; i<GRID_SIZE; i++)
+               {
+                       _tiles[getArrayIndex(_tileX + GRID_SIZE/2 + 1, _tileY + i - GRID_SIZE/2)] = null;
+                       _tiles[getArrayIndex(_tileX + i - GRID_SIZE/2, _tileY + GRID_SIZE/2 + 1)] = null;
+               }
+       }
+
+       /**
+        * Clear all the cached images
+        */
+       public void clearAll()
+       {
+               // Clear all images if zoom changed
+               for (int i=0; i<_tiles.length; i++) {
+                       _tiles[i] = null;
+               }
+       }
+
+       /**
+        * @param inX x index of tile
+        * @param inY y index of tile
+        * @return selected tile if already loaded, or null otherwise
+        */
+       public Image getTile(int inX, int inY)
+       {
+               int arrayIndex = getArrayIndex(inX, inY);
+               Image image = _tiles[arrayIndex];
+               if (image != null)
+               {
+                       // image already finished loading so return it
+                       return image;
+               }
+
+               // Trigger load if not already triggered
+               // Work out tile coords for URL
+               int urlX = getUrlCoordinate(inX, _zoom);
+               int urlY = getUrlCoordinate(inY, _zoom);
+               try
+               {
+                       String url = "http://tile.openstreetmap.org/" + _zoom + "/" + urlX + "/" + urlY + ".png";
+                       // Load image asynchronously, using observer
+                       image = Toolkit.getDefaultToolkit().createImage(new URL(url));
+                       _tiles[arrayIndex] = image;
+                       if (image.getWidth(this) > 0) {return image;}
+               }
+               catch (MalformedURLException urle) {} // ignore
+               return null;
+       }
+
+
+       /**
+        * Get the array index for the given coordinates
+        * @param inX x coord of tile
+        * @param inY y coord of tile
+        * @return array index
+        */
+       private int getArrayIndex(int inX, int inY)
+       {
+               //System.out.println("Getting array index for (" + inX + ", " + inY + ") where the centre is at ("  + _tileX + ", " + _tileY
+               //      + ") and grid coords (" + _gridCentreX + ", " + _gridCentreY + ")");
+               int x = getCacheCoordinate(inX - _tileX + _gridCentreX);
+               int y = getCacheCoordinate(inY - _tileY + _gridCentreY);
+               //System.out.println("Transformed to (" + x + ", " + y + ")");
+               return (x + y * GRID_SIZE);
+       }
+
+       /**
+        * Transform a coordinate from map tiles to array coordinates
+        * @param inTile coordinate of tile
+        * @return coordinate in array (wrapping around cache grid)
+        */
+       private static int getCacheCoordinate(int inTile)
+       {
+               int tile = inTile;
+               while (tile >= GRID_SIZE) {tile -= GRID_SIZE;}
+               while (tile < 0) {tile += GRID_SIZE;}
+               return tile;
+       }
+
+       /**
+        * Make sure a url coordinate is within range
+        * @param inTile coordinate of tile in map system
+        * @param inZoom zoom factor
+        * @return coordinate for url (either vertical or horizontal)
+        */
+       private static int getUrlCoordinate(int inTile, int inZoom)
+       {
+               int mapSize = 1 << inZoom;
+               int coord = inTile;
+               while (coord >= mapSize) {coord -= mapSize;}
+               while (coord < 0) {coord += mapSize;}
+               // coord is now between 0 and mapsize
+               return coord;
+       }
+
+       /**
+        * Convert to string for debug
+        * @see java.lang.Object#toString()
+        */
+       public String toString()
+       {
+               StringBuffer result = new StringBuffer("Grid centre (" + _gridCentreX + "," + _gridCentreY + ") - (" + _tileX + "," + _tileY + ")\n");
+               for (int i=0; i<GRID_SIZE; i++)
+               {
+                       for (int j=0; j<GRID_SIZE; j++) {
+                               if (i == _gridCentreY && j == _gridCentreX) {
+                                       result.append(_tiles[j + i*GRID_SIZE] == null?"c":"C");
+                               }
+                               else {
+                                       result.append(_tiles[j + i*GRID_SIZE] == null?".":"*");
+                               }
+                       }
+                       result.append("\n");
+               }
+               return result.toString();
+       }
+
+       /**
+        * Method called by image loader to inform of updates to the tiles
+        * @param img the image
+        * @param infoflags flags describing how much of the image is known
+        * @param x ignored
+        * @param y ignored
+        * @param width ignored
+        * @param height ignored
+        * @return false to carry on loading, true to stop
+        */
+       public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
+       {
+               boolean loaded = (infoflags & ImageObserver.ALLBITS) > 0;
+               boolean error = (infoflags & ImageObserver.ERROR) > 0;
+               if (loaded || error) {
+                       _parent.tilesUpdated(loaded);
+               }
+               return !loaded;
+       }
+}
diff --git a/tim/prune/gui/map/MapUtils.java b/tim/prune/gui/map/MapUtils.java
new file mode 100644 (file)
index 0000000..0c6cd1c
--- /dev/null
@@ -0,0 +1,48 @@
+package tim.prune.gui.map;
+
+/**
+ * Class to manage coordinate conversions for maps
+ */
+public abstract class MapUtils
+{
+       /**
+        * Transform a longitude into an x coordinate
+        * @param inLon longitude in degrees
+        * @return scaled X value from 0 to 1
+        */
+       public static double getXFromLongitude(double inLon)
+       {
+               return (inLon + 180.0) / 360.0;
+       }
+
+       /**
+        * Transform a latitude into a y coordinate
+        * @param inLat latitude in degrees
+        * @return scaled Y value from 0 to 1
+        */
+       public static double getYFromLatitude(double inLat)
+       {
+               return (1 - Math.log(Math.tan(inLat * Math.PI / 180) + 1 / Math.cos(inLat * Math.PI / 180)) / Math.PI) / 2;
+       }
+
+       /**
+        * Transform an x coordinate into a longitude
+        * @param inX scaled X value from 0 to 1
+        * @return longitude in degrees
+        */
+       public static double getLongitudeFromX(double inX)
+       {
+               return inX * 360.0 - 180.0;
+       }
+
+       /**
+        * Transform a y coordinate into a latitude
+        * @param inY scaled Y value from 0 to 1
+        * @return latitude in degrees
+        */
+       public static double getLatitudeFromY(double inY)
+       {
+               double n = Math.PI * (1 - 2 * inY);
+               return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
+       }
+}
diff --git a/tim/prune/gui/map/MapWindow.java b/tim/prune/gui/map/MapWindow.java
deleted file mode 100644 (file)
index f47414a..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-package tim.prune.gui.map;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-
-import tim.prune.I18nManager;
-import tim.prune.data.Track;
-
-/**
- * Class to hold the gui functions of the map window
- */
-public class MapWindow extends JFrame
-{
-       private MapCanvas _canvas = null;
-
-       /**
-        * Constructor
-        * @param inTrack track object
-        */
-       public MapWindow(Track inTrack)
-       {
-               super(I18nManager.getText("dialog.map.title"));
-               getContentPane().add(createComponents(inTrack));
-               setResizable(false);
-       }
-
-       /**
-        * @param inTrack track object
-        * @return gui components
-        */
-       private Component createComponents(Track inTrack)
-       {
-               JPanel panel = new JPanel();
-               panel.setLayout(new BorderLayout());
-               _canvas = new MapCanvas(inTrack);
-               panel.add(_canvas, BorderLayout.CENTER);
-               // Make panel for zoom buttons
-               JPanel buttonPanel = new JPanel();
-               JButton zoomInButton = new JButton(I18nManager.getText("menu.map.zoomin"));
-               zoomInButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _canvas.zoomIn();
-                       }
-               });
-               buttonPanel.add(zoomInButton);
-               JButton zoomOutButton = new JButton(I18nManager.getText("menu.map.zoomout"));
-               zoomOutButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _canvas.zoomOut();
-                       }
-               });
-               buttonPanel.add(zoomOutButton);
-               panel.add(buttonPanel, BorderLayout.SOUTH);
-               return panel;
-       }
-}
index d1861356e5e9e2e2f99a2ff6b733e4fc48f12ad0..53a65b026246171911219cd9637ae3acd640302a 100644 (file)
@@ -3,8 +3,9 @@
 
 # Menu entries
 menu.file=File
-menu.file.open=Open
+menu.file.open=Open file
 menu.file.addphotos=Add photos
+menu.file.loadfromgps=Load from GPS
 menu.file.save=Save
 menu.file.exportkml=Export KML
 menu.file.exportgpx=Export GPX
@@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Delete duplicates
 menu.edit.compress=Compress track
 menu.edit.interpolate=Interpolate
 menu.edit.reverse=Reverse range
+menu.edit.addtimeoffset=Add time offset
 menu.edit.mergetracksegments=Merge track segments
 menu.edit.rearrange=Rearrange waypoints
 menu.edit.rearrange.start=All to start of file
 menu.edit.rearrange.end=All to end of file
 menu.edit.rearrange.nearest=Each to nearest track point
+menu.edit.cutandmove=Cut and move selection
 menu.select=Select
 menu.select.all=Select all
 menu.select.none=Select none
@@ -39,18 +42,20 @@ menu.photo.correlate=Correlate all photos
 menu.photo.delete=Remove photo
 menu.view=View
 menu.view.show3d=Show in three-D
-menu.view.showmap=Show map
 menu.view.browser=Map in browser
 menu.view.browser.google=Google maps
 menu.view.browser.openstreetmap=Openstreetmap
 menu.help=Help
 menu.help.about=About Prune
+menu.help.checkversion=Check for new version
 # Popup menu for map
 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.connect=Connect track points
 menu.map.autopan=Autopan
+menu.map.showmap=Show map
 
 # Dialogs
 dialog.exit.confirm.title=Exit Prune
@@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extract of file
 dialog.openoptions.altitudeunits=Altitude units
 dialog.jpegload.subdirectories=Include subdirectories
 dialog.jpegload.loadjpegswithoutcoords=Include photos without coordinates
+dialog.jpegload.loadjpegsoutsidearea=Include photos outside current area
 dialog.jpegload.progress.title=Loading photos
 dialog.jpegload.progress=Please wait while the photos are searched
+dialog.gpsload.title=Load from GPS
+dialog.gpsload.nogpsbabel=No gpsbabel program could be found. Continue?
+dialog.gpsload.device=Device name
+dialog.gpsload.format=Format
+dialog.gpsload.getwaypoints=Load waypoints
+dialog.gpsload.gettracks=Load tracks
 dialog.saveoptions.title=Save file
 dialog.save.fieldstosave=Fields to save
 dialog.save.table.field=Field
@@ -102,21 +114,24 @@ dialog.exportkml.text=Title for the data
 dialog.exportkml.altitude=Include altitudes (for aviation)
 dialog.exportkml.kmz=Compress to make kmz file
 dialog.exportkml.exportimages=Export image thumbnails to kmz
-dialog.exportkml.filetype=KML, KMZ files
 dialog.exportgpx.title=Export GPX
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Description
-dialog.exportgpx.filetype=GPX files
+dialog.exportgpx.includetimestamps=Include timestamps
 dialog.exportpov.title=Export POV
 dialog.exportpov.text=Please enter the parameters for the POV export
 dialog.exportpov.font=Font
 dialog.exportpov.camerax=Camera X
 dialog.exportpov.cameray=Camera Y
 dialog.exportpov.cameraz=Camera Z
-dialog.exportpov.filetype=POV files
+dialog.exportpov.modelstyle=Model style
+dialog.exportpov.ballsandsticks=Balls and sticks
+dialog.exportpov.tubesandwalls=Tubes and walls
 dialog.exportpov.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue?
 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?
+dialog.confirmcutandmove.title=Confirm cut and move
+dialog.confirmcutandmove.text=This track contains timestamp information, which will be out of sequence after a move.\nAre you sure you want to move this section?
 dialog.interpolate.title=Interpolate points
 dialog.interpolate.parameter.text=Number of points to insert between selected points
 dialog.undo.title=Undo action(s)
@@ -136,7 +151,16 @@ dialog.pointnameedit.title=Edit waypoint name
 dialog.pointnameedit.name=Waypoint name
 dialog.pointnameedit.uppercase=UPPER case
 dialog.pointnameedit.lowercase=lower case
-dialog.pointnameedit.sentencecase=Sentence case
+dialog.pointnameedit.sentencecase=Sentence Case
+dialog.addtimeoffset.title=Add time offset
+dialog.addtimeoffset.add=Add time
+dialog.addtimeoffset.subtract=Subtract time
+dialog.addtimeoffset.days=Days
+dialog.addtimeoffset.hours=Hours
+dialog.addtimeoffset.minutes=Minutes
+dialog.addtimeoffset.notimestamps=Cannot add a time offset as this selection doesn't contain any timestamp information
+dialog.connect.title=Connect photo to point
+dialog.connectphoto.clonepoint=This point already has a photo.\nDo you want to make a copy of the point?
 dialog.saveexif.title=Save Exif
 dialog.saveexif.intro=Select the photos to save using the checkboxes
 dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save
@@ -171,7 +195,6 @@ 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.map.title=Prune map
 dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\nfor more information and user guides.
 dialog.about.title=About Prune
 dialog.about.version=Version
@@ -179,6 +202,7 @@ dialog.about.build=Build
 dialog.about.summarytext1=Prune is a program for loading, displaying and editing data from GPS receivers.
 dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.<br>Copying, redistribution and modification are permitted and encouraged<br>according to the conditions in the included <code>license.txt</code> file.
 dialog.about.summarytext3=Please see <code style="font-weight:bold">http://activityworkshop.net/</code> for more information and user guides.
+dialog.about.languages=Available languages
 dialog.about.translatedby=English text by activityworkshop.
 dialog.about.systeminfo=System info
 dialog.about.systeminfo.os=Operating System
@@ -199,6 +223,14 @@ dialog.about.credits.devtools=Development tools
 dialog.about.credits.othertools=Other tools
 dialog.about.credits.thanks=Thanks to
 dialog.about.readme=Readme
+dialog.checkversion.title=Check version
+dialog.checkversion.error=The version number couldn't be checked.\nPlease check the internet connection.
+dialog.checkversion.uptodate=You are using the latest version of Prune.
+dialog.checkversion.newversion1=A new version of Prune is now available!  The latest version is now version
+dialog.checkversion.newversion2=.
+dialog.checkversion.releasedate1=This new version was released on
+dialog.checkversion.releasedate2=.
+dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html.
 
 # 3d window
 dialog.3d.title=Prune Three-d view
@@ -216,8 +248,11 @@ confirm.deleteduplicates.multi=duplicates were deleted
 confirm.deletepoint.single=data point was removed
 confirm.deletepoint.multi=data points were removed
 confirm.point.edit=point edited
-confirm.mergetracksegments=track segments merged
+confirm.mergetracksegments=Track segments merged
 confirm.reverserange=Range reversed
+confirm.addtimeoffset=Time offset added
+confirm.rearrangewaypoints=Waypoints rearranged
+confirm.cutandmove=Selection moved
 confirm.saveexif.ok1=Saved
 confirm.saveexif.ok2=photo files
 confirm.undo.single=operation undone
@@ -228,6 +263,7 @@ confirm.photo.connect=photo connected
 confirm.photo.disconnect=photo disconnected
 confirm.correlate.single=photo was correlated
 confirm.correlate.multi=photos were correlated
+confirm.createpoint=point created
 
 # Buttons
 button.ok=OK
@@ -251,6 +287,15 @@ button.selectall=Select all
 button.selectnone=Select none
 button.preview=Preview
 button.guessfields=Guess fields
+button.showwebpage=Show webpage
+
+# File types
+filetype.txt=TXT files
+filetype.jpeg=JPG files
+filetype.kmlkmz=KML, KMZ files
+filetype.kml=KML files
+filetype.gpx=GPX files
+filetype.pov=POV files
 
 # Display components
 display.nodata=No data loaded
@@ -280,6 +325,7 @@ display.range.time.mins=m
 display.range.time.hours=h
 display.range.time.days=d
 details.range.avespeed=Ave speed
+details.range.avemovingspeed=Moving ave
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Photos
 details.photodetails=Photo details
@@ -291,13 +337,14 @@ details.photo.connected=Connected
 fieldname.latitude=Latitude
 fieldname.longitude=Longitude
 fieldname.altitude=Altitude
-fieldname.timestamp=Timestamp
+fieldname.timestamp=Time
 fieldname.waypointname=Name
 fieldname.waypointtype=Type
 fieldname.newsegment=Segment
 fieldname.custom=Custom
 fieldname.prefix=Field
 fieldname.distance=Distance
+fieldname.movingdistance=Moving distance
 fieldname.duration=Duration
 
 # Measurement units
@@ -339,10 +386,13 @@ undo.insert=insert points
 undo.deleteduplicates=delete duplicates
 undo.reverse=reverse range
 undo.mergetracksegments=merge track segments
+undo.addtimeoffset=add time offset
 undo.rearrangewaypoints=rearrange waypoints
+undo.cutandmove=move section
 undo.connectphoto=connect photo
 undo.disconnectphoto=disconnect photo
 undo.correlate=correlate photos
+undo.createpoint=create point
 
 # Error messages
 error.save.dialogtitle=Error saving data
index 1f8b0d155c3703cf145abd17f97bfe9b7b30bd7a..d01a8724452b55bd8648008b7ca154870028b869 100644 (file)
@@ -3,34 +3,37 @@
 
 # Menu entries
 menu.file=Datei
-menu.file.open=Öffnen
+menu.file.open=Datei Ã¶ffnen
 menu.file.addphotos=Fotos laden
+menu.file.loadfromgps=Vom GPS laden
 menu.file.save=Speichern
 menu.file.exportkml=KML exportieren
 menu.file.exportgpx=GPX exportieren
 menu.file.exportpov=POV exportieren
 menu.file.exit=Beenden
 menu.edit=Bearbeiten
-menu.edit.undo=Undo
-menu.edit.clearundo=Undo-Liste löschen
+menu.edit.undo=Rückgängig
+menu.edit.clearundo=Liste der letzten Ã„nderungen löschen
 menu.edit.editpoint=Punkt bearbeiten
 menu.edit.editwaypointname=Waypoint Name bearbeiten
 menu.edit.deletepoint=Punkt löschen
-menu.edit.deleterange=Spanne löschen
+menu.edit.deleterange=Bereich löschen
 menu.edit.deleteduplicates=Duplikate löschen
 menu.edit.compress=Track komprimieren
 menu.edit.interpolate=Interpolieren
-menu.edit.reverse=Spanne umkehren
-menu.edit.mergetracksegments=Trackteilen verbinden
+menu.edit.reverse=Bereich umkehren
+menu.edit.addtimeoffset=Zeitdifferenz addieren
+menu.edit.mergetracksegments=Trackteile verbinden
 menu.edit.rearrange=Waypoints reorganisieren
-menu.edit.rearrange.start=Alle zum Anfang
-menu.edit.rearrange.end=Alle zum Ende
-menu.edit.rearrange.nearest=Jeder zum nächsten Trackpunkt
+menu.edit.rearrange.start=Alle Waypoints zum Anfang
+menu.edit.rearrange.end=Alle Waypoints zum Ende
+menu.edit.rearrange.nearest=Jeden Waypoint zum nächsten Trackpunkt verschieben
+menu.edit.cutandmove=Schneiden und verschieben
 menu.select=Markieren
 menu.select.all=Alles markieren
 menu.select.none=Nichts markieren
-menu.select.start=Start setzen
-menu.select.end=Stopp setzen
+menu.select.start=Startpunkt setzen
+menu.select.end=Endpunkt setzen
 menu.photo=Foto
 menu.photo.saveexif=Exif Daten speichern
 menu.photo.connect=Mit Punkt verbinden
@@ -38,35 +41,37 @@ menu.photo.disconnect=Vom Punkt trennen
 menu.photo.correlate=Alle Fotos korrelieren
 menu.photo.delete=Foto entfernen
 menu.view=Ansicht
-menu.view.show3d=In drei-D zeigen
-menu.view.showmap=Karte zeigen
+menu.view.show3d=In 3D zeigen
 menu.view.browser=Karte in Browser
 menu.view.browser.google=Google Maps
 menu.view.browser.openstreetmap=Openstreetmap
 menu.help=Hilfe
 menu.help.about=Ãœber Prune
+menu.help.checkversion=Nach neuen Versionen suchen
 # Popup menu for map
-menu.map.zoomin=Einzoomen
-menu.map.zoomout=Auszoomen
-menu.map.zoomfull=Zoomen zum ganzes Bild
-menu.map.connect=Trackpunkte mit Linie
-menu.map.autopan=Autopan
+menu.map.zoomin=Hineinzoomen
+menu.map.zoomout=Herauszoomen
+menu.map.zoomfull=Auf Bildschirmgröße anpassen
+menu.map.newpoint=Neuen Punkt kreieren
+menu.map.connect=Trackpunkte mit Linie anzeigen
+menu.map.autopan=Autozentrierung
+menu.map.showmap=Karte zeigen
 
 # Dialogs
 dialog.exit.confirm.title=Prune beenden
-dialog.exit.confirm.text=Ihre Daten wurden nicht gespeichert. Wollen Sie trotzdem das Programm beenden?
-dialog.openappend.title=Daten anhängen oder ersetzen
-dialog.openappend.text=Häng diese Daten zu den aktuellen Daten an?
+dialog.exit.confirm.text=Ihre Daten wurden nicht gespeichert. Wollen Sie das Programm trotzdem beenden?
+dialog.openappend.title=an existierende Daten anhängen oder ersetzen
+dialog.openappend.text=Diese Daten an die aktuellen Daten anhängen?
 dialog.deletepoint.title=Punkt löschen
-dialog.deletepoint.deletephoto=Foto von diesem Punkt auch löschen?
-dialog.deletephoto.title=Photo entfernen
-dialog.deletephoto.deletepoint=Punkt von diesem Foto auch löschen?
+dialog.deletepoint.deletephoto=Das zu diesem Punkt gehörende Foto ebenfalls löschen?
+dialog.deletephoto.title=Foto entfernen
+dialog.deletephoto.deletepoint=Den zu diesem Foto gehörenden Punkt auch löschen?
 dialog.deleteduplicates.title=Duplikate löschen
 dialog.deleteduplicates.nonefound=Keine Duplikate gefunden
 dialog.compresstrack.title=Track komprimieren
-dialog.compresstrack.parameter.text=Parameter für Komprimierung (niedriger Nummer = höher Komprimierung)
-dialog.compresstrack.nonefound=Keine Punkte konnten entfernt werden
-dialog.openoptions.title=Öffnen Optionen
+dialog.compresstrack.parameter.text=Parameter für Komprimierung (niedrige Nummer = höhere Komprimierung)
+dialog.compresstrack.nonefound=Es konnten keine Punkte entfernt werden
+dialog.openoptions.title=Öffnen
 dialog.openoptions.filesnippet=Extrakt von der Datei
 dialog.load.table.field=Feld
 dialog.load.table.datatype=Daten Typ
@@ -74,53 +79,63 @@ dialog.load.table.description=Beschreibung
 dialog.delimiter.label=Feld Trennzeichen
 dialog.delimiter.comma=Komma ,
 dialog.delimiter.tab=Tab
-dialog.delimiter.space=Abstand
+dialog.delimiter.space=Leerstelle
 dialog.delimiter.semicolon=Strichpunkt ;
 dialog.delimiter.other=Andere
-dialog.openoptions.deliminfo.records=Rekords, mit
+dialog.openoptions.deliminfo.records=Aufnahmen, mit
 dialog.openoptions.deliminfo.fields=Feldern
 dialog.openoptions.deliminfo.norecords=Keine Rekords
 dialog.openoptions.tabledesc=Extrakt von der Datei
 dialog.openoptions.altitudeunits=Höhe Maßeinheiten
-dialog.jpegload.subdirectories=Subordnern auch durchsuchen
+dialog.jpegload.subdirectories=Unterordner auch durchsuchen
 dialog.jpegload.loadjpegswithoutcoords=Auch Fotos ohne Koordinaten laden
+dialog.jpegload.loadjpegsoutsidearea=Auch Fotos ausserhalb vom Track laden
 dialog.jpegload.progress.title=Fotos werden geladen
 dialog.jpegload.progress=Bitte warten während die Fotos durchgesucht werden
+dialog.gpsload.title=Vom GPS laden
+dialog.gpsload.nogpsbabel=Kein gpsbabel Programm wurde gefunden. Weiter?
+dialog.gpsload.device=Device Name
+dialog.gpsload.format=Format
+dialog.gpsload.getwaypoints=Waypoints laden
+dialog.gpsload.gettracks=Tracks laden
 dialog.saveoptions.title=Datei speichern
-dialog.save.fieldstosave=Felder zu speichern
+dialog.save.fieldstosave=Zu speichernde Felder
 dialog.save.table.field=Feld
-dialog.save.table.hasdata=Hat Daten
+dialog.save.table.hasdata=Enthält Daten
 dialog.save.table.save=Speichern
-dialog.save.headerrow=Titel Zeile speichern
+dialog.save.headerrow=Titelzeile speichern
 dialog.save.coordinateunits=Koordinaten Maßeinheiten
 dialog.save.altitudeunits=Höhe Maßeinheiten
 dialog.save.timestampformat=Zeitstempelformat
-dialog.save.overwrite.title=Datei existiert
-dialog.save.overwrite.text=Diese Datei existiert schon. Sind Sie sicher, Sie wollen die Datei Ã¼berschreiben?
+dialog.save.overwrite.title=Datei schon vorhanden
+dialog.save.overwrite.text=Diese Datei existiert schon. Wollen Sie die vorhandene Datei Ã¼berschreiben?
 dialog.exportkml.title=KML exportieren
 dialog.exportkml.text=Titel für die Daten
 dialog.exportkml.altitude=Auch Höheninformation (für Luftfahrt)
-dialog.exportkml.kmz=Daten ins kmz Datei komprimieren
-dialog.exportkml.exportimages=Bilder ins kmz exportieren
-dialog.exportkml.filetype=KML, KMZ Dateien
+dialog.exportkml.kmz=Daten in kmz Datei komprimieren
+dialog.exportkml.exportimages=Bilder in kmz exportieren
 dialog.exportgpx.title=GPX exportieren
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschreibung
-dialog.exportgpx.filetype=GPX Dateien
+dialog.exportgpx.includetimestamps=Zeitstempel exportieren
 dialog.exportpov.title=POV exportieren
-dialog.exportpov.text=Geben Sie die Parameter ein für das POV Export
+dialog.exportpov.text=Geben Sie die Parameter für den POV Export ein
 dialog.exportpov.font=Font
 dialog.exportpov.camerax=Kamera X
 dialog.exportpov.cameray=Kamera Y
 dialog.exportpov.cameraz=Kamera Z
-dialog.exportpov.filetype=POV Dateien
-dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nSind Sie sicher, Sie wollen fortsetzen?
+dialog.exportpov.modelstyle=Modellstil
+dialog.exportpov.ballsandsticks=Bälle und Stangen
+dialog.exportpov.tubesandwalls=Röhren und Wände
+dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nMöchten Sie den Vorgang trotzdem fortführen?
 dialog.confirmreversetrack.title=Umkehrung bestätigen
-dialog.confirmreversetrack.text=Diese Daten enthalten Zeitstempel Informationen, die bei einer Umkehrung ausser Reihenfolge erscheinen würden.\nSind Sie sicher, Sie wollen diese Spanne umkehren?
+dialog.confirmreversetrack.text=Diese Daten enthalten Zeitstempel Informationen, die bei einer Umkehrung in falscher Reihenfolge erscheinen würden.\nSind Sie sicher, dass Sie diesen Bereich umkehren wollen?
+dialog.confirmcutandmove.title=Verschieben bestätigen
+dialog.confirmcutandmove.text=Diese Daten enthalten Zeitstempel Informationen, die nach dem Verschieben in falscher Reihenfolge erscheinen würden.\nSind Sie sicher, dass Sie diesen Bereich verschieben wollen?
 dialog.interpolate.title=Punkte interpolieren
-dialog.interpolate.parameter.text=Anzahl Punkte zuzufügen zwischen den selektierten Punkten
-dialog.undo.title=Undo Operation(en)
-dialog.undo.pretext=Selektieren die Operationen die rückgängig gemacht werden sollen.
+dialog.interpolate.parameter.text=Anzahl Punkte die zwischen den gewählten Punkten eingefügt werden sollen
+dialog.undo.title=Aktionen Rückgängig
+dialog.undo.pretext=Auswahl der Operationen die rückgängig gemacht werden sollen.
 dialog.undo.none.title=Undo nicht möglich
 dialog.undo.none.text=Keine Operationen können rückgängig gemacht werden.
 dialog.clearundo.title=Undo-Liste löschen
@@ -134,9 +149,18 @@ dialog.pointedit.changevalue.text=Geben Sie den neuen Wert f
 dialog.pointedit.changevalue.title=Feld bearbeiten
 dialog.pointnameedit.title=Waypoint Name bearbeiten
 dialog.pointnameedit.name=Waypoint Name
-dialog.pointnameedit.uppercase=GROSS geschrieben
+dialog.pointnameedit.uppercase=GROß geschrieben
 dialog.pointnameedit.lowercase=klein geschrieben
 dialog.pointnameedit.sentencecase=Gemischt geschrieben
+dialog.addtimeoffset.title=Zeitdifferenz addieren
+dialog.addtimeoffset.add=Zeit addieren
+dialog.addtimeoffset.subtract=Zeit subtrahieren
+dialog.addtimeoffset.days=Tage
+dialog.addtimeoffset.hours=Stunde
+dialog.addtimeoffset.minutes=Minute
+dialog.addtimeoffset.notimestamps=Zeitdifferenz kann nicht addiert werden weil dieser Bereich keine Zeitinformation hat
+dialog.connect.title=Foto mit Punkt verbinden
+dialog.connectphoto.clonepoint=Dieser Punkt hat schon ein Foto.\nWollen Sie eine Kopie von dem Punkt machen?
 dialog.saveexif.title=Exif speichern
 dialog.saveexif.intro=Selektieren Sie die Fotos zu speichern
 dialog.saveexif.nothingtosave=Koordinaten sind nicht modifiziert, nichts zu speichern
@@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Keine Distanzgrenzen
 dialog.correlate.options.distancelimit=Distanzgrenzen
 dialog.correlate.options.correlate=Korrelieren
 dialog.correlate.alloutsiderange=Alle Fotos sind ausserhalb vom Track Zeitraum, so können nicht korreliert werden.\nVersuchen Sie mit einem anderen Offset oder verbinden Sie manuell mindestens ein Foto.
-dialog.map.title=Prune Karte
 dialog.help.help=Bitte sehen Sie\n http://activityworkshop.net/software/prune/\nfür weitere Information und Benutzeranleitungen.
 dialog.about.title=Ãœber Prune
 dialog.about.version=Version
@@ -179,6 +202,7 @@ dialog.about.build=Build
 dialog.about.summarytext1=Prune ist ein Programm für das Laden, Darstellen und Editieren von Daten von GPS Geräten.
 dialog.about.summarytext2=Es ist unter den Gnu GPL zur Verfügung gestellt, für frei, gratis und offen Gebrauch und Weiterentwicklung.<br>Kopieren, Weiterverbreitung und Veränderungen sind erlaubt und willkommen<br>unter die Bedingungen in der enthaltenen <code>license.txt</code> Datei.
 dialog.about.summarytext3=Bitte sehen Sie <code style="font-weight:bold">http://activityworkshop.net/</code> für weitere Information und Benutzeranleitungen.
+dialog.about.languages=Verfügbare Sprachen
 dialog.about.translatedby=Deutsche Ãœbersetzung von activityworkshop.
 dialog.about.systeminfo=System Information
 dialog.about.systeminfo.os=Betriebsystem
@@ -192,20 +216,28 @@ dialog.about.no=Nein
 dialog.about.credits=Credits
 dialog.about.credits.code=Prune Code geschrieben von
 dialog.about.credits.exifcode=Exif Code von
-dialog.about.credits.icons=Einige Ikons von
+dialog.about.credits.icons=Einige Bilder von
 dialog.about.credits.translators=Dolmetscher
 dialog.about.credits.translations=Ãœbersetzungen mit Hilfe von
 dialog.about.credits.devtools=Entwicklungsprogrammen
 dialog.about.credits.othertools=Andere Programmen
 dialog.about.credits.thanks=Danke an
 dialog.about.readme=Liesmich
+dialog.checkversion.title=Versionnummer prüfen
+dialog.checkversion.error=Die Versionnummer konnte nicht geprüft werden.\nBitte prüfen Sie die Internet Verbindung.
+dialog.checkversion.uptodate=Sie haben schon die letzte Version vom Prune.
+dialog.checkversion.newversion1=Eine neue Version vom Prune ist jetzt verfügbar!  Die neue Version heißt Version
+dialog.checkversion.newversion2=.
+dialog.checkversion.releasedate1=Diese neue Version ist am
+dialog.checkversion.releasedate2=veröffentlicht worden.
+dialog.checkversion.download=Um die neue Version herunterzuladen, schauen Sie nach http://activityworkshop.net/software/prune/download.html.
 
 # 3d window
-dialog.3d.title=Prune Drei-D Ansicht
+dialog.3d.title=Prune 3D Ansicht
 dialog.3d.altitudecap=Minimum Höhenskala
 dialog.3dlines.title=Prune Gitterlinien
 dialog.3dlines.empty=Keine Linien zum anzeigen!
-dialog.3dlines.intro=Hier sind die Linien für die drei-D Ansicht
+dialog.3dlines.intro=Hier sind die Linien für die 3D Ansicht
 
 # Confirm messages || These are displayed as confirmation in the status bar
 confirm.loadfile=Daten geladen vom
@@ -217,7 +249,10 @@ confirm.deletepoint.single=Punkt wurde entfernt
 confirm.deletepoint.multi=Punkte wurden entfernt
 confirm.point.edit=Punkt editiert
 confirm.mergetracksegments=Trackteilen verbunden
-confirm.reverserange=Spanne umgekehrt
+confirm.reverserange=Bereich umgekehrt
+confirm.addtimeoffset=Zeitdifferenz addiert
+confirm.rearrangewaypoints=Waypoints reorganisiert
+confirm.cutandmove=Bereich verschoben
 confirm.saveexif.ok1=Es wurden
 confirm.saveexif.ok2=Foto Dateien geschrieben
 confirm.undo.single=Operation rückgängig gemacht
@@ -228,6 +263,7 @@ confirm.photo.connect=Foto verbunden
 confirm.photo.disconnect=Foto getrennt
 confirm.correlate.single=Foto wurde korreliert
 confirm.correlate.multi=Fotos wurden korreliert
+confirm.createpoint=Punkt kreiert
 
 # Buttons
 button.ok=OK
@@ -251,6 +287,15 @@ button.selectall=Alle selektieren
 button.selectnone=Nichts selektieren
 button.preview=Vorschauen
 button.guessfields=Felder erraten
+button.showwebpage=Webseite anzeigen
+
+# File types
+filetype.txt=TXT Dateien
+filetype.jpeg=JPG Dateien
+filetype.kmlkmz=KML, KMZ Dateien
+filetype.kml=KML Dateien
+filetype.gpx=GPX Dateien
+filetype.pov=POV Dateien
 
 # Display components
 display.nodata=Keine Daten geladen
@@ -267,19 +312,20 @@ details.nopointselection=Nichts selektiert
 details.speed=Geschwindigkeit
 details.photofile=Foto Datei
 details.norangeselection=Nichts selektiert
-details.rangedetails=Details von Spanne
-details.range.selected=Selektiert
+details.rangedetails=Details vom Bereich
+details.range.selected=Markiert
 details.range.to=bis
 details.altitude.to=bis
 details.range.climb=Aufstieg
 details.range.descent=Abstieg
 details.coordformat=Koordinatenformat
 details.distanceunits=Distanz Maßeinheiten
-display.range.time.secs=S
-display.range.time.mins=M
-display.range.time.hours=Std
+display.range.time.secs=s
+display.range.time.mins=m
+display.range.time.hours=h
 display.range.time.days=T
 details.range.avespeed=Geschwindigkeit
+details.range.avemovingspeed=Geschwindigkeit unterwegs
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Fotos
 details.photodetails=Details vom Foto
@@ -298,21 +344,22 @@ fieldname.newsegment=Segment
 fieldname.custom=Custom
 fieldname.prefix=Feld
 fieldname.distance=Länge
+fieldname.movingdistance=Weglänge
 fieldname.duration=Zeitlänge
 
 # Measurement units
 units.original=Original
 units.default=Default
 units.metres=Meter
-units.metres.short=M
+units.metres.short=m
 units.feet=Füße
-units.feet.short=F
+units.feet.short=ft
 units.kilometres=Kilometer
-units.kilometres.short=Km
-units.kmh=Km/h
+units.kilometres.short=km
+units.kmh=km/h
 units.miles=Meilen
-units.miles.short=Mei
-units.mph=Mei/Std
+units.miles.short=mi
+units.mph=mi/h
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
@@ -333,16 +380,19 @@ undo.loadphotos=Fotos laden
 undo.editpoint=Punkt bearbeiten
 undo.deletepoint=Punkt löschen
 undo.deletephoto=Photo entfernen
-undo.deleterange=Spanne löschen
+undo.deleterange=Bereich löschen
 undo.compress=Track komprimieren
 undo.insert=Punkte hinzufügen
 undo.deleteduplicates=Duplikaten löschen
-undo.reverse=Spanne umdrehen
-undo.mergetracksegments=Trackteilen verbinden
+undo.reverse=Bereich umdrehen
+undo.mergetracksegments=Trackteile verbinden
+undo.addtimeoffset=zeitdifferenz addieren
 undo.rearrangewaypoints=Waypoints reorganisieren
+undo.cutandmove=Bereich verschieben
 undo.connectphoto=Foto verbinden
 undo.disconnectphoto=Foto trennen
 undo.correlate=Fotos korrelieren
+undo.createpoint=Punkt kreieren
 
 # Error messages
 error.save.dialogtitle=Fehler beim Speichern
@@ -370,6 +420,6 @@ error.function.notavailable.title=Funktion nicht verf
 error.function.nojava3d=Diese Funktion braucht den Java3d Library,\nvon Sun.com erhältlich.
 error.3d.title=Fehler mit 3d Darstellung
 error.3d=Ein Fehler ist mit der 3d Darstellung aufgetreten
-error.readme.notfound=Lies mich Datei nicht gefunden
+error.readme.notfound=Liesmich Datei nicht gefunden
 error.osmimage.dialogtitle=Laden von Karten-Bilder fehlgeschlagen
 error.osmimage.failed=Laden von Karten-Bilder fehlgeschlagen. Bitte prüfen Sie die Internet-Verbindung.
index 5b0c378a63c918392acdedf1031608a4bb2a0006..acd3c2286bb734ca5ecae79cc7f5b0a0a4a4264e 100644 (file)
@@ -3,7 +3,8 @@
 
 # Menu entries
 menu.file=Datei
-menu.file.open=Öffne
+menu.file.open=File Ã¶ffne
+menu.file.loadfromgps=uusem GPS lade
 menu.file.addphotos=Fötelis innätue
 menu.file.save=Speichere
 menu.file.exportkml=KML exportiere
@@ -16,16 +17,18 @@ menu.edit.clearundo=Undo-Liste l
 menu.edit.editpoint=Punkt editiere
 menu.edit.editwaypointname=Waypoint Name editiere
 menu.edit.deletepoint=Punkt lösche
-menu.edit.deleterange=Spanne lösche
+menu.edit.deleterange=Beriich lösche
 menu.edit.deleteduplicates=Doppeldate lösche
 menu.edit.compress=Date komprimiere
 menu.edit.interpolate=Interpoliere
-menu.edit.reverse=Spanne umdrähie
+menu.edit.reverse=Beriich umdrähie
+menu.edit.addtimeoffset=Ziitverschiebig zutue
 menu.edit.mergetracksegments=Track Segmänte merge
 menu.edit.rearrange=Waypoints reorganisiere
 menu.edit.rearrange.start=Alli zum Aafang
 menu.edit.rearrange.end=Alli zum Ã„nde
 menu.edit.rearrange.nearest=Jede zum nöchsti Trackpunkt
+menu.edit.cutandmove=Schniide und move
 menu.select=Selektiere
 menu.select.all=Alles selektiere
 menu.select.none=Nüüt selektiere
@@ -39,18 +42,20 @@ menu.photo.correlate=Alli F
 menu.photo.delete=Föteli entfernä
 menu.view=Aasicht
 menu.view.show3d=In drüü-D zeigä
-menu.view.showmap=Kate zeigä
 menu.view.browser=Karte inem Browser
 menu.view.browser.google=Google maps
 menu.view.browser.openstreetmap=Openstreetmap
 menu.help=Hilfe
 menu.help.about=Ãœber Prune
+menu.help.checkversion=Pruef nach ne noie Version
 # Popup menu for map
 menu.map.zoomin=Einzoome
 menu.map.zoomout=Uuszoome
 menu.map.zoomfull=Zoome zum ganzes Bild
+menu.map.newpoint=Noii Punkt
 menu.map.connect=Trackpünktli verbindä
 menu.map.autopan=Autopan
+menu.map.showmap=Kate zeigä
 
 # Dialogs
 dialog.exit.confirm.title=Prune beände
@@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extrakt vom File
 dialog.openoptions.altitudeunits=Höchi Masseiheite
 dialog.jpegload.subdirectories=Subordnern au
 dialog.jpegload.loadjpegswithoutcoords=Au Fötelis ohni Koordinate
+dialog.jpegload.loadjpegsoutsidearea=Au Fötelis uuserhalb vonem Track
 dialog.jpegload.progress.title=Fötelis lade
 dialog.jpegload.progress=Bitte warte während die Fötelis durägsucht werde
+dialog.gpsload.title=Lade uusem GPS
+dialog.gpsload.nogpsbabel=Kei gpsbabel Programm gfunde. Wiiter?
+dialog.gpsload.device=Device Name
+dialog.gpsload.format=Format
+dialog.gpsload.getwaypoints=Waypoints lade
+dialog.gpsload.gettracks=Tracks lade
 dialog.saveoptions.title=File speicherä
 dialog.save.fieldstosave=Fälder zu speicherä
 dialog.save.table.field=Fäld
@@ -102,21 +114,24 @@ dialog.exportkml.text=Titel f
 dialog.exportkml.altitude=Au Höchiinformation (fürs Fliege)
 dialog.exportkml.kmz=Date ins kmz File komprimierä
 dialog.exportkml.exportimages=Bildli ins Kmz exportierä
-dialog.exportkml.filetype=KML, KMZ Dateie
 dialog.exportgpx.title=GPX exportierä
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschriibig
-dialog.exportgpx.filetype=GPX Dateie
+dialog.exportgpx.includetimestamps=Au Ziitstämpel
 dialog.exportpov.title=POV exportierä
 dialog.exportpov.text=Gäbet Sie die Parameter ii fürs POV Export
 dialog.exportpov.font=Font
 dialog.exportpov.camerax=Kamera X
 dialog.exportpov.cameray=Kamera Y
 dialog.exportpov.cameraz=Kamera Z
-dialog.exportpov.filetype=POV Dateie
+dialog.exportpov.modelstyle=Modellstil
+dialog.exportpov.ballsandsticks=Bälle und Schtange
+dialog.exportpov.tubesandwalls=Röhre und Wände
 dialog.exportpov.warningtracksize=Dieser Track hät mega viele Punkte, die Java3D villiicht nöd chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze?
 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 diese Spanne umkehre?
+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?
+dialog.confirmcutandmove.title=Move bestätige
+dialog.confirmcutandmove.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Move usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend dn Beriich move?
 dialog.interpolate.title=Punkte interpoliere
 dialog.interpolate.parameter.text=Aazahl Punkte zum innätue zwüschet den selektierten Punkten
 dialog.undo.title=Undo Operation(e)
@@ -126,7 +141,7 @@ dialog.undo.none.text=Keini Operatione k
 dialog.clearundo.title=Undo-Liste löschä
 dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste lösche?\nAlle Undo Infos werdet verlore gah!
 dialog.pointedit.title=Punkt editierä
-dialog.pointedit.text=Wählet Sie jäden Fäld uus zu editiere, und mitem 'Editiere' Chnopf den Wert Ã¤ndere
+dialog.pointedit.text=Wählet Sie jäden Fäld uus zu editiere, und mitem 'Editierä' Chnopf den Wert Ã¤ndere
 dialog.pointedit.table.field=Fäld
 dialog.pointedit.table.value=Wert
 dialog.pointedit.table.changed=Geändert
@@ -136,7 +151,16 @@ dialog.pointnameedit.title=Waypoint Name editiere
 dialog.pointnameedit.name=Waypoint Name
 dialog.pointnameedit.uppercase=GROSS gschriebe
 dialog.pointnameedit.lowercase=chli gschriebe
-dialog.pointnameedit.sentencecase=Gmischt gschriebe
+dialog.pointnameedit.sentencecase=Gmischt Gschriebe
+dialog.addtimeoffset.title=Ziitverschiebig zutue
+dialog.addtimeoffset.add=Ziit zutue
+dialog.addtimeoffset.subtract=Ziit davo neh
+dialog.addtimeoffset.days=Tage
+dialog.addtimeoffset.hours=Schtunde
+dialog.addtimeoffset.minutes=Minute
+dialog.addtimeoffset.notimestamps=Ziitverschiebig nöd möglech wil dr Beriich kei Ziitinfo hät
+dialog.connect.title=Föteli verknüpfe
+dialog.connectphoto.clonepoint=Derer Punkt hät scho s Föteli.\nWend sie ne Kopie vonem Punkt machä?
 dialog.saveexif.title=Exif go speicherä
 dialog.saveexif.intro=Wählet Sie die Fötelis uus zum speicherä
 dialog.saveexif.nothingtosave=Koordinaten sin nöd geänderet, nüüt zum speicherä
@@ -171,14 +195,14 @@ 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.map.title=Prune Karte
 dialog.help.help=Bitte lueg na\n http://activityworkshop.net/software/prune/\nfür wiitere Information und Benutzeraaleitige.
 dialog.about.title=Ãœber Prune
 dialog.about.version=Version
 dialog.about.build=Build
-dialog.about.summarytext1=Prune isch n Programm fürs Lade, Darstelle und Editiere vo Date von GPS Geräte.
-dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verfüegig gstellt,für frei, gratis und offen Gebruuch und Wiiterentwicklig.<br>Kopiere, Wiiterverbreitig und Veränderige sin erlaubt und willkommen<br>unter die Bedingunge im enthaltene <code>license.txt</code> File.
-dialog.about.summarytext3=Bitte lueg na <code style="font-weight:bold">http://activityworkshop.net/</code> für wiitere Information und Benutzeraaleitige.
+dialog.about.summarytext1=Prune isch s Programm fürs Lade, Darstelle und Editiere vo Date von GPS Geräte.
+dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verfüegig gstellt,für frei, gratis und offen Gebruuch und Wiiterentwicklig.<br>Kopiere, Wiiterverbreitig und Veränderige sin erlaubt und willkomme<br>unter die Bedingige im enthaltene <code>license.txt</code> File.
+dialog.about.summarytext3=Bitte lueget Sie na <code style="font-weight:bold">http://activityworkshop.net/</code> für wiitere Informatione und Benutzeraaleitige.
+dialog.about.languages=Verfüegbare Sproche
 dialog.about.translatedby=Schwiizerdüütschi Ãœbersetzig vo activityworkshop.
 dialog.about.systeminfo=Syschtem Info
 dialog.about.systeminfo.os=Betriebsyschtem
@@ -192,13 +216,21 @@ dialog.about.no=Nei
 dialog.about.credits=Credits
 dialog.about.credits.code=Prune Code gschriebä vo
 dialog.about.credits.exifcode=Exif Code vo
-dialog.about.credits.icons=Einigi Ikons vo
+dialog.about.credits.icons=Einigi Bilder vo
 dialog.about.credits.translators=Dolmätscher
 dialog.about.credits.translations=Ãœbersetzige mit dr Hilfe vo
 dialog.about.credits.devtools=Entwicklungswärkzüüge
 dialog.about.credits.othertools=Anderi Wärkzüüge
 dialog.about.credits.thanks=Danke an
 dialog.about.readme=Läsmi
+dialog.checkversion.title=Pruef d'Version
+dialog.checkversion.error=Die Versionnummer könne nöd gefprüft werdä.\nGits ne internet Verbindig?
+dialog.checkversion.uptodate=Sie han die noischti Version vonem Prune scho.
+dialog.checkversion.newversion1=Ne noii Version vonem Prune isch jetzt usse! Die heisst jetzt Version
+dialog.checkversion.newversion2=.
+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.
 
 # 3d window
 dialog.3d.title=Prune Drüü-d Aasicht
@@ -217,7 +249,10 @@ confirm.deletepoint.single=Punkt isch entfernt worde
 confirm.deletepoint.multi=Punkte sin entfernt worde
 confirm.point.edit=Punkt editiert
 confirm.mergetracksegments=Segmänte gmerged
-confirm.reverserange=Spanne umgdrähet
+confirm.reverserange=Beriich umgdrähet
+confirm.addtimeoffset=Ziitverschiebig zutue
+confirm.rearrangewaypoints=Waypoints umorganisiert
+confirm.cutandmove=Beriich gmoved
 confirm.saveexif.ok1=Es sin
 confirm.saveexif.ok2=Fötelis gschriebe worde
 confirm.undo.single=Operation rückgängig gmacht worde.
@@ -228,6 +263,7 @@ confirm.photo.connect=F
 confirm.photo.disconnect=Föteli gtrännt
 confirm.correlate.single=Föteli isch korreliert worde
 confirm.correlate.multi=Fötelis sin korreliert worde
+confirm.createpoint=Punkt kreiert worde
 
 # Buttons
 button.ok=OK
@@ -251,6 +287,15 @@ button.selectall=Alli selektier
 button.selectnone=Nüüt selektierä
 button.preview=Vorschauä
 button.guessfields=Fälde erratä
+button.showwebpage=Websiite aazeigä
+
+# File types
+filetype.txt=TXT Dateie
+filetype.jpeg=JPG Dateie
+filetype.kmlkmz=KML, KMZ Dateie
+filetype.kml=KML Dateie
+filetype.gpx=GPX Dateie
+filetype.pov=POV Dateie
 
 # Display components
 display.nodata=Kei Date glade worde
@@ -267,7 +312,7 @@ details.nopointselection=N
 details.speed=Gschwindikeit
 details.photofile=Föteli Datei
 details.norangeselection=Nüüt selektiert
-details.rangedetails=Details vo dr Spanne
+details.rangedetails=Details vonem Beriich
 details.range.selected=Selektiert
 details.range.to=bis
 details.altitude.to=bis
@@ -275,11 +320,12 @@ details.range.climb=Uufstieg
 details.range.descent=Abstieg
 details.coordformat=Koordinatenformat
 details.distanceunits=Distanz Masseinheite
-display.range.time.secs=S
-display.range.time.mins=M
+display.range.time.secs=Sek
+display.range.time.mins=Min
 display.range.time.hours=Std
 display.range.time.days=T
 details.range.avespeed=Gschwindikeit
+details.range.avemovingspeed=Gschwindikeit ufem Wäg
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Fötelis
 details.photodetails=Details vom Föteli
@@ -298,21 +344,22 @@ fieldname.newsegment=Segm
 fieldname.custom=Custom
 fieldname.prefix=Fäld
 fieldname.distance=Längi
+fieldname.movingdistance=Weglängi
 fieldname.duration=Ziitlängi
 
 # Measurement units
 units.original=Original
 units.default=Default
 units.metres=Meter
-units.metres.short=M
+units.metres.short=m
 units.feet=Fuess
-units.feet.short=F
+units.feet.short=ft
 units.kilometres=Kilometer
-units.kilometres.short=Km
-units.kmh=Km/h
+units.kilometres.short=km
+units.kmh=km/h
 units.miles=Meile
-units.miles.short=Mei
-units.mph=Mei/Std
+units.miles.short=mi
+units.mph=mi/Std
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
@@ -333,16 +380,19 @@ undo.loadphotos=F
 undo.editpoint=Punkt editierä
 undo.deletepoint=Punkt löschä
 undo.deletephoto=Föteli entfärnä
-undo.deleterange=Spanne löschä
+undo.deleterange=Beriich löschä
 undo.compress=Track komprimierä
 undo.insert=Punkte innätuä
 undo.deleteduplicates=Duplikaten löschä
-undo.reverse=Spanne umdrähie
+undo.reverse=Beriich umdrähie
 undo.mergetracksegments=track segmänte merge
+undo.addtimeoffset=ziitverschiebig 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.createpoint=Punkt kreierä
 
 # Error messages
 error.save.dialogtitle=Fähle bim Speichere
index 191260380543916965c17ac61f15ab4cc255d018..cfa1a09d6c72faa2b9ec298b9e56be0deec467f8 100644 (file)
@@ -3,8 +3,9 @@
 
 # Menu entries
 menu.file=Archivo
-menu.file.open=Abrir
+menu.file.open=Abrir archivo
 menu.file.addphotos=Cargar fotos
+menu.file.loadfromgps=Cargar datos del GPS
 menu.file.save=Guardar
 menu.file.exportkml=Exportar KML
 menu.file.exportgpx=Exportar GPX
@@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Eliminar duplicados
 menu.edit.compress=Comprimir track
 menu.edit.interpolate=Interpolar
 menu.edit.reverse=Invertir rango
+menu.edit.addtimeoffset=
 menu.edit.mergetracksegments=Unir los segmentos de track
 menu.edit.rearrange=Reorganizar waypoints
 menu.edit.rearrange.start=Volver al comienzo
 menu.edit.rearrange.end=Ir al final
 menu.edit.rearrange.nearest=Ir al más próximo
+menu.edit.cutandmove=
 menu.select=Seleccionar
 menu.select.all=Seleccionar todo
 menu.select.none=No seleccionar nada
@@ -39,18 +42,20 @@ menu.photo.correlate=Correlacionar todas las fotos
 menu.photo.delete=Eliminar foto
 menu.view=Ver
 menu.view.show3d=Mostrar en 3-D
-menu.view.showmap=Mostrar el mapa
 menu.view.browser=Mapa en un navegador
 menu.view.browser.google=Google maps
 menu.view.browser.openstreetmap=Openstreetmap
 menu.help=Ayuda
 menu.help.about=Acerca de Prune
+menu.help.checkversion=Buscar una nueva versión
 # Popup menu for map
 menu.map.zoomin=Ampliar zoom
 menu.map.zoomout=Reducir zoom
 menu.map.zoomfull=Mostrar todo
+menu.map.newpoint=Crear uno punto nuevo
 menu.map.connect=Conectar puntos de track
 menu.map.autopan=Posicionar automáticamente
+menu.map.showmap=Mostrar el mapa
 
 # Dialogs
 dialog.exit.confirm.title=Salir de Prune
@@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extraer archivo
 dialog.openoptions.altitudeunits=Unidades altitud
 dialog.jpegload.subdirectories=Incluir subdirectorios
 dialog.jpegload.loadjpegswithoutcoords=Fotos sin coordenadas tambien
+dialog.jpegload.loadjpegsoutsidearea=
 dialog.jpegload.progress.title=Cargando fotos
 dialog.jpegload.progress=Por favor espere mientras se buscan las fotos
+dialog.gpsload.title=
+dialog.gpsload.nogpsbabel=gpsbabel program no encontrado. Desea continuar?
+dialog.gpsload.device=
+dialog.gpsload.format=
+dialog.gpsload.getwaypoints=
+dialog.gpsload.gettracks=
 dialog.saveoptions.title=Guardar archivo
 dialog.save.fieldstosave=Campos a guardar
 dialog.save.table.field=Campo
@@ -102,21 +114,24 @@ dialog.exportkml.text=Descripci
 dialog.exportkml.altitude=Incluir altitudes (para aviación)
 dialog.exportkml.kmz=Comprimir al archivo kmz
 dialog.exportkml.exportimages=Exportar fotos al kmz
-dialog.exportkml.filetype=Archivos KML, KMZ
 dialog.exportgpx.title=Exportar GPX
 dialog.exportgpx.name=Nombre
 dialog.exportgpx.desc=Descripción
-dialog.exportgpx.filetype=Archivos GPX
+dialog.exportgpx.includetimestamps=Tiempo tambien
 dialog.exportpov.title=Exportar POV
 dialog.exportpov.text=Introdzca los Parametros para exportar
 dialog.exportpov.font=Fuente
 dialog.exportpov.camerax=Cámara X
 dialog.exportpov.cameray=Cámara Y
 dialog.exportpov.cameraz=Cámara Z
-dialog.exportpov.filetype=Archivos POV
+dialog.exportpov.modelstyle=
+dialog.exportpov.ballsandsticks=
+dialog.exportpov.tubesandwalls=
 dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Está seguro de que desea continuar?
 dialog.confirmreversetrack.title=Confirmar inversión
 dialog.confirmreversetrack.text=Este track contiene información sobre la fecha, que estará fuera de secuencia después de la inversión. Esta seguro que desea invertir esta sección?
+dialog.confirmcutandmove.title=Confirmar ...
+dialog.confirmcutandmove.text=Este track contiene información sobre la fecha, que estará fuera de secuencia después de la .... Esta seguro que desea ... esta sección?
 dialog.interpolate.title=Interpolar puntos
 dialog.interpolate.parameter.text=Número de los puntos a insertar entre los puntos elegidos
 dialog.undo.title=Deshacer
@@ -137,6 +152,15 @@ dialog.pointnameedit.name=Nombre de waypoint
 dialog.pointnameedit.uppercase=Mayúsculas
 dialog.pointnameedit.lowercase=minúsculas
 dialog.pointnameedit.sentencecase=Mezcla
+dialog.addtimeoffset.title=
+dialog.addtimeoffset.add=
+dialog.addtimeoffset.subtract=
+dialog.addtimeoffset.days=Dias
+dialog.addtimeoffset.hours=Horas
+dialog.addtimeoffset.minutes=Minutos
+dialog.addtimeoffset.notimestamps=
+dialog.connect.title=Conectar foto
+dialog.connectphoto.clonepoint=
 dialog.saveexif.title=Guardar Exif
 dialog.saveexif.intro=Seleccione fotos a guardar
 dialog.saveexif.nothingtosave=Coordenadas no modificadas, nada que guardar
@@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Sin l
 dialog.correlate.options.distancelimit=Límite de distancia
 dialog.correlate.options.correlate=Correlacionar
 dialog.correlate.alloutsiderange=Todas las fotos están 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.map.title=Mapa Prune
 dialog.help.help=Por favor, ver\n http://activityworkshop.net/software/prune/\npara más información y guías del usuario.
 dialog.about.title=Acerca de Prune
 dialog.about.version=Versión
@@ -179,6 +202,7 @@ dialog.about.build=Construir
 dialog.about.summarytext1=Prune es un programa para cargar, mostrar y editar datos de receptores GPS.
 dialog.about.summarytext2=Distribuido bajo el GNU GPL para uso libre y gratuito.<br>Se permite (y se anima) la copia, redistribución y modificación de acuerdo<br>a las condiciones incluidas en el archivo <code>licence.txt</code>.
 dialog.about.summarytext3=Por favor, ver <code style="font-weight:bold">http://activityworkshop.net/</code> para más información y guías del usuario.
+dialog.about.languages=
 dialog.about.translatedby=Traducción al español realizada por activityworkshop y amigos muy amables!
 dialog.about.systeminfo=Informacion del sistema
 dialog.about.systeminfo.os=Sistema operativo
@@ -199,6 +223,14 @@ dialog.about.credits.devtools=Herramientas de desarrollo
 dialog.about.credits.othertools=Otras herramientas
 dialog.about.credits.thanks=Gracias a
 dialog.about.readme=Readme
+dialog.checkversion.title=
+dialog.checkversion.error=
+dialog.checkversion.uptodate=
+dialog.checkversion.newversion1=
+dialog.checkversion.newversion2=
+dialog.checkversion.releasedate1=
+dialog.checkversion.releasedate2=
+dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html.
 
 # 3d window
 dialog.3d.title=Prune vista 3-D
@@ -218,6 +250,9 @@ confirm.deletepoint.multi=puntos eliminados
 confirm.point.edit=Punto editado
 confirm.mergetracksegments=Segmentos unidos
 confirm.reverserange=Rango invertido
+confirm.addtimeoffset=
+confirm.rearrangewaypoints=Waypoints reorganizados
+confirm.cutandmove=
 confirm.saveexif.ok1=Guardando
 confirm.saveexif.ok2=fotos
 confirm.undo.single=operación no realizada
@@ -228,6 +263,7 @@ confirm.photo.connect=Foto conectado
 confirm.photo.disconnect=Foto desconectado
 confirm.correlate.single=foto fue correlada
 confirm.correlate.multi=fotos fueron correladas
+confirm.createpoint=
 
 # Buttons
 button.ok=Aceptar
@@ -251,6 +287,15 @@ button.selectall=Seleccionar todo
 button.selectnone=Seleccionar nada
 button.preview=Previsualización
 button.guessfields=Adivinar campos
+button.showwebpage=
+
+# File types
+filetype.txt=Archivos TXT
+filetype.jpeg=Archivos JPG
+filetype.kmlkmz=Archivos KML, KMZ
+filetype.kml=Archivos KML
+filetype.gpx=Archivos GPX
+filetype.pov=Archivos POV
 
 # Display components
 display.nodata=Ningún dato cargado
@@ -280,6 +325,7 @@ display.range.time.mins=m
 display.range.time.hours=h
 display.range.time.days=d
 details.range.avespeed=Velocidad media
+details.range.avemovingspeed=
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Fotos
 details.photodetails=Detalles del Foto
@@ -298,6 +344,7 @@ fieldname.newsegment=Segmento
 fieldname.custom=Personalizado
 fieldname.prefix=Campo
 fieldname.distance=Distancia
+fieldname.movingdistance=
 fieldname.duration=Duración
 
 # Measurement units
@@ -339,10 +386,13 @@ undo.insert=insertar puntos
 undo.deleteduplicates=eliminar duplicados
 undo.reverse=invertir rango
 undo.mergetracksegments=unir los segmentos de track
+undo.addtimeoffset=
 undo.rearrangewaypoints=reordenar waypoints
+undo.cutandmove=
 undo.connectphoto=conectar foto
 undo.disconnectphoto=desconectar foto
 undo.correlate=correlacionar fotos
+undo.createpoint=crear punto
 
 # Error messages
 error.save.dialogtitle=Fallo al guardar datos
index 1906ea7ae6b107df8a73938caebc1dcde3ec7425..0926a554340ba1928582fb3f2208e466942849fa 100644 (file)
@@ -3,8 +3,9 @@
 
 # Menu entries
 menu.file=Fichier
-menu.file.open=Ouvrir
+menu.file.open=Ouvrir fichier
 menu.file.addphotos=Ouvrir photos
+menu.file.loadfromgps=Charger Ã  partir du GPS
 menu.file.save=Enregistrer
 menu.file.exportkml=Exporter en KML
 menu.file.exportgpx=Exporter en GPX
@@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Supprimer les doublons
 menu.edit.compress=Compacter la trace
 menu.edit.interpolate=Interpoler
 menu.edit.reverse=Inverser l'étendue
+menu.edit.addtimeoffset=
 menu.edit.mergetracksegments=Fusionner les segments de trace
 menu.edit.rearrange=Réarranger les waypoints
 menu.edit.rearrange.start=Tous au début du fichier
 menu.edit.rearrange.end=Tous Ã  la fin du fichier
 menu.edit.rearrange.nearest=Chacun au point de trace le plus proche
+menu.edit.cutandmove=
 menu.select=Sélectionner
 menu.select.all=Tout sélectionner
 menu.select.none=Rien sélectionner
@@ -39,18 +42,20 @@ menu.photo.correlate=Corr
 menu.photo.delete=Retirer la photo
 menu.view=Affichage
 menu.view.show3d=Montrer en 3D
-menu.view.showmap=Montrer la carte
 menu.view.browser=Ouvrir la carte dans le navigateur
 menu.view.browser.google=Google maps
 menu.view.browser.openstreetmap=Openstreetmap
 menu.help=Aide
 menu.help.about=À propos de Prune
+menu.help.checkversion=
 # Popup menu for map
 menu.map.zoomin=Zoom avant
 menu.map.zoomout=Zoom arrière
 menu.map.zoomfull=Adapter Ã  la vue
+menu.map.newpoint=
 menu.map.connect=Relier les points de trace
 menu.map.autopan=Déplacement automatique
+menu.map.showmap=Montrer la carte
 
 # Dialogs
 dialog.exit.confirm.title=Quitter Prune
@@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extrait de fichier
 dialog.openoptions.altitudeunits=Unités d'altitude
 dialog.jpegload.subdirectories=Inclure les sous-dossiers
 dialog.jpegload.loadjpegswithoutcoords=Inclure les photos sans coordonnées
+dialog.jpegload.loadjpegsoutsidearea=
 dialog.jpegload.progress.title=Chargement des photos
 dialog.jpegload.progress=Veuillez patienter pendant la recherche des photos
+dialog.gpsload.title=
+dialog.gpsload.nogpsbabel=Gpsbabel introuvable. Continuer ?
+dialog.gpsload.device=
+dialog.gpsload.format=
+dialog.gpsload.getwaypoints=
+dialog.gpsload.gettracks=
 dialog.saveoptions.title=Enregistrer le fichier
 dialog.save.fieldstosave=Champs Ã  enregistrer
 dialog.save.table.field=Champ
@@ -102,21 +114,24 @@ dialog.exportkml.text=Titre pour les donn
 dialog.exportkml.altitude=Inclure les altitudes (pour aviation)
 dialog.exportkml.kmz=Compresser au format kmz
 dialog.exportkml.exportimages=Exporter les vignettes au format kmz
-dialog.exportkml.filetype=Fichiers KML, KMZ
 dialog.exportgpx.title=Exporter en GPX
 dialog.exportgpx.name=Nom
 dialog.exportgpx.desc=Légende
-dialog.exportgpx.filetype=Fichiers GPX
+dialog.exportgpx.includetimestamps=
 dialog.exportpov.title=Exporter en POV
 dialog.exportpov.text=Entrez les paramètres pour l'export POV
 dialog.exportpov.font=Police
 dialog.exportpov.camerax=Camera X
 dialog.exportpov.cameray=Camera Y
 dialog.exportpov.cameraz=Camera Z
-dialog.exportpov.filetype=Fichiers POV
+dialog.exportpov.modelstyle=
+dialog.exportpov.ballsandsticks=
+dialog.exportpov.tubesandwalls=
 dialog.exportpov.warningtracksize=Cette trace possède un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\nEtes-vous sûr de vouloir continuer ?
 dialog.confirmreversetrack.title=Confirmer l'inversion
 dialog.confirmreversetrack.text=Cette trace contient des informations temporelles qui seront désordonnées après une inversion.\nEtes-vous sûr de vouloir inverser cette section ?
+dialog.confirmcutandmove.title=Confirmer ...
+dialog.confirmcutandmove.text=Cette trace contient des informations temporelles qui seront désordonnées après une ....\nEtes-vous sûr de vouloir ... cette section ?
 dialog.interpolate.title=Interpoler les points
 dialog.interpolate.parameter.text=Nombre de points Ã  insérer entre les points sélectionnés
 dialog.undo.title=Annuler les actions
@@ -137,6 +152,15 @@ dialog.pointnameedit.name=Nom de waypoint
 dialog.pointnameedit.uppercase=CASSE MAJUSCULES
 dialog.pointnameedit.lowercase=casse minuscules
 dialog.pointnameedit.sentencecase=Casse Phrase
+dialog.addtimeoffset.title=
+dialog.addtimeoffset.add=
+dialog.addtimeoffset.subtract=
+dialog.addtimeoffset.days=Jours
+dialog.addtimeoffset.hours=Heures
+dialog.addtimeoffset.minutes=Minutes
+dialog.addtimeoffset.notimestamps=
+dialog.connect.title=
+dialog.connectphoto.clonepoint=
 dialog.saveexif.title=Enregistrer Exif
 dialog.saveexif.intro=Sélectionner les photos Ã  sauver Ã  l'aide des cases Ã  cocher
 dialog.saveexif.nothingtosave=Coordonnées inchangées, rien Ã  enregistrer
@@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Pas de limite de distance
 dialog.correlate.options.distancelimit=Limite de distance
 dialog.correlate.options.correlate=Corréler
 dialog.correlate.alloutsiderange=Les photos ne correspondent pas Ã  la plage de temps de la trace, aucune ne peut Ãªtre corrélée.\nEssayez de modifier le décalage ou de corréler manuellement au moins une photo.
-dialog.map.title=Prune carte
 dialog.help.help=Consultez la page\n http://activityworkshop.net/software/prune/\npour plus de détails et des manuels utilisateur.
 dialog.about.title=À propos de Prune
 dialog.about.version=Version
@@ -179,6 +202,7 @@ dialog.about.build=Build
 dialog.about.summarytext1=Prune est un programme pour charger, afficher et Ã©diter des données de récepteurs GPS.
 dialog.about.summarytext2=Distribué sous license Gnu GPL pour un usage et une amélioration libres, ouverts et mondiaux.<br>La copie, la redistribution et la modification sont autorisées et encouragées<br>selon les conditions détaillées dans le fichier <code>license.txt</code> inclus.
 dialog.about.summarytext3=Consultez la page <code style="font-weight:bold">http://activityworkshop.net/</code> pour plus de détails et des manuels utilisateur.
+dialog.about.languages=
 dialog.about.translatedby=Texte en français par Petrovsk.
 dialog.about.systeminfo=Info Système
 dialog.about.systeminfo.os=Système d'exploitation
@@ -199,6 +223,14 @@ dialog.about.credits.devtools=Outils de d
 dialog.about.credits.othertools=Autre outils
 dialog.about.credits.thanks=Merci Ã 
 dialog.about.readme=Lisez-moi
+dialog.checkversion.title=
+dialog.checkversion.error=
+dialog.checkversion.uptodate=
+dialog.checkversion.newversion1=
+dialog.checkversion.newversion2=
+dialog.checkversion.releasedate1=
+dialog.checkversion.releasedate2=
+dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html.
 
 # 3d window
 dialog.3d.title=Vue 3D de Prune
@@ -218,6 +250,9 @@ confirm.deletepoint.multi=points ont 
 confirm.point.edit=point Ã©dité
 confirm.mergetracksegments=Segments de trace ont Ã©té fusionné
 confirm.reverserange=Etendue inversée
+confirm.addtimeoffset=
+confirm.rearrangewaypoints=
+confirm.cutandmove=
 confirm.saveexif.ok1=Enregistrement de
 confirm.saveexif.ok2=fichiers photo
 confirm.undo.single=opération annulée
@@ -228,6 +263,7 @@ confirm.photo.connect=photo reli
 confirm.photo.disconnect=photo détachée
 confirm.correlate.single=photo a Ã©té corrélée
 confirm.correlate.multi=photos ont Ã©té corrélées
+confirm.createpoint=
 
 # Buttons
 button.ok=OK
@@ -251,6 +287,15 @@ button.selectall=Tout s
 button.selectnone=Ne rien sélectionner
 button.preview=Aperçu
 button.guessfields=Deviner les champs
+button.showwebpage=
+
+# File types
+filetype.txt=Fichiers TXT
+filetype.jpeg=Fichiers JPG
+filetype.kmlkmz=Fichiers KML, KMZ
+filetype.kml=Fichiers KML
+filetype.gpx=Fichiers GPX
+filetype.pov=Fichiers POV
 
 # Display components
 display.nodata=Pas de données chargées
@@ -280,6 +325,7 @@ display.range.time.mins=m
 display.range.time.hours=h
 display.range.time.days=j
 details.range.avespeed=Vitesse moyenne
+details.range.avemovingspeed=
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Photos
 details.photodetails=Détails de la photo
@@ -298,6 +344,7 @@ fieldname.newsegment=Segment
 fieldname.custom=Personnalisé
 fieldname.prefix=Champ
 fieldname.distance=Distance
+fieldname.movingdistance=
 fieldname.duration=Durée
 
 # Measurement units
@@ -339,10 +386,13 @@ undo.insert=ins
 undo.deleteduplicates=effacer les doublons
 undo.reverse=inverser l'étendue
 undo.mergetracksegments=fusionner les segments de trace
+undo.addtimeoffset=
 undo.rearrangewaypoints=réarranger les waypoints
+undo.cutandmove=
 undo.connectphoto=relier la photo
 undo.disconnectphoto=détacher la photo
 undo.correlate=corréler les photos
+undo.createpoint=
 
 # Error messages
 error.save.dialogtitle=Erreur Ã  l'enregistrement des données
diff --git a/tim/prune/lang/prune-texts_it.properties b/tim/prune/lang/prune-texts_it.properties
new file mode 100644 (file)
index 0000000..8c4e780
--- /dev/null
@@ -0,0 +1,425 @@
+# Text entries for the Prune application
+# Italian entries as extra
+
+# Menu entries
+menu.file=File
+menu.file.open=Apri file
+menu.file.addphotos=Aggiungi foto
+menu.file.loadfromgps=Carica dati da GPS
+menu.file.save=Salva
+menu.file.exportkml=Esporta in KML
+menu.file.exportgpx=Esporta in GPX
+menu.file.exportpov=Esporta in POV
+menu.file.exit=Esci
+menu.edit=Edita
+menu.edit.undo=Annulla
+menu.edit.clearundo=Cancella la lista annulla
+menu.edit.editpoint=Edita punto
+menu.edit.editwaypointname=Edita nome waypoint
+menu.edit.deletepoint=Cancella punto
+menu.edit.deleterange=Cancella la serie
+menu.edit.deleteduplicates=Cancella duplicati
+menu.edit.compress=Comprimi traccia
+menu.edit.interpolate=Interpola
+menu.edit.reverse=Inverti la serie
+menu.edit.addtimeoffset=Imposta lo scarto di orario
+menu.edit.mergetracksegments=Unisci segmenti traccia
+menu.edit.rearrange=Riorganizza waypoint
+menu.edit.rearrange.start=Tutti all'inizio del file
+menu.edit.rearrange.end=Tutti alla fine del file
+menu.edit.rearrange.nearest=Sul punto più vicino
+menu.edit.cutandmove=Taglia e muovi la selezione
+menu.select=Seleziona
+menu.select.all=Seleziona tutto
+menu.select.none=Deseleziona tutto
+menu.select.start=Imposta inizio serie
+menu.select.end=Imposta fine serie
+menu.photo=Foto
+menu.photo.saveexif=Salva su Exif
+menu.photo.connect=Collega al punto
+menu.photo.disconnect=Scollega dal punto
+menu.photo.correlate=Correla tutte le foto
+menu.photo.delete=Rimuovi foto
+menu.view=Visualizza
+menu.view.show3d=Mostra in 3D
+menu.view.browser=Mappa sul browser
+menu.view.browser.google=Google maps
+menu.view.browser.openstreetmap=Openstreetmap
+menu.help=Aiuto
+menu.help.about=Informazioni su Prune
+menu.help.checkversion=Controlla gli aggiornamenti
+# Popup menu for map
+menu.map.zoomin=Zoom +
+menu.map.zoomout=Zoom -
+menu.map.zoomfull=Zoom tutto
+menu.map.newpoint=
+menu.map.connect=Aggancia ai punti
+menu.map.autopan=Autopan
+menu.map.showmap=Mostra sulla mappa
+
+# Dialogs
+dialog.exit.confirm.title=Esci da Prune
+dialog.exit.confirm.text=Le modifiche non sono state salvate. Sei sicuro di voler uscire?
+dialog.openappend.title=Aggiungi ai dati esistenti
+dialog.openappend.text=Aggiungi questi dati a quelli già caricati?
+dialog.deletepoint.title=Cancella Punto
+dialog.deletepoint.deletephoto=Cancella la foto collegata a questo punto?
+dialog.deletephoto.title=Cancella Foto
+dialog.deletephoto.deletepoint=Cancella il punto collegato a questa foto?
+dialog.deleteduplicates.title=Cancella Duplicati
+dialog.deleteduplicates.nonefound=Nessun punto duplicato trovato
+dialog.compresstrack.title=Comprimi la traccia
+dialog.compresstrack.parameter.text=Parametro di compressione (più basso il numero = maggiore la compressione)
+dialog.compresstrack.nonefound=Nessun punto rimosso
+dialog.openoptions.title=Apri opzioni
+dialog.openoptions.filesnippet=Estrai dal file
+dialog.load.table.field=Campo
+dialog.load.table.datatype=Tipo di dato
+dialog.load.table.description=Descrizione
+dialog.delimiter.label=Delimitatore di campo
+dialog.delimiter.comma=Virgola ,
+dialog.delimiter.tab=Tab
+dialog.delimiter.space=Spazio
+dialog.delimiter.semicolon=Punto e virgola ;
+dialog.delimiter.other=Altro
+dialog.openoptions.deliminfo.records=registra, con
+dialog.openoptions.deliminfo.fields=campi
+dialog.openoptions.deliminfo.norecords=Nessun record
+dialog.openoptions.tabledesc=Estrai dal file
+dialog.openoptions.altitudeunits=Unità di misura altitudine
+dialog.jpegload.subdirectories=Includi sottocartelle
+dialog.jpegload.loadjpegswithoutcoords=Includi foto senza coordinate
+dialog.jpegload.loadjpegsoutsidearea=Includi foto fuori dall'area corrente
+dialog.jpegload.progress.title=Caricamento foto
+dialog.jpegload.progress=Per favore aspetta, sto cercando le foto
+dialog.gpsload.title=Carica dati da GPS
+dialog.gpsload.nogpsbabel=Non ho trovato il programma gpsbabel. Continuo?
+dialog.gpsload.device=Nome del Dispositivo
+dialog.gpsload.format=Formato
+dialog.gpsload.getwaypoints=Carica waypoint
+dialog.gpsload.gettracks=Carica tracce
+dialog.saveoptions.title=Salva il file
+dialog.save.fieldstosave=Campi da salvare
+dialog.save.table.field=Campo
+dialog.save.table.hasdata=Ha dati
+dialog.save.table.save=Salva
+dialog.save.headerrow=Regista l'intestazione delle righe
+dialog.save.coordinateunits=Unità di misura coordinate
+dialog.save.altitudeunits=Unità di misura altitudine
+dialog.save.timestampformat=Formato della data
+dialog.save.overwrite.title=File già esistente
+dialog.save.overwrite.text=Questo file esiste già. Sei sicuro di volerlo sovrascrivere?
+dialog.exportkml.title=Esporta in KML
+dialog.exportkml.text=Titolo dei dati
+dialog.exportkml.altitude=Includi altitudine (per aviazione)
+dialog.exportkml.kmz=Comprimi per file kmz
+dialog.exportkml.exportimages=Esporta le anteprime delle immagini per kmz
+dialog.exportgpx.title=Esporta in GPX
+dialog.exportgpx.name=Nome
+dialog.exportgpx.desc=Descrizione
+dialog.exportgpx.includetimestamps=Includi dati temporali
+dialog.exportpov.title=Esporta in POV
+dialog.exportpov.text=Per favore inserisci i parametri per l'esportazione in POV
+dialog.exportpov.font=Font
+dialog.exportpov.camerax=Camera X
+dialog.exportpov.cameray=Camera Y
+dialog.exportpov.cameraz=Camera Z
+dialog.exportpov.modelstyle=Stile del modello
+dialog.exportpov.ballsandsticks=Palle e bacchette
+dialog.exportpov.tubesandwalls=Tubi e pareti
+dialog.exportpov.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D può non essere in grado di visualizzarli.\nSei sicuro di voler continuare?
+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?
+dialog.confirmcutandmove.title=Conferma il taglio e lo spostamento
+dialog.confirmcutandmove.text=Questa traccia contiene informazioni sull'orario di scatto che possono essere messe fuori sequenza dopo lo spostamento\nSei sicuro di voler spostare questa sezione?
+dialog.interpolate.title=Interpola i punti
+dialog.interpolate.parameter.text=Numero di punti da inserire tra i punti selezionati
+dialog.undo.title=Annulla l'azione(i)
+dialog.undo.pretext=Per favore seleziona l'azione(i) da annullare
+dialog.undo.none.title=Non Ã¨ possibile annullare
+dialog.undo.none.text=Nessuna operazione da annullare!
+dialog.clearundo.title=Cancella la lista annulla
+dialog.clearundo.text=Sei sicuro di voler cancellare la lista annulla?\nTutte le informazioni saranno perse!
+dialog.pointedit.title=Edita il punto
+dialog.pointedit.text=Seleziona ogni campo da editare e usa il pulsante 'Edita' per cambiare il valore
+dialog.pointedit.table.field=Campo
+dialog.pointedit.table.value=Valore
+dialog.pointedit.table.changed=Cambiato
+dialog.pointedit.changevalue.text=Inserisci il nuovo valore di questo campo
+dialog.pointedit.changevalue.title=Edita il campo
+dialog.pointnameedit.title=Edita il nome del waypoint
+dialog.pointnameedit.name=Nome del waypoint
+dialog.pointnameedit.uppercase=MAIUSCOLE
+dialog.pointnameedit.lowercase=minuscole
+dialog.pointnameedit.sentencecase=Iniziali Maiuscole
+dialog.addtimeoffset.title=Aggiungi uno scarto temporale
+dialog.addtimeoffset.add=Scarto in aggiunta
+dialog.addtimeoffset.subtract=Scarto in sottrazione
+dialog.addtimeoffset.days=Giorni
+dialog.addtimeoffset.hours=Ore
+dialog.addtimeoffset.minutes=Minuti
+dialog.addtimeoffset.notimestamps=Non posso aggiungere uno scarto temporale a questa selezione perché non contiene informazioni temporali
+dialog.connect.title=Collega la foto al punto
+dialog.connectphoto.clonepoint=Questo punto ha già una foto collegata.\nVuoi fare una copia del punto?
+dialog.saveexif.title=Salva Exif
+dialog.saveexif.intro=Seleziona le foto da salvare usando le caselle di spunta
+dialog.saveexif.nothingtosave=Le coordinate non sono cambiate, niente da registrare
+dialog.saveexif.noexiftool=Non ho trovato il programma exiftool. Continuo?
+dialog.saveexif.table.photoname=Nome della foto
+dialog.saveexif.table.status=Stato
+dialog.saveexif.table.save=Salva
+dialog.saveexif.photostatus.connected=Collegata
+dialog.saveexif.photostatus.disconnected=Scollegata
+dialog.saveexif.photostatus.modified=Modificata
+dialog.saveexif.overwrite=Sovrascrivi il file
+dialog.correlate.title=Correla le foto
+dialog.correlate.notimestamps=Non ci sono informazioni temporali tra i dati dei punti, non c'è 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.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
+dialog.correlate.options.offset=Scarto
+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.limitspanel=Limiti di correlamento
+dialog.correlate.options.notimelimit=Nessun limite di tempo
+dialog.correlate.options.timelimit=Limite di tempo
+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ò essere correlata.\nProva a cambiare lo scarto o correla manualmente almeno una foto.
+dialog.help.help=Per favore vedi\n  http://activityworkshop.net/software/prune/\nper maggiori informazioni e per la guida utente.
+dialog.about.title=Informazioni su Prune
+dialog.about.version=Versione
+dialog.about.build=Build
+dialog.about.summarytext1=Prune Ã¨ un programma per il caricamento, la visione e l'edit di dati provenienti da un GPS.
+dialog.about.summarytext2=È rilasciato sotto la licenza Gnu GPL per l'uso gratuito e aperto ed il suo miglioramento, con validità mondiale.<br>La copia, la ridistribuzione sono permesse e incoraggiate<br>in accordo con i termini inclusi nel file <code>license.txt</code>.
+dialog.about.summarytext3=Per favore vedi <code style="font-weight:bold">http://activityworkshop.net/</code> per maggiori informazioni e per la guida utente.
+dialog.about.languages=Lingue disponibili
+dialog.about.translatedby=Testo italiano di Giovanni Sartor
+dialog.about.systeminfo=Info di sistema
+dialog.about.systeminfo.os=Sistema operativo
+dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo.java3d=Java3d installato
+dialog.about.systeminfo.povray=Povray installato
+dialog.about.systeminfo.exiftool=Exiftool installato
+dialog.about.systeminfo.gpsbabel=Gpsbabel installato
+dialog.about.yes=Sì
+dialog.about.no=No
+dialog.about.credits=Crediti
+dialog.about.credits.code=Il codice Prune code Ã¨ scritto da
+dialog.about.credits.exifcode=Il codice Exif Ã¨ scritto da
+dialog.about.credits.icons=Alcune icone prese da
+dialog.about.credits.translators=Traduttori
+dialog.about.credits.translations=Aiuto nella traduzione da
+dialog.about.credits.devtools=Tool di sviluppo
+dialog.about.credits.othertools=Altri tool
+dialog.about.credits.thanks=Grazie a
+dialog.about.readme=Leggimi
+dialog.checkversion.title=Controlla aggiornamenti
+dialog.checkversion.error=Non posso verificare l'aggiornamento.\nPer favore controlla la connessione internet.
+dialog.checkversion.uptodate=Stai usando l'ultima versione di Prune
+dialog.checkversion.newversion1=Una nuova versione di Prune Ã¨ disponibile! L'ultima versione Ã¨ ora la versione
+dialog.checkversion.newversion2=.
+dialog.checkversion.releasedate1=Questa nuova versione Ã¨ stata rilasciata il
+dialog.checkversion.releasedate2=.
+dialog.checkversion.download=Per scaricare la nuova versione vai a http://activityworkshop.net/software/prune/download.html.
+
+# 3d window
+dialog.3d.title=Visione Prune in 3D
+dialog.3d.altitudecap=Minimo intervallo si altitudine
+dialog.3dlines.title=Griglia di Prune
+dialog.3dlines.empty=Nessuna griglia mostrata!
+dialog.3dlines.intro=Queste sono le linee della griglia per la visione 3D
+
+# Confirm messages || These are displayed as confirmation in the status bar
+confirm.loadfile=Dati caricati da file
+confirm.save.ok1=Salvati con successo
+confirm.save.ok2=punti nel file
+confirm.deleteduplicates.single=duplicato Ã¨ stato cancellato
+confirm.deleteduplicates.multi=duplicati sono stati cancellati
+confirm.deletepoint.single=punto Ã¨ stato rimosso
+confirm.deletepoint.multi=punti sono stati rimossi
+confirm.point.edit=punto editato
+confirm.mergetracksegments=segmeni di traccia uniti
+confirm.reverserange=Intervallo invertito
+confirm.addtimeoffset=Scarto temporale aggiunto
+confirm.rearrangewaypoints=Waypoint riorganizzati
+confirm.cutandmove=Selezione spostata
+confirm.saveexif.ok1=Salvato
+confirm.saveexif.ok2=foto
+confirm.undo.single=operazione annullate
+confirm.undo.multi=operazioni annullate
+confirm.jpegload.single=foto Ã¨ stata aggiunta
+confirm.jpegload.multi=foto sono state aggiunte
+confirm.photo.connect=foto collegata
+confirm.photo.disconnect=foto scollegata
+confirm.correlate.single=foto era correlata
+confirm.correlate.multi=foto erano correlate
+confirm.createpoint=
+
+# Buttons
+button.ok=OK
+button.back=Precedente
+button.next=Prossimo
+button.finish=Fine
+button.cancel=Annulla
+button.overwrite=Sovrascrivi
+button.moveup=Sposta in alto
+button.movedown=Sposta in basso
+button.showlines=Mostra linee
+button.edit=Modifica
+button.exit=Esci
+button.close=Chiudi
+button.continue=Continuare
+button.yes=Sì
+button.no=No
+button.yestoall=Si a tutto
+button.notoall=No a tutto
+button.selectall=Seleziona tutto
+button.selectnone=Deseleziona tutto
+button.preview=Anteprima
+button.guessfields=Campi soluzione
+button.showwebpage=Mostra pagina
+
+# File types
+filetype.txt=File TXT
+filetype.jpeg=File JPG
+filetype.kmlkmz=File KML, KMZ
+filetype.kml=File KML
+filetype.gpx=File GPX
+filetype.pov=File POV
+
+# Display components
+display.nodata=Nessun dato caricato
+display.noaltitudes=I dati della traccia non includono l'altitudine
+details.trackdetails=Dettagli della traccia
+details.notrack=Nessuna traccia caricata
+details.track.points=Punti
+details.track.file=File
+details.track.numfiles=Numero di file
+details.pointdetails=Dettagli punto
+details.index.selected=Indice
+details.index.of=di
+details.nopointselection=Nessun punto selezionato
+details.speed=Velocità
+details.photofile=File della foto
+details.norangeselection=Nessun intervallo selezionato
+details.rangedetails=Dettagli dell'intervallo
+details.range.selected=Selezionato
+details.range.to=a
+details.altitude.to=a
+details.range.climb=Salita
+details.range.descent=Discesa
+details.coordformat=Formato coordinate
+details.distanceunits=Unità di misura distanze
+display.range.time.secs=s
+display.range.time.mins=m
+display.range.time.hours=h
+display.range.time.days=g
+details.range.avespeed=Velocità media
+details.range.avemovingspeed=
+details.waypointsphotos.waypoints=Waypoint
+details.waypointsphotos.photos=Foto
+details.photodetails=Dettagli foto
+details.nophoto=Nessuna foto selezionata
+details.photo.loading=Caricamento
+details.photo.connected=Collegata
+
+# Field names
+fieldname.latitude=Latitudine
+fieldname.longitude=Longitudine
+fieldname.altitude=Altitudine
+fieldname.timestamp=Dati temporali
+fieldname.waypointname=Nome
+fieldname.waypointtype=Tipo
+fieldname.newsegment=Segmento
+fieldname.custom=Custom
+fieldname.prefix=Campo
+fieldname.distance=Distanza
+fieldname.movingdistance=
+fieldname.duration=Durata
+
+# Measurement units
+units.original=Originale
+units.default=Default
+units.metres=Metri
+units.metres.short=m
+units.feet=Feet
+units.feet.short=ft
+units.kilometres=Kilometri
+units.kilometres.short=km
+units.kmh=km/h
+units.miles=Miglia
+units.miles.short=mi
+units.mph=mph
+units.degminsec=Deg-min-sec
+units.degmin=Deg-min
+units.deg=Degrees
+units.iso8601=ISO 8601
+
+# External urls
+url.googlemaps=maps.google.it
+
+# Cardinals for 3d plots
+cardinal.n=N
+cardinal.s=S
+cardinal.e=E
+cardinal.w=O
+
+# Undo operations
+undo.load=carica dati
+undo.loadphotos=carica foto
+undo.editpoint=edita punto
+undo.deletepoint=cancella punto
+undo.deletephoto=rimuovi foto
+undo.deleterange=cancella l'intervallo
+undo.compress=comprimi traccia
+undo.insert=inserisci punti
+undo.deleteduplicates=cancella duplicati
+undo.reverse=inverti l'intervallo
+undo.mergetracksegments=unisci segmenti traccia
+undo.addtimeoffset=aggiungi scarto temporale
+undo.rearrangewaypoints=riorganizza waypoint
+undo.cutandmove=muovi selezione
+undo.connectphoto=collega foto
+undo.disconnectphoto=scollega foto
+undo.correlate=correla foto
+undo.createpoint=
+
+# Error messages
+error.save.dialogtitle=Errore nel salvataggio dati
+error.save.nodata=Nessun dato da salvare
+error.save.failed=Fallito il tentativo di salvare i dati nel file:
+error.saveexif.filenotfound=Non trovato un file di foto
+error.saveexif.cannotoverwrite1=File di foto
+error.saveexif.cannotoverwrite2=è in sola lettura e non può essere sovrascritto. Creo una copia?
+error.load.dialogtitle=Errore nel caricamento dati
+error.load.noread=Non posso leggere il file
+error.load.nopoints=Non ci sono coordinate nel file
+error.load.unknownxml=Formato xml non riconosciuto:
+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.undofailed.title=Impossibile annullare
+error.undofailed.text=Impossibile annullare l'operazione
+error.function.noop.title=La funzione non ha avuto effetto
+error.rearrange.noop=La riorganizzazione dei waypoint non ha avuto effetto
+error.function.notimplemented=Mi dispiace, questa funzione non Ã¨ ancora stata implementata.
+error.function.notavailable.title=Funzione non disponibile
+error.function.nojava3d=Questa funzione richiede la libreria Java3d,\ndisponibile all'indirizzo Sun.com.
+error.3d.title=Errore nella visualizzazione 3D
+error.3d=È avvenuto un errore nella visualizzazione 3D
+error.readme.notfound=Non ho trovato il file Leggimi
+error.osmimage.dialogtitle=Errore nel caricamento nelle immagini della mappa
+error.osmimage.failed=Impossibile caricare le immagini della mappa. Per favore verifica la connessione a internet.
index f3d97c39bcf4986f3f78e2135fd69bcc038b7923..a16c0377e9016e31cc72d4e7c4d994749688c923 100644 (file)
@@ -5,6 +5,7 @@
 menu.file=Plik
 menu.file.open=Otw\u00F3rz
 menu.file.addphotos=Dodaj zdj\u0119cia
+menu.file.loadfromgps=
 menu.file.save=Zapisz
 menu.file.exportkml=Eksportuj jako KML
 menu.file.exportgpx=Eksportuj jako GPX
@@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Usu\u0144 duplikaty
 menu.edit.compress=Skompresuj scie\u017Ck\u0119
 menu.edit.interpolate=Interpoluj punkty
 menu.edit.reverse=Odwr\u00F3\u0107 zakres
-menu.edit.mergetracksegments=Merge track segments
+menu.edit.addtimeoffset=
+menu.edit.mergetracksegments=
 menu.edit.rearrange=Zmie\u0144 kolejno\u015B\u0107 punkt\u00F3w po\u015Brednich
 menu.edit.rearrange.start=Wszystkie na pocz\u0105tek \u015Bcie\u017Cki
 menu.edit.rearrange.end=Wszystkie na koniec \u015Bcie\u017Cki
 menu.edit.rearrange.nearest=Do najbli\u017Cszego punktu
+menu.edit.cutandmove=
 menu.select=Zakres
 menu.select.all=Zaznacz wszystko
 menu.select.none=Usu\u0144 zaznaczenie
@@ -39,24 +42,26 @@ menu.photo.correlate=Skoreluj wszystkie zdj\u0119cia
 menu.photo.delete=Usu\u0144 zdj\u0119cie
 menu.view=Widok
 menu.view.show3d=Poka\u017C 3D model
-menu.view.showmap=Show map
-menu.view.browser=Map in browser
-menu.view.browser.google=Google maps
-menu.view.browser.openstreetmap=Openstreetmap
+menu.view.browser=
+menu.view.browser.google=
+menu.view.browser.openstreetmap=
 menu.help=Pomoc
 menu.help.about=Prune - Informacje
+menu.help.checkversion=
 # Popup menu for map
 menu.map.zoomin=Powi\u0119ksz
 menu.map.zoomout=Zmniejsz
 menu.map.zoomfull=Dostosuj powi\u0119kszenie
+menu.map.newpoint=
 menu.map.connect=Po\u0142\u0105czenie track punkty
 menu.map.autopan=Przesuwanie mapy
+menu.map.showmap=
 
 # Dialogs
 dialog.exit.confirm.title=Zako\u0144cz Prune
-dialog.exit.confirm.text=Your data is not saved. Are you sure you want to exit?
-dialog.openappend.title=Append to existing data
-dialog.openappend.text=Append this data to the data already loaded?
+dialog.exit.confirm.text=
+dialog.openappend.title=
+dialog.openappend.text=
 dialog.deletepoint.title=Usu\u0144 punkt
 dialog.deletepoint.deletephoto=Usu\u0144 zdj\u0119cie attached to this punkt?
 dialog.deletephoto.title=Usu\u0144 zdj\u0119cie
@@ -64,7 +69,7 @@ dialog.deletephoto.deletepoint=Usu\u0144 punkt attached to this zdj\u0119cie?
 dialog.deleteduplicates.title=Usu\u0144 Duplicates
 dialog.deleteduplicates.nonefound=Brak duplikaty found
 dialog.compresstrack.title=Skompresuj scie\u017Ck\u0119
-dialog.compresstrack.parameter.text=Parameter for compression (lower number = more compression)
+dialog.compresstrack.parameter.text=
 dialog.compresstrack.nonefound=No data punkty could be removed
 dialog.openoptions.title=Otw\u00F3rz opcje
 dialog.openoptions.filesnippet=Extract of plik
@@ -77,75 +82,94 @@ dialog.delimiter.tab=Tabulator
 dialog.delimiter.space=Spacja
 dialog.delimiter.semicolon=\u015Arednik ;
 dialog.delimiter.other=Inne
-dialog.openoptions.deliminfo.records=records, with
+dialog.openoptions.deliminfo.records=
 dialog.openoptions.deliminfo.fields=pola
-dialog.openoptions.deliminfo.norecords=No records
+dialog.openoptions.deliminfo.norecords=
 dialog.openoptions.tabledesc=Extract of plik
 dialog.openoptions.altitudeunits=Wysoko\u015B\u0107 jednostki
-dialog.jpegload.subdirectories=Include subdirectories
+dialog.jpegload.subdirectories=
 dialog.jpegload.loadjpegswithoutcoords=Include zdj\u0119cia without koordinaty
+dialog.jpegload.loadjpegsoutsidearea=
 dialog.jpegload.progress.title=Loading zdj\u0119cia
 dialog.jpegload.progress=Please wait while the zdj\u0119cia are searched
+dialog.gpsload.title=
+dialog.gpsload.nogpsbabel=
+dialog.gpsload.device=
+dialog.gpsload.format=
+dialog.gpsload.getwaypoints=
+dialog.gpsload.gettracks=
 dialog.saveoptions.title=Zapisz plik
 dialog.save.fieldstosave=Pola to save
 dialog.save.table.field=Pole
-dialog.save.table.hasdata=Has data
+dialog.save.table.hasdata=
 dialog.save.table.save=Zapisz
-dialog.save.headerrow=Output header row
+dialog.save.headerrow=
 dialog.save.coordinateunits=Wsp\u00f3\u0142rz\u0119dne jednostki
 dialog.save.altitudeunits=Wysoko\u015B\u0107 jednostki
-dialog.save.timestampformat=Timestamp format
+dialog.save.timestampformat=
 dialog.save.overwrite.title=Plik ju\u017C istnieje
 dialog.save.overwrite.text=This plik already exists. Are you sure you want to overwrite the plik?
 dialog.exportkml.title=Eksportuj KML
 dialog.exportkml.text=Tytu\u0142 for the data
-dialog.exportkml.altitude=Include altitudes (for aviation)
-dialog.exportkml.kmz=Compress to make kmz plik
+dialog.exportkml.altitude=
+dialog.exportkml.kmz=
 dialog.exportkml.exportimages=Eksportuj image thumbnails to kmz
-dialog.exportkml.filetype=Pliki KML, KMZ
 dialog.exportgpx.title=Eksportuj jako GPX
 dialog.exportgpx.name=Nazwa
 dialog.exportgpx.desc=Opis
-dialog.exportgpx.filetype=Pliki GPX
+dialog.exportgpx.includetimestamps=
 dialog.exportpov.title=Eksportuj jako POV
-dialog.exportpov.text=Please enter the parameters for the POV export
+dialog.exportpov.text=
 dialog.exportpov.font=Czcionka
 dialog.exportpov.camerax=Camera X
 dialog.exportpov.cameray=Camera Y
 dialog.exportpov.cameraz=Camera Z
-dialog.exportpov.filetype=Pliki POV
+dialog.exportpov.modelstyle=
+dialog.exportpov.ballsandsticks=
+dialog.exportpov.tubesandwalls=
 dialog.exportpov.warningtracksize=This track has a large number of punkty, which Java3D might not be able to display.\nCzy chcesz kontynuowa\u0107?
-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?
+dialog.confirmreversetrack.title=
+dialog.confirmreversetrack.text=
+dialog.confirmcutandmove.title=
+dialog.confirmcutandmove.text=
 dialog.interpolate.title=Interpoluj punkty
 dialog.interpolate.parameter.text=Number of punkty to insert between selected punkty
 dialog.undo.title=Cofnij action(s)
-dialog.undo.pretext=Please select the action(s) to undo
-dialog.undo.none.title=Cannot undo
-dialog.undo.none.text=No operations to undo!
+dialog.undo.pretext=
+dialog.undo.none.title=
+dialog.undo.none.text=
 dialog.clearundo.title=Wyczy\u015B\u0107 list\u0119 zmian
-dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost!
+dialog.clearundo.text=
 dialog.pointedit.title=Edytuj punkt
-dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value
+dialog.pointedit.text=
 dialog.pointedit.table.field=Pole
-dialog.pointedit.table.value=Value
+dialog.pointedit.table.value=
 dialog.pointedit.table.changed=Zmieniony
-dialog.pointedit.changevalue.text=Enter the new value for this field
+dialog.pointedit.changevalue.text=
 dialog.pointedit.changevalue.title=Edytuj field
 dialog.pointnameedit.title=Zmie\u0144 nazw\u0119 punktu po\u015Bredniego
 dialog.pointnameedit.name=Waypoint nazwa
-dialog.pointnameedit.uppercase=UPPER case
-dialog.pointnameedit.lowercase=lower case
-dialog.pointnameedit.sentencecase=Sentence case
+dialog.pointnameedit.uppercase=
+dialog.pointnameedit.lowercase=
+dialog.pointnameedit.sentencecase=
+dialog.addtimeoffset.title=
+dialog.addtimeoffset.add=
+dialog.addtimeoffset.subtract=
+dialog.addtimeoffset.days=
+dialog.addtimeoffset.hours=
+dialog.addtimeoffset.minutes=Minuty
+dialog.addtimeoffset.notimestamps=
+dialog.connect.title=
+dialog.connectphoto.clonepoint=
 dialog.saveexif.title=Zapisz Exif
 dialog.saveexif.intro=Select the zdj\u0119cia to save using the checkboxes
-dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save
-dialog.saveexif.noexiftool=No exiftool program could be found. Continue?
+dialog.saveexif.nothingtosave=
+dialog.saveexif.noexiftool=
 dialog.saveexif.table.photoname=Nazwa zdj\u0119cie
 dialog.saveexif.table.status=Status
 dialog.saveexif.table.save=Zapisz
-dialog.saveexif.photostatus.connected=Connected
-dialog.saveexif.photostatus.disconnected=Disconnected
+dialog.saveexif.photostatus.connected=
+dialog.saveexif.photostatus.disconnected=
 dialog.saveexif.photostatus.modified=Zmodyfikowany
 dialog.saveexif.overwrite=Overwrite pliki
 dialog.correlate.title=Skoreluj zdj\u0119cie
@@ -153,74 +177,85 @@ dialog.correlate.notimestamps=There are no timestamps in the data punkty, so the
 dialog.correlate.nouncorrelatedphotos=There are no uncorrelated zdj\u0119cia.\nAre you sure you want to continue?
 dialog.correlate.photoselect.intro=Select one of these correlated zdj\u0119cia to use as the time offset
 dialog.correlate.photoselect.photoname=Nazwa zdj\u0119cie
-dialog.correlate.photoselect.timediff=Time difference
+dialog.correlate.photoselect.timediff=
 dialog.correlate.photoselect.photolater=Zdj\u0119cie later
 dialog.correlate.options.tip=Tip: By manually correlating at least one zdj\u0119cie, 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
+dialog.correlate.options.intro=
+dialog.correlate.options.offsetpanel=
+dialog.correlate.options.offset=
 dialog.correlate.options.offset.hours=hours,
 dialog.correlate.options.offset.minutes=minuty i
 dialog.correlate.options.offset.seconds=seconds
 dialog.correlate.options.photolater=Zdj\u0119cie po punkt
 dialog.correlate.options.pointlater=Punkt po zdj\u0119cie
-dialog.correlate.options.limitspanel=Correlation limits
-dialog.correlate.options.notimelimit=No time limit
-dialog.correlate.options.timelimit=Time limit
-dialog.correlate.options.nodistancelimit=No distance limit
-dialog.correlate.options.distancelimit=Distance limit
-dialog.correlate.options.correlate=Correlate
+dialog.correlate.options.limitspanel=
+dialog.correlate.options.notimelimit=
+dialog.correlate.options.timelimit=
+dialog.correlate.options.nodistancelimit=
+dialog.correlate.options.distancelimit=
+dialog.correlate.options.correlate=
 dialog.correlate.alloutsiderange=All zdj\u0119cia are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one zdj\u0119cie.
-dialog.map.title=Prune map
 dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\npo wi\u0119cej informacji and user guides.
 dialog.about.title=Prune Informacje
 dialog.about.version=Wersja
 dialog.about.build=Build
-dialog.about.summarytext1=Prune is a program for loading, displaying and editing data from GPS receivers.
-dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.<br>Copying, redistribution and modification are permitted and encouraged<br>according to the conditions in the included <code>license.txt</code> file.
-dialog.about.summarytext3=Please see <code style="font-weight:bold">http://activityworkshop.net/</code> for more informacji and user guides.
+dialog.about.summarytext1=
+dialog.about.summarytext2=
+dialog.about.summarytext3=
+dialog.about.languages=
 dialog.about.translatedby=Tekst po polsku by Piotr.
-dialog.about.systeminfo=System info
-dialog.about.systeminfo.os=Operating System
-dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo=
+dialog.about.systeminfo.os=
+dialog.about.systeminfo.java=
 dialog.about.systeminfo.java3d=Java3d zainstalowana
 dialog.about.systeminfo.povray=Povray zainstalowana
 dialog.about.systeminfo.exiftool=Exiftool zainstalowana
 dialog.about.systeminfo.gpsbabel=Gpsbabel zainstalowana
 dialog.about.yes=Tak
 dialog.about.no=Nie
-dialog.about.credits=Credits
-dialog.about.credits.code=Prune code written by
-dialog.about.credits.exifcode=Exif code by
-dialog.about.credits.icons=Some icons taken from
-dialog.about.credits.translators=Translators
+dialog.about.credits=
+dialog.about.credits.code=
+dialog.about.credits.exifcode=
+dialog.about.credits.icons=
+dialog.about.credits.translators=
 dialog.about.credits.translations=T\u0142umaczenie helped by
-dialog.about.credits.devtools=Development tools
-dialog.about.credits.othertools=Other tools
+dialog.about.credits.devtools=
+dialog.about.credits.othertools=
 dialog.about.credits.thanks=Dzi\u0119kuje to
 dialog.about.readme=Readme
+dialog.checkversion.title=
+dialog.checkversion.error=
+dialog.checkversion.uptodate=
+dialog.checkversion.newversion1=
+dialog.checkversion.newversion2=
+dialog.checkversion.releasedate1=
+dialog.checkversion.releasedate2=
+dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html.
 
 # 3d window
 dialog.3d.title=Prune tr\u00f3jwymiarowa model
-dialog.3d.altitudecap=Minimum altitude range
-dialog.3dlines.title=Prune gridlines
-dialog.3dlines.empty=No gridlines to display!
-dialog.3dlines.intro=These are the gridlines for the three-d view
+dialog.3d.altitudecap=
+dialog.3dlines.title=
+dialog.3dlines.empty=
+dialog.3dlines.intro=
 
 # Confirm messages || These are displayed as confirmation in the status bar
-confirm.loadfile=Data loaded from file
-confirm.save.ok1=Successfully saved
+confirm.loadfile=
+confirm.save.ok1=
 confirm.save.ok2=punkty to plik
-confirm.deleteduplicates.single=duplicate was deleted
-confirm.deleteduplicates.multi=duplicates were deleted
+confirm.deleteduplicates.single=
+confirm.deleteduplicates.multi=
 confirm.deletepoint.single=data punkt was removed
 confirm.deletepoint.multi=data punkty were removed
-confirm.undo.single=operation undone
-confirm.undo.multi=operations undone
+confirm.undo.single=
+confirm.undo.multi=
 confirm.point.edit=punkt edited
-confirm.mergetracksegments=track segments merged
-confirm.reverserange=Range reversed
-confirm.saveexif.ok1=Saved
+confirm.mergetracksegments=
+confirm.reverserange=
+confirm.addtimeoffset=
+confirm.rearrangewaypoints=
+confirm.cutandmove=
+confirm.saveexif.ok1=
 confirm.saveexif.ok2=zdj\u0119cia pliki
 confirm.jpegload.single=zdj\u0119cie was added
 confirm.jpegload.multi=zdj\u0119cia were added
@@ -228,6 +263,7 @@ confirm.photo.connect=zdj\u0119cie connected
 confirm.photo.disconnect=zdj\u0119cie disconnected
 confirm.correlate.single=zdj\u0119cie was correlated
 confirm.correlate.multi=zdj\u0119cia were correlated
+confirm.createpoint=
 
 # Buttons
 button.ok=OK
@@ -250,7 +286,16 @@ button.notoall=Nie wszystko
 button.selectall=Zaznacz wszystko
 button.selectnone=Odznacz
 button.preview=Podgl\u0105d
-button.guessfields=Guess fields
+button.guessfields=
+button.showwebpage=
+
+# File types
+filetype.txt=Pliki TXT
+filetype.jpeg=Pliki JPG
+filetype.kmlkmz=Pliki KML, KMZ
+filetype.kml=Pliki KML
+filetype.gpx=Pliki GPX
+filetype.pov=Pliki POV
 
 # Display components
 display.nodata=Brak za\u0142awadowanych danych
@@ -264,41 +309,43 @@ details.pointdetails=Punkt szczeg\u00F3\u0142y
 details.index.selected=Indeks
 details.index.of=z
 details.nopointselection=Brak punkt zaznaczenia
-details.speed=Speed
+details.speed=
 details.photofile=Plik zdj\u0119cie
 details.norangeselection=Brak range zaznaczenia
 details.rangedetails=Range szczeg\u00F3\u0142y
-details.range.selected=Selected
-details.range.to=to
-details.altitude.to=to
-details.range.climb=Climb
-details.range.descent=Descent
+details.range.selected=
+details.range.to=
+details.altitude.to=
+details.range.climb=
+details.range.descent=
 details.coordformat=Format wsp\u00f3\u0142rz\u0119dne
 details.distanceunits=Jednostki dystansowy
 display.range.time.secs=s
 display.range.time.mins=m
 display.range.time.hours=h
 display.range.time.days=d
-details.range.avespeed=Ave speed
+details.range.avespeed=
+details.range.avemovingspeed=
 details.waypointsphotos.waypoints=Waypoints
 details.waypointsphotos.photos=Zdj\u0119cia
 details.photodetails=Zdj\u0119cie szczeg\u00F3\u0142y
 details.nophoto=Brak zdj\u0119cie zaznaczenia
 details.photo.loading=Wczytywanie
-details.photo.connected=Connected
+details.photo.connected=
 
 # Field names
 fieldname.latitude=Szeroko\u015B\u0107
 fieldname.longitude=D\u0142ugo\u015B\u0107
 fieldname.altitude=Wysoko\u015B\u0107
-fieldname.timestamp=Timestamp
+fieldname.timestamp=
 fieldname.waypointname=Nazwa
 fieldname.waypointtype=Type
-fieldname.newsegment=Segment
+fieldname.newsegment=
 fieldname.custom=U\u017Cytkownika
 fieldname.prefix=Pole
 fieldname.distance=Dystansowy
-fieldname.duration=Duration
+fieldname.movingdistance=
+fieldname.duration=
 
 # Measurement units
 units.original=Oryginalny
@@ -334,19 +381,22 @@ undo.editpoint=edycja punkt
 undo.deletepoint=usu\u0144 punkt
 undo.deletephoto=remove zdj\u0119cie
 undo.deleterange=usu\u0144 range
-undo.compress=compress track
+undo.compress=
 undo.insert=insert punkty
 undo.deleteduplicates=usu\u0144 duplicates
-undo.reverse=reverse range
-undo.mergetracksegments=merge track segments
-undo.rearrangewaypoints=rearrange waypoints
+undo.reverse=
+undo.mergetracksegments=
+undo.addtimeoffset=
+undo.rearrangewaypoints=
+undo.cutandmove=
 undo.connectphoto=connect zdj\u0119cie
 undo.disconnectphoto=disconnect zdj\u0119cie
 undo.correlate=correlate zdj\u0119cia
+undo.createpoint=
 
 # Error messages
-error.save.dialogtitle=Error saving data
-error.save.nodata=No data to save
+error.save.dialogtitle=
+error.save.nodata=
 error.save.failed=Failed to save the data to plik:
 error.saveexif.filenotfound=Failed to find zdj\u0119cie plik
 error.saveexif.cannotoverwrite1=Zdj\u0119cie plik
@@ -365,11 +415,11 @@ error.undofailed.title=Cofnij failed
 error.undofailed.text=Nie mo\u017Cna cofn\u0105\u0107
 error.function.noop.title=Funkcji had no effect
 error.rearrange.noop=Rearranging waypoints had no effect
-error.function.notimplemented=Sorry, this funkcji has not yet been implemented.
+error.function.notimplemented=
 error.function.notavailable.title=Funkcji nie jest dost\u0119pny
 error.function.nojava3d=This funkcji requires the Java3d library,\navailable from Sun.com.
 error.3d.title=B\u0142\u0105d in 3d display
 error.3d=A b\u0142\u0105d occurred with the 3d display
-error.readme.notfound=Readme file not found
-error.osmimage.dialogtitle=Error loading map images
-error.osmimage.failed=Failed to load map images. Please check internet connection.
+error.readme.notfound=
+error.osmimage.dialogtitle=
+error.osmimage.failed=
index c1bf3dfc1a8e05d202bf00923f2cb94fc37d1017..4d9d2be7feb4a06d3e1968b078871352f9e5f087 100644 (file)
@@ -7,6 +7,7 @@ import javax.swing.JFrame;
 import javax.swing.JOptionPane;
 
 import tim.prune.App;
+import tim.prune.Config;
 import tim.prune.I18nManager;
 import tim.prune.load.xml.XmlFileLoader;
 
@@ -42,14 +43,27 @@ public class FileLoader
         */
        public void openFile()
        {
+               // Construct file chooser if necessary
                if (_fileChooser == null)
+               {
                        _fileChooser = new JFileChooser();
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.kml", new String[] {"kml"}));
+                       _fileChooser.setAcceptAllFileFilterUsed(true);
+                       // start from directory in config if already set (by load jpegs)
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
+               }
+               // Show the open dialog
                if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                {
                        File file = _fileChooser.getSelectedFile();
                        // Check file exists and is readable
                        if (file != null && file.exists() && file.canRead())
                        {
+                               // Store directory in config for later
+                               Config.setWorkingDirectory(file.getParentFile());
                                // Check file type to see if it's xml or just normal text
                                String fileExtension = file.getName().toLowerCase();
                                if (fileExtension.length() > 4)
diff --git a/tim/prune/load/GenericFileFilter.java b/tim/prune/load/GenericFileFilter.java
new file mode 100644 (file)
index 0000000..3af4d9e
--- /dev/null
@@ -0,0 +1,123 @@
+package tim.prune.load;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+import tim.prune.I18nManager;
+
+/**
+ * Class to act as a generic file filter based on file extension
+ */
+public class GenericFileFilter extends FileFilter
+{
+       /** Filter description for display */
+       private String _filterDesc = null;
+       /** Array of allowed three-character suffixes */
+       private String[] _threeCharSuffixes = null;
+       /** Array of allowed four-character suffixes */
+       private String[] _fourCharSuffixes = null;
+
+
+       /**
+        * Constructor
+        * @param inDescKey key for filter description
+        * @param inSuffixes array of allowed 3- and 4-character file suffixes
+        */
+       public GenericFileFilter(String inDescKey, String[] inSuffixes)
+       {
+               _filterDesc = I18nManager.getText(inDescKey);
+               if (inSuffixes != null && inSuffixes.length > 0)
+               {
+                       _threeCharSuffixes = new String[inSuffixes.length];
+                       _fourCharSuffixes = new String[inSuffixes.length];
+                       int threeIndex = 0, fourIndex = 0;
+                       for (int i=0; i<inSuffixes.length; i++)
+                       {
+                               String suffix = inSuffixes[i];
+                               if (suffix != null)
+                               {
+                                       suffix = suffix.trim().toLowerCase();
+                                       if (suffix.length() == 3) {
+                                               _threeCharSuffixes[threeIndex++] = suffix;
+                                       }
+                                       else if (suffix.length() == 4) {
+                                               _fourCharSuffixes[fourIndex++] = suffix;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Check whether to accept the specified file or not
+        * @see javax.swing.filechooser.FileFilter#accept(java.io.File)
+        */
+       public boolean accept(File inFile)
+       {
+               return inFile.isDirectory() || acceptFilename(inFile.getName());
+       }
+
+       /**
+        * Check whether to accept the given filename
+        * @param inName name of file
+        * @return true if accepted, false otherwise
+        */
+       public boolean acceptFilename(String inName)
+       {
+               if (inName != null)
+               {
+                       int nameLen = inName.length();
+                       if (nameLen > 4)
+                       {
+                               // Check for three character suffixes
+                               char currChar = inName.charAt(nameLen - 4);
+                               if (currChar == '.')
+                               {
+                                       return acceptFilename(inName.substring(nameLen - 3).toLowerCase(), _threeCharSuffixes);
+                               }
+                               // check for four character suffixes
+                               currChar = inName.charAt(nameLen - 5);
+                               if (currChar == '.')
+                               {
+                                       return acceptFilename(inName.substring(nameLen - 4).toLowerCase(), _fourCharSuffixes);
+                               }
+                       }
+               }
+               // Not matched so don't accept
+               return false;
+       }
+
+       /**
+        * Check whether to accept the given filename
+        * @param inSuffixToCheck suffix to check
+        * @param inAllowedSuffixes array of allowed suffixes
+        * @return true if accepted, false otherwise
+        */
+       public boolean acceptFilename(String inSuffixToCheck, String[] inAllowedSuffixes)
+       {
+               if (inSuffixToCheck != null && inAllowedSuffixes != null)
+               {
+                       // Loop over allowed suffixes
+                       for (int i=0; i<inAllowedSuffixes.length; i++)
+                       {
+                               if (inAllowedSuffixes[i] != null && inSuffixToCheck.equals(inAllowedSuffixes[i]))
+                               {
+                                       return true;
+                               }
+                       }
+               }
+               // Fallen through so not allowed
+               return false;
+       }
+
+
+       /**
+        * Get description
+        * @see javax.swing.filechooser.FileFilter#getDescription()
+        */
+       public String getDescription()
+       {
+               return _filterDesc;
+       }
+
+}
diff --git a/tim/prune/load/GpsLoader.java b/tim/prune/load/GpsLoader.java
new file mode 100644 (file)
index 0000000..b803844
--- /dev/null
@@ -0,0 +1,295 @@
+package tim.prune.load;
+
+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.BufferedReader;
+import java.io.InputStreamReader;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import tim.prune.App;
+import tim.prune.Config;
+import tim.prune.ExternalTools;
+import tim.prune.I18nManager;
+import tim.prune.data.Altitude;
+import tim.prune.load.xml.XmlFileLoader;
+import tim.prune.load.xml.XmlHandler;
+
+/**
+ * Class to manage the loading of GPS data using GpsBabel
+ */
+public class GpsLoader implements Runnable
+{
+       private App _app = null;
+       private JFrame _parentFrame = null;
+       private boolean _gpsBabelChecked = false;
+       private JDialog _dialog = null;
+       private JTextField _deviceField = null, _formatField = null;
+       private JCheckBox _waypointCheckbox = null, _trackCheckbox = null;
+       private JButton _okButton = null;
+       private JProgressBar _waypointProgressBar = null, _trackProgressBar = null;
+       private boolean _cancelled = false;
+
+
+       /**
+        * Constructor
+        * @param inApp Application object to inform of data load
+        * @param inParentFrame parent frame to reference for dialogs
+        */
+       public GpsLoader(App inApp, JFrame inParentFrame)
+       {
+               _app = inApp;
+               _parentFrame = inParentFrame;
+       }
+
+
+       /**
+        * Open the GUI to select options and start the load
+        */
+       public void openDialog()
+       {
+               // Check if gpsbabel looks like it's installed
+               if (_gpsBabelChecked || ExternalTools.isGpsbabelInstalled()
+                       || JOptionPane.showConfirmDialog(_dialog,
+                               I18nManager.getText("dialog.gpsload.nogpsbabel"),
+                               I18nManager.getText("dialog.gpsload.title"),
+                               JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
+               {
+                       _gpsBabelChecked = true;
+                       // Make dialog window
+                       if (_dialog == null)
+                       {
+                               _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.gpsload.title"), true);
+                               _dialog.setLocationRelativeTo(_parentFrame);
+                               _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                               _dialog.getContentPane().add(makeDialogComponents());
+                               _dialog.pack();
+                       }
+                       // Initialise progress bars, buttons
+                       enableOkButton();
+                       setupProgressBars(true);
+                       _dialog.show();
+               }
+       }
+
+
+       /**
+        * @return a panel containing the main dialog components
+        */
+       private JPanel makeDialogComponents()
+       {
+               JPanel outerPanel = new JPanel();
+               outerPanel.setLayout(new BorderLayout());
+               // Main panel with options etc
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+
+               // text fields for options
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(0, 2, 10, 3));
+               JLabel deviceLabel = new JLabel(I18nManager.getText("dialog.gpsload.device"));
+               deviceLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               gridPanel.add(deviceLabel);
+               _deviceField = new JTextField(Config.getGpsDevice(), 12);
+               gridPanel.add(_deviceField);
+               JLabel formatLabel = new JLabel(I18nManager.getText("dialog.gpsload.format"));
+               formatLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               gridPanel.add(formatLabel);
+               _formatField = new JTextField(Config.getGpsFormat(), 12);
+               gridPanel.add(_formatField);
+               gridPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+               gridPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 20));
+               mainPanel.add(gridPanel);
+
+               // checkboxes
+               ChangeListener checkboxListener = new ChangeListener() {
+                       public void stateChanged(ChangeEvent e)
+                       {
+                               enableOkButton();
+                       }
+               };
+               _waypointCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsload.getwaypoints"), true);
+               _waypointCheckbox.addChangeListener(checkboxListener);
+               _waypointCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+               mainPanel.add(_waypointCheckbox);
+               _trackCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsload.gettracks"), true);
+               _trackCheckbox.addChangeListener(checkboxListener);
+               _trackCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+               mainPanel.add(_trackCheckbox);
+               // progress bars (initially invisible)
+               _waypointProgressBar = new JProgressBar(0, 10);
+               mainPanel.add(_waypointProgressBar);
+               _trackProgressBar = new JProgressBar(0, 10);
+               mainPanel.add(_trackProgressBar);
+               outerPanel.add(mainPanel, BorderLayout.NORTH);
+
+               // Lower panel with ok and cancel buttons
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               // start thread to call gpsbabel
+                               _cancelled = false;
+                               new Thread(GpsLoader.this).start();
+                       }
+               });
+               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);
+               outerPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return outerPanel;
+       }
+
+
+       /**
+        * @param inStart true if the dialog is restarting
+        */
+       private void setupProgressBars(boolean inStart)
+       {
+               // set visibility
+               _waypointProgressBar.setVisible(!inStart && _waypointCheckbox.isSelected());
+               _trackProgressBar.setVisible(!inStart && _trackCheckbox.isSelected());
+               // set indeterminate flags, initial value
+               _waypointProgressBar.setIndeterminate(false);
+               _waypointProgressBar.setValue(0);
+               _trackProgressBar.setIndeterminate(false);
+               _trackProgressBar.setValue(0);
+       }
+
+
+       /**
+        * Enable or disable the ok button
+        */
+       private void enableOkButton()
+       {
+               _okButton.setEnabled(_waypointCheckbox.isSelected() || _trackCheckbox.isSelected());
+       }
+
+
+       /**
+        * Run method for performing tasks in separate thread
+        */
+       public void run()
+       {
+               _okButton.setEnabled(false);
+               setupProgressBars(false);
+               if (_waypointCheckbox.isSelected())
+               {
+                       _waypointProgressBar.setIndeterminate(true);
+                       _trackProgressBar.setIndeterminate(false);
+                       try
+                       {
+                               callGpsBabel(true);
+                       }
+                       catch (Exception e)
+                       {
+                               JOptionPane.showMessageDialog(_dialog, e.getMessage(),
+                                       I18nManager.getText("dialog.gpsload.title"), JOptionPane.ERROR_MESSAGE);
+                               _cancelled = true;
+                       }
+               }
+               // Exit if cancelled or failed
+               if (_cancelled) {
+                       setupProgressBars(true);
+                       enableOkButton();
+                       return;
+               }
+               if (_trackCheckbox.isSelected())
+               {
+                       _waypointProgressBar.setIndeterminate(false);
+                       _waypointProgressBar.setValue(10);
+                       _trackProgressBar.setIndeterminate(true);
+                       try
+                       {
+                               callGpsBabel(false);
+                       }
+                       catch (Exception e)
+                       {
+                               JOptionPane.showMessageDialog(_dialog, e.getMessage(),
+                                       I18nManager.getText("dialog.gpsload.title"), JOptionPane.ERROR_MESSAGE);
+                               _cancelled = true;
+                       }
+               }
+               setupProgressBars(true);
+               enableOkButton();
+
+               // Close dialog
+               if (!_cancelled) {
+                       _dialog.dispose();
+               }
+       }
+
+
+       /**
+        * Execute the call to gpsbabel and pass the results back to the app
+        * @param inWaypoints true to get waypoints, false to get track data
+        */
+       private void callGpsBabel(boolean inWaypoints) throws Exception
+       {
+               // Set up command to call gpsbabel
+               String[] commands = {"gpsbabel", null, "-i", _formatField.getText(), "-f", _deviceField.getText(), "-o", "gpx", "-F", "-"};
+               commands[1] = inWaypoints?"-w":"-t";
+
+               String errorMessage = "";
+               XmlHandler handler = null;
+               Process process = Runtime.getRuntime().exec(commands);
+
+               // Pass input stream to try to parse the xml
+               try
+               {
+                       XmlFileLoader xmlLoader = new XmlFileLoader(_app, _parentFrame);
+                       SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+                       saxParser.parse(process.getInputStream(), xmlLoader);
+                       handler = xmlLoader.getHandler();
+                       if (handler == null) {
+                               errorMessage = "Null handler";
+                       }
+               }
+               catch (Exception e) {
+                       errorMessage = e.getMessage();
+               }
+
+               // Read the error stream to see if there's a better error message there
+               BufferedReader r = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+               String line = null;
+               String errorMessage2 = "";
+               while ((line = r.readLine()) != null) {
+                       errorMessage2 += line + "\n";
+               }
+               if (errorMessage2.length() > 0) {errorMessage = errorMessage2;}
+               if (errorMessage.length() > 0) {throw new Exception(errorMessage);}
+
+               // Send data back to app
+               boolean append = _waypointCheckbox.isSelected() && !inWaypoints;
+               _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
+                       Altitude.FORMAT_METRES, _deviceField.getText(), append);
+       }
+}
index cab6c565524bc5a48bcab9b9e4b80b56b36660ed..c506236f44eb3e3e3aa2a37ce6683cd382da85de 100644 (file)
@@ -18,9 +18,11 @@ import javax.swing.JPanel;
 import javax.swing.JProgressBar;
 
 import tim.prune.App;
+import tim.prune.Config;
 import tim.prune.I18nManager;
 import tim.prune.data.Altitude;
 import tim.prune.data.DataPoint;
+import tim.prune.data.LatLonRectangle;
 import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
 import tim.prune.data.Photo;
@@ -39,12 +41,15 @@ public class JpegLoader implements Runnable
        private App _app = null;
        private JFrame _parentFrame = null;
        private JFileChooser _fileChooser = null;
+       private GenericFileFilter _fileFilter = null;
        private JCheckBox _subdirCheckbox = null;
        private JCheckBox _noExifCheckbox = null;
+       private JCheckBox _outsideAreaCheckbox = null;
        private JDialog _progressDialog   = null;
        private JProgressBar _progressBar = null;
        private int[] _fileCounts = null;
        private boolean _cancelled = false;
+       private LatLonRectangle _trackRectangle = null;
        private TreeSet _photos = null;
 
 
@@ -57,31 +62,45 @@ public class JpegLoader implements Runnable
        {
                _app = inApp;
                _parentFrame = inParentFrame;
+               String[] fileTypes = {"jpg", "jpe", "jpeg"};
+               _fileFilter = new GenericFileFilter("filetype.jpeg", fileTypes);
        }
 
 
        /**
         * Open the GUI to select options and start the load
+        * @param inRectangle track rectangle
         */
-       public void openDialog()
+       public void openDialog(LatLonRectangle inRectangle)
        {
-               // TODO: Allow restriction of load area, either to current track or manually-entered range
+               // 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("menu.file.addphotos"));
                        _subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories"));
                        _subdirCheckbox.setSelected(true);
                        _noExifCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegswithoutcoords"));
                        _noExifCheckbox.setSelected(true);
+                       _outsideAreaCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegsoutsidearea"));
+                       _outsideAreaCheckbox.setSelected(true);
                        JPanel panel = new JPanel();
                        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                        panel.add(_subdirCheckbox);
                        panel.add(_noExifCheckbox);
+                       panel.add(_outsideAreaCheckbox);
                        _fileChooser.setAccessory(panel);
+                       // start from directory in config if already set by other operations
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
                }
+               // enable/disable track checkbox
+               _trackRectangle = inRectangle;
+               _outsideAreaCheckbox.setEnabled(_trackRectangle != null && !_trackRectangle.isEmpty());
+               // Show file dialog to choose file / directory(ies)
                if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                {
                        // Bring up dialog before starting
@@ -170,9 +189,7 @@ public class JpegLoader implements Runnable
                }
                else
                {
-                       // Found some photos to load
-                       // TODO: Load jpeg information into dialog for confirmation?
-                       // Pass information back to app
+                       // Found some photos to load - pass information back to app
                        _app.informPhotosLoaded(_photos);
                }
        }
@@ -231,7 +248,8 @@ public class JpegLoader implements Runnable
                _progressBar.repaint();
 
                // Check whether filename corresponds with accepted filenames
-               if (!acceptPhotoFilename(inFile.getName())) {return;}
+               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);
@@ -251,6 +269,7 @@ public class JpegLoader implements Runnable
                                // Make DataPoint and attach to Photo
                                DataPoint point = createDataPoint(jpegData);
                                point.setPhoto(photo);
+                               point.setSegmentStart(true);
                                photo.setDataPoint(point);
                                photo.setOriginalStatus(PhotoStatus.TAGGED);
                                _fileCounts[3]++;
@@ -265,17 +284,13 @@ public class JpegLoader implements Runnable
                catch (JpegException jpe) { // don't list errors, just count them
                }
                // Use file timestamp if exif timestamp isn't available
-               if (photo.getTimestamp() == null)
-               {
+               if (photo.getTimestamp() == null) {
                        photo.setTimestamp(new Timestamp(inFile.lastModified()));
-                       //System.out.println("No exif, using timestamp from file: " + inFile.lastModified() + " -> " + photo.getTimestamp().getText());
                }
-               else
-               {
-                       //System.out.println("timestamp from file = " + photo.getTimestamp().getText());
-               }
-               // Add the photo if it's got a point or if pointless photos should be added
-               if (photo.getDataPoint() != null || _noExifCheckbox.isSelected())
+               // 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);
                }
@@ -300,6 +315,10 @@ public class JpegLoader implements Runnable
                                File file = inFiles[i];
                                if (file.exists() && file.canRead())
                                {
+                                       // Store first directory in config for later
+                                       if (i == 0 && inFirstDir) {
+                                               Config.setWorkingDirectory(file.isDirectory()?file:file.getParentFile());
+                                       }
                                        // Check whether it's a file or a directory
                                        if (file.isFile())
                                        {
@@ -393,34 +412,4 @@ public class JpegLoader implements Runnable
                catch (NumberFormatException nfe) {}
                return stamp;
        }
-
-
-       /**
-        * Check whether to accept the given filename
-        * @param inName name of file
-        * @return true if accepted, false otherwise
-        */
-       private static boolean acceptPhotoFilename(String inName)
-       {
-               if (inName != null && inName.length() > 4)
-               {
-                       // Check for three-character file extensions jpg and jpe
-                       String lastFour = inName.substring(inName.length() - 4).toLowerCase();
-                       if (lastFour.equals(".jpg") || lastFour.equals(".jpe"))
-                       {
-                               return true;
-                       }
-                       // If not found, check for file extension jpeg
-                       if (inName.length() > 5)
-                       {
-                               String lastFive = inName.substring(inName.length() - 5).toLowerCase();
-                               if (lastFive.equals(".jpeg"))
-                               {
-                                       return true;
-                               }
-                       }
-               }
-               // Not matched so don't accept
-               return false;
-       }
 }
index f0408da1c4eaab15363b1e1bde2c6c3ae184e994..0d7ef481056d515595cedf2ea217f5c2ff66e54e 100644 (file)
@@ -267,6 +267,7 @@ public class TextFileLoader
                _cardPanel.setLayout(_layout);
                JPanel firstCard = new JPanel();
                firstCard.setLayout(new BorderLayout());
+               firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
 
                JPanel delimsPanel = new JPanel();
                delimsPanel.setLayout(new GridLayout(0, 2));
@@ -310,6 +311,7 @@ public class TextFileLoader
                // Second screen, for field order selection
                JPanel secondCard = new JPanel();
                secondCard.setLayout(new BorderLayout());
+               secondCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
                // table for file contents
                _fileExtractTableModel = new FileExtractTableModel();
                JTable extractTable = new JTable(_fileExtractTableModel);
@@ -319,6 +321,8 @@ public class TextFileLoader
                secondCard.add(makeLabelledPanel("dialog.openoptions.tabledesc", tableScrollPane), BorderLayout.NORTH);
                JPanel innerPanel2 = new JPanel();
                innerPanel2.setLayout(new BorderLayout());
+               innerPanel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
                _fieldTable = new JTable(new FieldSelectionTableModel());
                _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                // add listener for selected table row
index 987841e6935cc4c680a34d5b7db2ac3c24dd8fe5..8801d00e08f6db1083a7617b9e9f74106f0e0f69 100644 (file)
@@ -145,4 +145,12 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
                }
                super.endElement(uri, localName, qName);
        }
+
+       /**
+        * @return The Xml handler used for the parsing
+        */
+       public XmlHandler getHandler()
+       {
+               return _handler;
+       }
 }
index 44adc9fd4a6302b5e6c0b28b47d223961527d3b3..bdc6cae04187d792deac0e6112a553a3c1a42e99 100644 (file)
@@ -1,4 +1,4 @@
-Prune version 5
+Prune version 6
 ===============
 
 Prune is an application for viewing, editing and managing coordinate data from GPS systems,
@@ -16,7 +16,7 @@ Running
 =======
 
 To run Prune from the jar file, simply call it from a command prompt or shell:
-   java -jar prune_05.jar
+   java -jar prune_06.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
@@ -24,9 +24,22 @@ 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_05.jar --lang=DE
+   java -jar prune_06.jar --lang=DE
 
 
+New with version 6
+==================
+
+The following features were added since version 5:
+  - Map view using OpenStreetMap images is now integrated in the main window, with control for map transparency
+  - Pov export has new option to use sphere sweeps for better appearance
+  - New function to check online for a newer version of Prune
+  - New function to take a section of track and cut/paste it to another position
+  - New function to add or subtract a time offset from point timestamps
+  - New function to call gpsbabel to load data directly from GPS receiver
+  - Additional file filter options on load and save
+  - Italian language thanks to generous user input
+
 New with version 5
 ==================
 
index c42a644c4c7cf3562a7e85f78367f6bc25d79f4d..5991b8636ef8b26f99b14a160016f109906f708f 100644 (file)
@@ -32,6 +32,7 @@ import javax.swing.ListSelectionModel;
 import javax.swing.table.TableModel;
 
 import tim.prune.App;
+import tim.prune.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.data.Altitude;
@@ -41,6 +42,7 @@ import tim.prune.data.Field;
 import tim.prune.data.FieldList;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
+import tim.prune.load.GenericFileFilter;
 import tim.prune.load.OneCharDocument;
 
 /**
@@ -220,7 +222,7 @@ public class FileSaver
 
                // header checkbox
                firstCard.add(Box.createRigidArea(new Dimension(0,10)));
-               _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"));
+               _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"), true);
                firstCard.add(_headerRowCheckbox);
 
                _cards.add(firstCard, "card1");
@@ -382,8 +384,17 @@ public class FileSaver
                boolean saveOK = true;
                FileWriter writer = null;
                if (_fileChooser == null)
+               {
                        _fileChooser = new JFileChooser();
-               _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
+                       _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.kml", new String[] {"kml"}));
+                       _fileChooser.setAcceptAllFileFilterUsed(true);
+                       // start from directory in config which should be set
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
+               }
                if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                {
                        File saveFile = _fileChooser.getSelectedFile();
@@ -485,7 +496,7 @@ public class FileSaver
                                                                {
                                                                        try
                                                                        {
-                                                                               buffer.append(point.getAltitude().getValue(altitudeFormat));
+                                                                               buffer.append(point.getAltitude().getStringValue(altitudeFormat));
                                                                        }
                                                                        catch (NullPointerException npe) {}
                                                                }
@@ -518,6 +529,8 @@ public class FileSaver
                                                writer.write(buffer.toString());
                                                writer.write(lineSeparator);
                                        }
+                                       // Store directory in config for later
+                                       Config.setWorkingDirectory(saveFile.getParentFile());
                                        // Save successful
                                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
                                                 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
index 0e028d18ddd9abff7bf41125ff8018f997c1afa4..5afdda3a91a66e41d3e2fa1cf8e08ea7600a6ce8 100644 (file)
@@ -12,8 +12,10 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 
+import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JDialog;
 import javax.swing.JFileChooser;
 import javax.swing.JFrame;
@@ -21,8 +23,8 @@ import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
-import javax.swing.filechooser.FileFilter;
 
+import tim.prune.Config;
 import tim.prune.GpsPruner;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
@@ -32,6 +34,7 @@ import tim.prune.data.DataPoint;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
+import tim.prune.load.GenericFileFilter;
 
 /**
  * Class to export track information
@@ -44,6 +47,7 @@ public class GpxExporter implements Runnable
        private JDialog _dialog = null;
        private JTextField _nameField = null;
        private JTextField _descriptionField = null;
+       private JCheckBox _timestampsCheckbox = null;
        private JFileChooser _fileChooser = null;
        private File _exportFile = null;
 
@@ -103,6 +107,10 @@ public class GpxExporter implements Runnable
                _descriptionField = new JTextField(10);
                descPanel.add(_descriptionField);
                mainPanel.add(descPanel);
+               // checkbox for timestamps
+               _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
+               _timestampsCheckbox.setSelected(true);
+               mainPanel.add(_timestampsCheckbox);
                dialogPanel.add(mainPanel, BorderLayout.CENTER);
 
                // button panel at bottom
@@ -127,6 +135,7 @@ public class GpxExporter implements Runnable
                });
                buttonPanel.add(cancelButton);
                dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
                return dialogPanel;
        }
 
@@ -138,20 +147,15 @@ public class GpxExporter implements Runnable
        {
                // OK pressed, so choose output file
                if (_fileChooser == null)
-                       {_fileChooser = new JFileChooser();}
-               _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
-               _fileChooser.setFileFilter(new FileFilter() {
-                       public boolean accept(File f)
-                       {
-                               return (f != null && (f.isDirectory()
-                                       || f.getName().toLowerCase().endsWith(".gpx")));
-                       }
-                       public String getDescription()
-                       {
-                               return I18nManager.getText("dialog.exportgpx.filetype");
-                       }
-               });
-               _fileChooser.setAcceptAllFileFilterUsed(false);
+               {
+                       _fileChooser = new JFileChooser();
+                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
+                       _fileChooser.setAcceptAllFileFilterUsed(false);
+                       // start from directory in config which should be set
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
+               }
                // Allow choose again if an existing file is selected
                boolean chooseAgain = false;
                do
@@ -202,6 +206,8 @@ public class GpxExporter implements Runnable
 
                        // close file
                        writer.close();
+                       // Store directory in config for later
+                       Config.setWorkingDirectory(_exportFile.getParentFile());
                        // Show confirmation
                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
                                 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
@@ -237,7 +243,9 @@ public class GpxExporter implements Runnable
                inWriter.write(GPX_VERSION_NUMBER);
                inWriter.write("\" creator=\"");
                inWriter.write(GPX_CREATOR);
-               inWriter.write("\">\n");
+               inWriter.write("\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+                       + " xmlns=\"http://www.topografix.com/GPX/1/0\""
+                       + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
                // Name field
                if (_nameField != null && _nameField.getText() != null && !_nameField.getText().equals(""))
                {
@@ -284,11 +292,11 @@ public class GpxExporter implements Runnable
                        for (i=0; i<numPoints; i++)
                        {
                                point = _track.getPoint(i);
+                               // restart track segment if necessary
                                if (point.getSegmentStart() && !firstPoint) {
                                        inWriter.write("\t</trkseg>\n\t<trkseg>\n");
                                }
                                if (!point.isWaypoint()) {
-                                       // restart track segment if necessary
                                        // export the track point
                                        exportTrackpoint(point, inWriter);
                                        firstPoint = false;
@@ -321,11 +329,11 @@ public class GpxExporter implements Runnable
                if (inPoint.hasAltitude())
                {
                        inWriter.write("\t\t<ele>");
-                       inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES));
                        inWriter.write("</ele>\n");
                }
                // timestamp if available (point might have altitude and then be turned into a waypoint)
-               if (inPoint.hasTimestamp())
+               if (inPoint.hasTimestamp() && _timestampsCheckbox.isSelected())
                {
                        inWriter.write("\t\t<time>");
                        inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
@@ -352,11 +360,11 @@ public class GpxExporter implements Runnable
                if (inPoint.hasAltitude())
                {
                        inWriter.write("<ele>");
-                       inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES));
                        inWriter.write("</ele>");
                }
-               // timestamp if available
-               if (inPoint.hasTimestamp())
+               // timestamp if available (and selected)
+               if (inPoint.hasTimestamp() && _timestampsCheckbox.isSelected())
                {
                        inWriter.write("<time>");
                        inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
index a6a3ee6e55df281c43aef3a8f6884d62655ea313..82b4f1c12ca7cc7f679f8896bbe86555142137a0 100644 (file)
@@ -32,8 +32,8 @@ import javax.swing.JPanel;
 import javax.swing.JProgressBar;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
-import javax.swing.filechooser.FileFilter;
 
+import tim.prune.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.data.Altitude;
@@ -43,6 +43,7 @@ import tim.prune.data.Field;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 import tim.prune.gui.ImageUtils;
+import tim.prune.load.GenericFileFilter;
 
 /**
  * Class to export track information
@@ -189,19 +190,14 @@ public class KmlExporter implements Runnable
        {
                // OK pressed, so choose output file
                if (_fileChooser == null)
-                       {_fileChooser = new JFileChooser();}
-               _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
-               _fileChooser.setFileFilter(new FileFilter() {
-                       public boolean accept(File f)
-                       {
-                               return (f != null && (f.isDirectory()
-                                       || f.getName().toLowerCase().endsWith(".kml") || f.getName().toLowerCase().endsWith(".kmz")));
-                       }
-                       public String getDescription()
-                       {
-                               return I18nManager.getText("dialog.exportkml.filetype");
-                       }
-               });
+               {
+                       _fileChooser = new JFileChooser();
+                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.setFileFilter(new GenericFileFilter("filetype.kmlkmz", new String[] {"kml", "kmz"}));
+                       // start from directory in config which should be set
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
+               }
                String requiredExtension = null, otherExtension = null;
                if (_kmzCheckbox.isSelected())
                {
@@ -303,6 +299,8 @@ public class KmlExporter implements Runnable
 
                        // close file
                        writer.close();
+                       // Store directory in config for later
+                       Config.setWorkingDirectory(_exportFile.getParentFile());
                        // show confirmation
                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
                                 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
@@ -442,7 +440,7 @@ public class KmlExporter implements Runnable
                inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL));
                inWriter.write(",");
                if (inExportAltitude && inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES));
                }
                else {
                        inWriter.write("0");
@@ -488,7 +486,7 @@ public class KmlExporter implements Runnable
                inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL));
                inWriter.write(",");
                if (inExportAltitude && inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES));
                }
                else {
                        inWriter.write("0");
@@ -511,7 +509,7 @@ public class KmlExporter implements Runnable
                // Altitude either absolute or locked to ground by Google Earth
                inWriter.write(",");
                if (inExportAltitude && inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES));
                }
                else {
                        inWriter.write("0");
diff --git a/tim/prune/save/ModelSegment.java b/tim/prune/save/ModelSegment.java
new file mode 100644 (file)
index 0000000..b6596c7
--- /dev/null
@@ -0,0 +1,64 @@
+package tim.prune.save;
+
+/**
+ * Class to hold a single track segment of a data model
+ */
+public class ModelSegment
+{
+       /** Start index of segment */
+       private int _startIndex = 0;
+       /** End index of segment */
+       private int _endIndex = 0;
+       /** Number of track points within segment */
+       private int _numTrackPoints = 0;
+
+
+       /**
+        * Constructor
+        * @param inStartIndex start index of segment
+        */
+       public ModelSegment(int inStartIndex)
+       {
+               _startIndex = inStartIndex;
+       }
+
+       /**
+        * @return start index of segment
+        */
+       public int getStartIndex()
+       {
+               return _startIndex;
+       }
+
+       /**
+        * @param inEndIndex end index of segment
+        */
+       public void setEndIndex(int inEndIndex)
+       {
+               _endIndex = inEndIndex;
+       }
+
+       /**
+        * @return end index of segment
+        */
+       public int getEndIndex()
+       {
+               return _endIndex;
+       }
+
+       /**
+        * @param inNumPoints number of track points in segment
+        */
+       public void setNumTrackPoints(int inNumPoints)
+       {
+               _numTrackPoints = inNumPoints;
+       }
+
+       /**
+        * @return number of track points in segment
+        */
+       public int getNumTrackPoints()
+       {
+               return _numTrackPoints;
+       }
+}
index 1afa255f1c7bd39852ed50764c0d167de2a033b4..6f8a32202ef227633ffa533dc62df2f6159e2007 100644 (file)
@@ -9,7 +9,11 @@ import java.awt.event.ActionListener;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
 
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
 import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JFileChooser;
@@ -17,13 +21,15 @@ import javax.swing.JFrame;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
-import javax.swing.filechooser.FileFilter;
 
+import tim.prune.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.data.Track;
+import tim.prune.load.GenericFileFilter;
 import tim.prune.threedee.LineDialog;
 import tim.prune.threedee.ThreeDModel;
 
@@ -41,11 +47,11 @@ public class PovExporter
        private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
        private JTextField _fontName = null, _altitudeCapField = null;
        private int _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP;
+       private JRadioButton _ballsAndSticksButton = null;
 
        // defaults
        private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
        private static final String DEFAULT_FONT_FILE = "crystal.ttf";
-       // alternative font: DejaVuSans-Bold.ttf
 
 
        /**
@@ -158,7 +164,11 @@ public class PovExporter
                JLabel fontLabel = new JLabel(I18nManager.getText("dialog.exportpov.font"));
                fontLabel.setHorizontalAlignment(SwingConstants.TRAILING);
                centralPanel.add(fontLabel);
-               _fontName = new JTextField(DEFAULT_FONT_FILE, 12);
+               String defaultFont = Config.getPovrayFont();
+               if (defaultFont == null || defaultFont.equals("")) {
+                       defaultFont = DEFAULT_FONT_FILE;
+               }
+               _fontName = new JTextField(defaultFont, 12);
                _fontName.setAlignmentX(Component.LEFT_ALIGNMENT);
                centralPanel.add(_fontName);
                //coordinates of camera
@@ -184,8 +194,32 @@ public class PovExporter
                _altitudeCapField = new JTextField("" + _altitudeCap);
                centralPanel.add(_altitudeCapField);
 
-               JPanel flowPanel = new JPanel();
-               flowPanel.add(centralPanel);
+               // Radio buttons for style - balls on sticks or tubes
+               JPanel stylePanel = new JPanel();
+               stylePanel.setLayout(new GridLayout(0, 2, 10, 4));
+               JLabel styleLabel = new JLabel(I18nManager.getText("dialog.exportpov.modelstyle"));
+               styleLabel.setHorizontalAlignment(SwingConstants.TRAILING);
+               stylePanel.add(styleLabel);
+               JPanel radioPanel = new JPanel();
+               radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS));
+               _ballsAndSticksButton = new JRadioButton(I18nManager.getText("dialog.exportpov.ballsandsticks"));
+               _ballsAndSticksButton.setSelected(false);
+               radioPanel.add(_ballsAndSticksButton);
+               JRadioButton tubesButton = new JRadioButton(I18nManager.getText("dialog.exportpov.tubesandwalls"));
+               tubesButton.setSelected(true);
+               radioPanel.add(tubesButton);
+               ButtonGroup group = new ButtonGroup();
+               group.add(_ballsAndSticksButton); group.add(tubesButton);
+               stylePanel.add(radioPanel);
+
+               // add this grid to the holder panel
+               JPanel holderPanel = new JPanel();
+               holderPanel.setLayout(new BorderLayout(5, 5));
+               JPanel boxPanel = new JPanel();
+               boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+               boxPanel.add(centralPanel);
+               boxPanel.add(stylePanel);
+               holderPanel.add(boxPanel, BorderLayout.CENTER);
 
                // show lines button
                JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
@@ -201,8 +235,11 @@ public class PovExporter
                                dialog.showDialog();
                        }
                });
+               JPanel flowPanel = new JPanel();
+               flowPanel.setLayout(new FlowLayout());
                flowPanel.add(showLinesButton);
-               panel.add(flowPanel, BorderLayout.CENTER);
+               holderPanel.add(flowPanel, BorderLayout.EAST);
+               panel.add(holderPanel, BorderLayout.CENTER);
                return panel;
        }
 
@@ -219,19 +256,15 @@ public class PovExporter
 
                // OK pressed, so choose output file
                if (_fileChooser == null)
+               {
                        _fileChooser = new JFileChooser();
-               _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
-               _fileChooser.setFileFilter(new FileFilter() {
-                       public boolean accept(File f)
-                       {
-                               return (f != null && (f.isDirectory() || f.getName().toLowerCase().endsWith(".pov")));
-                       }
-                       public String getDescription()
-                       {
-                               return I18nManager.getText("dialog.exportpov.filetype");
-                       }
-               });
-               _fileChooser.setAcceptAllFileFilterUsed(false);
+                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.setFileFilter(new GenericFileFilter("filetype.pov", new String[] {"pov"}));
+                       _fileChooser.setAcceptAllFileFilterUsed(false);
+                       // start from directory in config which should be set
+                       File configDir = Config.getWorkingDirectory();
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
+               }
 
                // Allow choose again if an existing file is selected
                boolean chooseAgain = false;
@@ -258,6 +291,8 @@ public class PovExporter
                                        if (exportFile(file))
                                        {
                                                // file saved
+                                               // Store directory in config for later
+                                               Config.setWorkingDirectory(file.getParentFile());
                                        }
                                        else
                                        {
@@ -306,7 +341,12 @@ public class PovExporter
                        writeLatLongLines(writer, model, lineSeparator);
 
                        // write out points
-                       writeDataPoints(writer, model, lineSeparator);
+                       if (_ballsAndSticksButton.isSelected()) {
+                               writeDataPointsBallsAndSticks(writer, model, lineSeparator);
+                       }
+                       else {
+                               writeDataPointsTubesAndWalls(writer, model, lineSeparator);
+                       }
 
                        // everything worked
                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
@@ -433,7 +473,16 @@ public class PovExporter
                  "      pigment {color rgb <0.1 1.0 1.0>}",
                  "      finish { phong 1 }",
                  "   }",
-                 " }", "",
+                 " }",
+                 "#declare track_sphere_t =",
+                 "  sphere {",
+                 "   <0, 0, 0>, 0.25", // size should depend on model size
+                 "   texture {",
+                 "      pigment {color rgb <0.6 1.0 0.2>}",
+                 "      finish { phong 1 }",
+                 "   } no_shadow",
+                 " }",
+                 "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
                  "// Base plane",
                  "box {",
                  "   <-" + inModelSize + ", -0.15, -" + inModelSize + ">,  // Near lower left corner",
@@ -510,13 +559,13 @@ public class PovExporter
 
 
        /**
-        * Write out all the data points to the file
+        * Write out all the data points to the file in the balls-and-sticks style
         * @param inWriter Writer to use for writing file
         * @param inModel model object for getting data points
         * @param inLineSeparator line separator to use
         * @throws IOException on file writing error
         */
-       private void writeDataPoints(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
+       private void writeDataPointsBallsAndSticks(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
        throws IOException
        {
                inWriter.write("// Data points:");
@@ -551,6 +600,159 @@ public class PovExporter
        }
 
 
+       /**
+        * Write out all the data points to the file in the tubes-and-walls style
+        * @param inWriter Writer to use for writing file
+        * @param inModel model object for getting data points
+        * @param inLineSeparator line separator to use
+        * @throws IOException on file writing error
+        */
+       private void writeDataPointsTubesAndWalls(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
+       throws IOException
+       {
+               inWriter.write("// Data points:");
+               inWriter.write(inLineSeparator);
+               int numPoints = inModel.getNumPoints();
+               int numTrackPoints = 0;
+               // Loop over all points and write out waypoints as balls
+               for (int i=0; i<numPoints; i++)
+               {
+                       if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
+                       {
+                               // waypoint ball
+                               inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i)
+                                       + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }");
+                               // vertical rod (if altitude positive)
+                               if (inModel.getScaledAltValue(i) > 0.0)
+                               {
+                                       inWriter.write(inLineSeparator);
+                                       inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0,"
+                                               + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }");
+                               }
+                               inWriter.write(inLineSeparator);
+                       }
+                       else {numTrackPoints++;}
+               }
+               inWriter.write(inLineSeparator);
+
+               // Loop over all the track segments
+               ArrayList segmentList = getSegmentList(inModel);
+               Iterator segmentIterator = segmentList.iterator();
+               while (segmentIterator.hasNext())
+               {
+                       ModelSegment segment = (ModelSegment) segmentIterator.next();
+                       int segLength = segment.getNumTrackPoints();
+
+                       // if the track segment is long enough, do a cubic spline sphere sweep
+                       if (segLength <= 1)
+                       {
+                               // single point in segment - just draw sphere
+                               int index = segment.getStartIndex();
+                               inWriter.write("object { track_sphere_t"
+                                       + " translate <" + inModel.getScaledHorizValue(index) + "," + inModel.getScaledAltValue(index)
+                                       + "," + inModel.getScaledVertValue(index) + "> }");
+                               // maybe draw some kind of polygon too or rod?
+                       }
+                       else
+                       {
+                               writeSphereSweep(inWriter, inModel, segment, inLineSeparator);
+                       }
+
+                       // Write wall underneath segment
+                       if (segLength > 1)
+                       {
+                               writePolygonWall(inWriter, inModel, segment, inLineSeparator);
+                       }
+               }
+       }
+
+
+       /**
+        * Write out a single sphere sweep using either cubic spline or linear spline
+        * @param inWriter Writer to use for writing file
+        * @param inModel model object for getting data points
+        * @param inSegment model segment to draw
+        * @param inLineSeparator line separator to use
+        * @throws IOException on file writing error
+        */
+       private static void writeSphereSweep(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
+       throws IOException
+       {
+               // 3d sphere sweep
+               inWriter.write("// Sphere sweep:");
+               inWriter.write(inLineSeparator);
+               String splineType = inSegment.getNumTrackPoints() < 5?"linear_spline":"cubic_spline";
+               inWriter.write("sphere_sweep { "); inWriter.write(splineType);
+               inWriter.write(" " + inSegment.getNumTrackPoints() + ",");
+               inWriter.write(inLineSeparator);
+               // Loop over all points in this segment and write out sphere sweep
+               for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
+               {
+                       if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
+                       {
+                               inWriter.write("  <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i)
+                                       + "," + inModel.getScaledVertValue(i) + ">, 0.25");
+                               inWriter.write(inLineSeparator);
+                       }
+               }
+               inWriter.write("  tolerance 0.1");
+               inWriter.write(inLineSeparator);
+               inWriter.write("  texture { pigment {color rgb <0.6 1.0 0.2>}  finish {phong 1} }");
+               inWriter.write(inLineSeparator);
+               inWriter.write("  no_shadow");
+               inWriter.write(inLineSeparator);
+               inWriter.write("}");
+               inWriter.write(inLineSeparator);
+       }
+
+
+       /**
+        * Write out a single polygon-based wall for the tubes-and-walls style
+        * @param inWriter Writer to use for writing file
+        * @param inModel model object for getting data points
+        * @param inSegment model segment to draw
+        * @param inLineSeparator line separator to use
+        * @throws IOException on file writing error
+        */
+       private static void writePolygonWall(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
+       throws IOException
+       {
+               // wall
+               inWriter.write(inLineSeparator);
+               inWriter.write("// wall between sweep and floor:");
+               inWriter.write(inLineSeparator);
+               // Loop over all points in this segment again and write out polygons
+               int prevIndex = -1;
+               for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
+               {
+                       if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
+                       {
+                               if (prevIndex >= 0)
+                               {
+                                       double xDiff = inModel.getScaledHorizValue(i) - inModel.getScaledHorizValue(prevIndex);
+                                       double yDiff = inModel.getScaledVertValue(i) - inModel.getScaledVertValue(prevIndex);
+                                       double dist = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+                                       if (dist > 0)
+                                       {
+                                               inWriter.write("polygon {");
+                                               inWriter.write("  5, <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">,");
+                                               inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", " + inModel.getScaledAltValue(prevIndex) + ", "
+                                                       + inModel.getScaledVertValue(prevIndex) + ">,");
+                                               inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", " + inModel.getScaledAltValue(i) + ", "
+                                                       + inModel.getScaledVertValue(i) + ">,");
+                                               inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", 0.0, " + inModel.getScaledVertValue(i) + ">,");
+                                               inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">");
+                                               inWriter.write("  pigment { color wall_colour } no_shadow");
+                                               inWriter.write("}");
+                                               inWriter.write(inLineSeparator);
+                                       }
+                               }
+                               prevIndex = i;
+                       }
+               }
+       }
+
+
        /**
         * @param inCode height code to check
         * @return validated height code within range 0 to max
@@ -579,4 +781,46 @@ public class PovExporter
                catch (Exception e) {} // ignore parse failures
                return "" + value;
        }
+
+       /**
+        * Go through the points making a list of the segment starts and the number of track points in each segment
+        * @param inModel model containing data
+        * @return list of ModelSegment objects
+        */
+       private static ArrayList getSegmentList(ThreeDModel inModel)
+       {
+               ArrayList segmentList = new ArrayList();
+               if (inModel != null && inModel.getNumPoints() > 0)
+               {
+                       ModelSegment currSegment = null;
+                       int numTrackPoints = 0;
+                       for (int i=0; i<inModel.getNumPoints(); i++)
+                       {
+                               if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
+                               {
+                                       if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_SEGMENT_START || currSegment == null)
+                                       {
+                                               // start of segment
+                                               if (currSegment != null)
+                                               {
+                                                       currSegment.setEndIndex(i-1);
+                                                       currSegment.setNumTrackPoints(numTrackPoints);
+                                                       segmentList.add(currSegment);
+                                                       numTrackPoints = 0;
+                                               }
+                                               currSegment = new ModelSegment(i);
+                                       }
+                                       numTrackPoints++;
+                               }
+                       }
+                       // Add last segment to list
+                       if (currSegment != null && numTrackPoints > 0)
+                       {
+                               currSegment.setEndIndex(inModel.getNumPoints()-1);
+                               currSegment.setNumTrackPoints(numTrackPoints);
+                               segmentList.add(currSegment);
+                       }
+               }
+               return segmentList;
+       }
 }
index b6a017ae05e90606381cff9cc80385ab3ade0301..8d1a476c35e35bc4b7fd67e34d5ff1304a38eff8 100644 (file)
@@ -7,6 +7,8 @@ import java.awt.GraphicsConfiguration;
 import java.awt.GraphicsEnvironment;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
 import java.awt.geom.GeneralPath;
 
 import javax.media.j3d.AmbientLight;
@@ -207,11 +209,19 @@ public class Java3DWindow implements ThreeDWindow
                                _frame.dispose();
                                _frame = null;
                                _orbit = null;
-                       }});
+                       }
+               });
                panel.add(closeButton);
                _frame.getContentPane().add(panel, BorderLayout.SOUTH);
                _frame.setSize(500, 350);
                _frame.pack();
+               // Add a listener to clean up when window closed
+               _frame.addWindowListener(new WindowAdapter() {
+                       public void windowClosing(WindowEvent e)
+                       {
+                               dispose();
+                       }
+               });
 
                // show frame
                _frame.show();
@@ -221,6 +231,16 @@ public class Java3DWindow implements ThreeDWindow
                }
        }
 
+       /**
+        * Dispose of the frame and its resources
+        */
+       public void dispose()
+       {
+               if (_frame != null) {
+                       _frame.dispose();
+                       _frame = null;
+               }
+       }
 
        /**
         * Create the whole scenery from the given track
index 141ec38a88d8656e18110e68181bb238b99f170c..5123f7c83bf65d6ad557b49a96f5065da2ef262e 100644 (file)
@@ -26,8 +26,9 @@ public class ThreeDModel
        public static final int MINIMUM_ALTITUDE_CAP = 100;
 
        // Constants for point types
-       public static final byte POINT_TYPE_WAYPOINT = 1;
-       public static final byte POINT_TYPE_NORMAL_POINT = 2;
+       public static final byte POINT_TYPE_WAYPOINT      = 1;
+       public static final byte POINT_TYPE_NORMAL_POINT  = 2;
+       public static final byte POINT_TYPE_SEGMENT_START = 3;
 
 
        /**
@@ -136,7 +137,7 @@ public class ThreeDModel
                for (int i=0; i<numPoints; i++)
                {
                        DataPoint point = _track.getPoint(i);
-                       _pointTypes[i] = (point.isWaypoint()?POINT_TYPE_WAYPOINT:POINT_TYPE_NORMAL_POINT);
+                       _pointTypes[i] = (point.isWaypoint()?POINT_TYPE_WAYPOINT:(point.getSegmentStart()?POINT_TYPE_SEGMENT_START:POINT_TYPE_NORMAL_POINT));
                        _pointHeights[i] = (byte) (point.getAltitude().getValue(Altitude.FORMAT_METRES) / 500);
                }
        }
index 5a926b76f5d569156acb729b1aca3a25ae94241c..4aef1f613a66fee384e49eaae0948bcdd720d658 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.App;
  */
 public abstract class WindowFactory
 {
-       private static ThreeDWindow _window = null;
+       private static Java3DWindow _window = null;
 
        /**
         * Get a Window object
@@ -21,7 +21,12 @@ public abstract class WindowFactory
        {
                if (isJava3dEnabled())
                {
-                       if (_window == null) _window = new Java3DWindow(inApp, inFrame);
+                       if (_window == null) {
+                               _window = new Java3DWindow(inApp, inFrame);
+                       }
+                       else {
+                               _window.dispose();
+                       }
                        return _window;
                }
                return null;
diff --git a/tim/prune/undo/UndoAddTimeOffset.java b/tim/prune/undo/UndoAddTimeOffset.java
new file mode 100644 (file)
index 0000000..329d09a
--- /dev/null
@@ -0,0 +1,51 @@
+package tim.prune.undo;
+
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Undo addition/subtraction of a time offset
+ */
+public class UndoAddTimeOffset implements UndoOperation
+{
+       /** Start and end indices of section */
+       private int _startIndex, _endIndex;
+       /** time offset */
+       private long _timeOffset;
+
+
+       /**
+        * Constructor
+        * @param inStart start index of section
+        * @param inEnd end index of section
+        * @param inOffset time offset
+        */
+       public UndoAddTimeOffset(int inStart, int inEnd, long inOffset)
+       {
+               _startIndex = inStart;
+               _endIndex = inEnd;
+               _timeOffset = inOffset;
+       }
+
+
+       /**
+        * @return description of operation including number of points adjusted
+        */
+       public String getDescription()
+       {
+               return I18nManager.getText("undo.addtimeoffset") + " (" + (_endIndex - _startIndex + 1) + ")";
+       }
+
+
+       /**
+        * Perform the undo operation on the given Track
+        * @param inTrackInfo TrackInfo object on which to perform the operation
+        */
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException
+       {
+               // Perform the inverse operation
+               inTrackInfo.getTrack().addTimeOffset(_startIndex, _endIndex, -_timeOffset);
+               UpdateMessageBroker.informSubscribers();
+       }
+}
diff --git a/tim/prune/undo/UndoConnectPhotoWithClone.java b/tim/prune/undo/UndoConnectPhotoWithClone.java
new file mode 100644 (file)
index 0000000..2fb2ca6
--- /dev/null
@@ -0,0 +1,41 @@
+package tim.prune.undo;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Operation to undo the connection of a photo to a point
+ * where the point had to be cloned
+ */
+public class UndoConnectPhotoWithClone extends UndoConnectPhoto
+{
+       /** Additional undo object for removing inserted point */
+       private UndoInsert _undoInsert = null;
+
+
+       /**
+        * Constructor
+        * @param inPoint data point
+        * @param inFilename filename of photo
+        * @param inIndex index of cloned point
+        */
+       public UndoConnectPhotoWithClone(DataPoint inPoint, String inFilename, int inIndex)
+       {
+               super(inPoint, inFilename);
+               // Make an undo object for the insert
+               _undoInsert = new UndoInsert(inIndex, 1);
+       }
+
+       /**
+        * Perform the undo operation on the given Track
+        * @param inTrackInfo TrackInfo object on which to perform the operation
+        */
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException
+       {
+               //System.out.println("Performing undo: (" + super.getDescription() + ", " + _undoInsert.getDescription() + ")");
+               // Firstly, undo connect
+               super.performUndo(inTrackInfo);
+               // Next, undo insert to remove cloned point
+               _undoInsert.performUndo(inTrackInfo);
+       }
+}
diff --git a/tim/prune/undo/UndoCreatePoint.java b/tim/prune/undo/UndoCreatePoint.java
new file mode 100644 (file)
index 0000000..7d297fe
--- /dev/null
@@ -0,0 +1,37 @@
+package tim.prune.undo;
+
+import tim.prune.I18nManager;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Undo creation of new point
+ */
+public class UndoCreatePoint implements UndoOperation
+{
+       /**
+        * @return description of operation
+        */
+       public String getDescription()
+       {
+               return I18nManager.getText("undo.createpoint");
+       }
+
+
+       /**
+        * Perform the undo operation on the given Track
+        * @param inTrackInfo TrackInfo object on which to perform the operation
+        */
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException
+       {
+               if (inTrackInfo.getTrack().getNumPoints() < 1)
+               {
+                       throw new UndoException(getDescription());
+               }
+               // Reset selection if last point selected
+               if (inTrackInfo.getSelection().getCurrentPointIndex() == (inTrackInfo.getTrack().getNumPoints()-1)) {
+                       inTrackInfo.getSelection().clearAll(); // Note: Informers told twice now!
+               }
+               // Remove last point
+               inTrackInfo.getTrack().cropTo(inTrackInfo.getTrack().getNumPoints() - 1);
+       }
+}
diff --git a/tim/prune/undo/UndoCutAndMove.java b/tim/prune/undo/UndoCutAndMove.java
new file mode 100644 (file)
index 0000000..8f61392
--- /dev/null
@@ -0,0 +1,95 @@
+package tim.prune.undo;
+
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Undo cut and move of a track section
+ */
+public class UndoCutAndMove implements UndoOperation
+{
+       /** Start and end indices of section */
+       private int _startIndex, _endIndex;
+       /** Index of move to point */
+       private int _moveToIndex;
+       /** First track point in section, next track point after where it was */
+       private DataPoint _firstTrackPoint = null, _followingTrackPoint = null;
+       /** Next track point after where it's being moved to */
+       private DataPoint _moveTrackPoint = null;
+       /** Segment flags for these points */
+       private boolean _firstSegmentFlag, _followingSegmentFlag, _moveToSegmentFlag;
+
+
+       /**
+        * Constructor
+        * @param inTrack track object for copying segment flags
+        * @param inStart start index of section
+        * @param inEnd end index of section
+        * @param inMoveTo index of moveTo point
+        */
+       public UndoCutAndMove(Track inTrack, int inStart, int inEnd, int inMoveTo)
+       {
+               // System.out.println("Construct undo with params " + inStart + ", "+  inEnd + ", " + inMoveTo);
+               _startIndex = inStart;
+               _endIndex = inEnd;
+               _moveToIndex = inMoveTo;
+               // Look for first track point in section to be moved, store flag
+               _firstTrackPoint = inTrack.getNextTrackPoint(inStart);
+               if (_firstTrackPoint != null) {
+                       _firstSegmentFlag = _firstTrackPoint.getSegmentStart();
+               }
+               // Look for following track point, store flag
+               _followingTrackPoint = inTrack.getNextTrackPoint(inEnd + 1);
+               if (_followingTrackPoint != null) {
+                       _followingSegmentFlag = _followingTrackPoint.getSegmentStart();
+               }
+               // Look for next track point after move point, store flag
+               _moveTrackPoint = inTrack.getNextTrackPoint(inMoveTo);
+               if (_moveTrackPoint != null) {
+                       _moveToSegmentFlag = _moveTrackPoint.getSegmentStart();
+               }
+       }
+
+
+       /**
+        * @return description of operation including number of points moved
+        */
+       public String getDescription()
+       {
+               return I18nManager.getText("undo.cutandmove") + " (" + (_endIndex - _startIndex + 1) + ")";
+       }
+
+
+       /**
+        * Perform the undo operation on the given Track
+        * @param inTrackInfo TrackInfo object on which to perform the operation
+        */
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException
+       {
+               // Cut and move the section back to where it was before
+               int numMoved = _endIndex - _startIndex + 1;
+               // Calculate new positions depending on whether section was moved forward or backward
+               if (_startIndex > _moveToIndex)
+               {
+                       inTrackInfo.getTrack().cutAndMoveSection(_moveToIndex, _moveToIndex + numMoved - 1, _startIndex + numMoved);
+               }
+               else
+               {
+                       inTrackInfo.getTrack().cutAndMoveSection(_moveToIndex - numMoved, _moveToIndex - 1, _startIndex);
+               }
+               // Restore segment start flags
+               if (_firstTrackPoint != null) {
+                       _firstTrackPoint.setSegmentStart(_firstSegmentFlag);
+               }
+               if (_followingTrackPoint != null) {
+                       _followingTrackPoint.setSegmentStart(_followingSegmentFlag);
+               }
+               if (_moveTrackPoint != null) {
+                       _moveTrackPoint.setSegmentStart(_moveToSegmentFlag);
+               }
+               UpdateMessageBroker.informSubscribers();
+       }
+}