[Git][debian-gis-team/gpsprune][upstream] New upstream version 22.2

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Mon Oct 3 04:54:16 BST 2022



Bas Couwenberg pushed to branch upstream at Debian GIS Project / gpsprune


Commits:
1279e934 by Bas Couwenberg at 2022-10-03T05:43:34+02:00
New upstream version 22.2
- - - - -


27 changed files:

- build.sh
- tim/prune/GpsPrune.java
- tim/prune/data/CoordFormatters.java
- tim/prune/data/MediaList.java
- tim/prune/data/MidpointData.java
- tim/prune/data/SymbolScaleFactor.java
- tim/prune/data/Track.java
- tim/prune/function/ShowThreeDFunction.java
- tim/prune/function/compress/CompressTrackFunction.java
- tim/prune/function/compress/SingleParameterAlgorithm.java
- tim/prune/function/estimate/EstimateTime.java
- tim/prune/function/estimate/EstimationParameters.java
- tim/prune/function/estimate/LearnParameters.java
- tim/prune/function/estimate/ParametersPanel.java
- tim/prune/gui/DecimalNumberField.java
- tim/prune/gui/WholeNumberField.java
- tim/prune/jpeg/JpegData.java
- tim/prune/lang/prune-texts_es.properties
- tim/prune/lang/prune-texts_fr.properties
- tim/prune/lang/prune-texts_it.properties
- tim/prune/lang/prune-texts_pl.properties
- tim/prune/load/babel/DistanceFilter.java
- tim/prune/load/babel/InterpolateFilter.java
- tim/prune/load/babel/SimplifyFilter.java
- tim/prune/readme.txt
- tim/prune/save/PovExporter.java
- tim/prune/undo/UndoApplyTimestamps.java


Changes:

=====================================
build.sh
=====================================
@@ -1,6 +1,6 @@
 # Build script
 # Version number
-PRUNENAME=gpsprune_22.1
+PRUNENAME=gpsprune_22.2
 # remove compile directory
 rm -rf compile
 # remove dist directory


=====================================
tim/prune/GpsPrune.java
=====================================
@@ -38,9 +38,9 @@ import tim.prune.gui.profile.ProfileChart;
 public class GpsPrune
 {
 	/** Version number of application, used in about screen and for version check */
-	public static final String VERSION_NUMBER = "22.1";
+	public static final String VERSION_NUMBER = "22.2";
 	/** Build number, just used for about screen */
-	public static final String BUILD_NUMBER = "399";
+	public static final String BUILD_NUMBER = "401";
 	/** Static reference to App object */
 	private static App APP = null;
 
@@ -330,7 +330,9 @@ public class GpsPrune
 		{
 			String name = file.getName();
 			file = file.getParentFile();
-			if (file != null && file.exists() && file.canRead()) return "Tried to load file '" + inParam + "' but cannot find '" + name + "'";
+			if (file != null && file.exists() && file.canRead()) {
+				return "Tried to load file '" + inParam + "' but cannot find '" + name + "'";
+			}
 		}
 		while (file != null);
 


=====================================
tim/prune/data/CoordFormatters.java
=====================================
@@ -10,7 +10,8 @@ import java.util.Locale;
  */
 public abstract class CoordFormatters
 {
-	private static HashMap<Integer, NumberFormat> _formatters = new HashMap<Integer, NumberFormat>();
+	/** Map of all the formatters created so far */
+	private static final HashMap<Integer, NumberFormat> _formatters = new HashMap<>();
 
 	/**
 	 * Get a formatter for the given number of decimal digits, creating and caching it if necessary
@@ -25,16 +26,12 @@ public abstract class CoordFormatters
 			// Formatter doesn't exist yet, so create a new one
 			formatter = NumberFormat.getNumberInstance(Locale.UK);
 			// Select the UK locale for this formatter so that decimal point is always used (not comma)
-			// Now make a pattern with the right number of digits
-			StringBuilder patternBuilder = new StringBuilder("0.");
-			if (inDigits > 0)
+			if (formatter instanceof DecimalFormat)
 			{
-				for (int i=0; i<inDigits; i++) {
-					patternBuilder.append('0');
-				}
+				// Now make a pattern with the right number of digits
+				final String digitPattern = "0." + "0".repeat(inDigits);
+				((DecimalFormat) formatter).applyPattern(digitPattern);
 			}
-			final String digitPattern = patternBuilder.toString();
-			if (formatter instanceof DecimalFormat) ((DecimalFormat) formatter).applyPattern(digitPattern);
 			// Store in map
 			_formatters.put(inDigits, formatter);
 		}


=====================================
tim/prune/data/MediaList.java
=====================================
@@ -11,13 +11,6 @@ public abstract class MediaList
 	protected ArrayList<MediaObject> _media = null;
 
 
-	/**
-	 * Empty constructor
-	 */
-	public MediaList() {
-		this(null);
-	}
-
 	/**
 	 * Constructor
 	 * @param inList ArrayList containing media objects
@@ -139,20 +132,6 @@ public abstract class MediaList
 	}
 
 
-	/**
-	 * @return array of file names
-	 */
-	public String[] getNameList()
-	{
-		final int num = getNumMedia();
-		String[] names = new String[num];
-		for (int i=0; i<num; i++) {
-			names[i] = getMedia(i).getName();
-		}
-		return names;
-	}
-
-
 	/**
 	 * @return true if list contains correlated media
 	 */


=====================================
tim/prune/data/MidpointData.java
=====================================
@@ -78,7 +78,7 @@ public class MidpointData
 		if (_track == null) return -1;
 		if (_needRefresh) updateData();
 		final int numPoints = _track.getNumPoints();
-		int nearestPoint = 0;
+		int nearestPoint = -1;
 		double nearestDist = -1.0;
 		double currDist;
 		for (int i=1; i < numPoints; i++)


=====================================
tim/prune/data/SymbolScaleFactor.java
=====================================
@@ -6,7 +6,7 @@ package tim.prune.data;
 public abstract class SymbolScaleFactor
 {
 	private static final double MIN_FACTOR = 0.1;
-	private static final double MAX_FACTOR = 2.0;
+	private static final double MAX_FACTOR = 3.0;
 
 	/**
 	 * @param inFactor value entered by user


=====================================
tim/prune/data/Track.java
=====================================
@@ -848,7 +848,7 @@ public class Track
 	 */
 	public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
 	{
-		int nearestPoint = 0;
+		int nearestPoint = -1;
 		double nearestDist = -1.0;
 		double mDist, yDist;
 		for (int i=0; i < getNumPoints(); i++)
@@ -869,8 +869,7 @@ public class Track
 			}
 		}
 		// Check whether it's within required distance
-		if (nearestDist > inMaxDist && inMaxDist > 0.0)
-		{
+		if (nearestDist > inMaxDist && inMaxDist > 0.0) {
 			return -1;
 		}
 		return nearestPoint;


=====================================
tim/prune/function/ShowThreeDFunction.java
=====================================
@@ -115,14 +115,14 @@ public class ShowThreeDFunction extends GenericFunction
 		altLabel.setHorizontalAlignment(SwingConstants.TRAILING);
 		grid.add(altLabel);
 		_exaggField = new DecimalNumberField(); // don't allow negative numbers
-		_exaggField.setText("5.0");
+		_exaggField.setValue(5.0);
 		grid.add(_exaggField);
 		// Row for symbol scaling
 		JLabel scaleLabel = new JLabel(I18nManager.getText("dialog.3d.symbolscalefactor") + ": ");
 		scaleLabel.setHorizontalAlignment(SwingConstants.TRAILING);
 		grid.add(scaleLabel);
 		_symbolScaleField = new DecimalNumberField(); // don't allow negative numbers
-		_symbolScaleField.setText("1.0");
+		_symbolScaleField.setValue(1.0);
 		grid.add(_symbolScaleField);
 		innerPanel.add(gridPanel);
 		innerPanel.add(Box.createVerticalStrut(4));
@@ -176,7 +176,9 @@ public class ShowThreeDFunction extends GenericFunction
 				if (_symbolScaleField.isEmpty()) {
 					_symbolScaleField.setValue(1.0);
 				}
-				window.setSymbolScalingFactor(SymbolScaleFactor.validateFactor(_symbolScaleField.getValue()));
+				final double symbolScaleFactor = SymbolScaleFactor.validateFactor(_symbolScaleField.getValue());
+				window.setSymbolScalingFactor(symbolScaleFactor);
+				_symbolScaleField.setValue(symbolScaleFactor);
 				// Also pass the base image parameters from input dialog
 				window.setBaseImageParameters(_baseImagePanel.getImageDefinition());
 				window.setTerrainParameters(new TerrainDefinition(_terrainPanel.getUseTerrain(), _terrainPanel.getGridSize()));


=====================================
tim/prune/function/compress/CompressTrackFunction.java
=====================================
@@ -76,9 +76,8 @@ public class CompressTrackFunction extends MarkAndDeleteFunction
 	{
 		int numToDelete = 0;
 		boolean[] deleteFlags = new boolean[_track.getNumPoints()];
-		for (int i=0; i<_algorithms.length; i++)
-		{
-			numToDelete += _algorithms[i].preview(deleteFlags);
+		for (CompressionAlgorithm algorithm : _algorithms) {
+			numToDelete += algorithm.preview(deleteFlags);
 		}
 		_summaryLabel.setValue(numToDelete);
 		_okButton.setEnabled(numToDelete > 0);
@@ -100,9 +99,9 @@ public class CompressTrackFunction extends MarkAndDeleteFunction
 		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
 
 		// Add each of the algorithm components to the panel
-		for (int i=0; i<_algorithms.length; i++)
+		for (CompressionAlgorithm algorithm : _algorithms)
 		{
-			mainPanel.add(_algorithms[i].getGuiComponents());
+			mainPanel.add(algorithm.getGuiComponents());
 			mainPanel.add(Box.createRigidArea(new Dimension(0, 2)));
 		}
 		// Summary label below algorithms


=====================================
tim/prune/function/compress/SingleParameterAlgorithm.java
=====================================
@@ -7,11 +7,11 @@ import java.awt.event.KeyListener;
 
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import javax.swing.JTextField;
 import javax.swing.SwingConstants;
 
 import tim.prune.I18nManager;
 import tim.prune.data.Track;
+import tim.prune.gui.DecimalNumberField;
 
 /**
  * Superclass for compression algorithms with a single text field parameter
@@ -19,7 +19,7 @@ import tim.prune.data.Track;
 public abstract class SingleParameterAlgorithm extends CompressionAlgorithm
 {
 	/** Text field for entering parameter */
-	private JTextField _parameterField = null;
+	private final DecimalNumberField _parameterField;
 	/** Listener from parent dialog */
 	private ActionListener _listener = null;
 
@@ -34,12 +34,16 @@ public abstract class SingleParameterAlgorithm extends CompressionAlgorithm
 	{
 		super(inTrack, inDetails, inListener);
 		_listener = inListener;
-		_parameterField = new JTextField();
+		_parameterField = new DecimalNumberField();
 		// Add listener to parameter field to re-run preview (and en/disable ok) when param changed
 		_parameterField.addKeyListener(new KeyListener() {
 			public void keyTyped(java.awt.event.KeyEvent arg0) {}
 			public void keyPressed(java.awt.event.KeyEvent arg0) {}
-			public void keyReleased(java.awt.event.KeyEvent arg0) {if (isActivated()) _listener.actionPerformed(null);}
+			public void keyReleased(java.awt.event.KeyEvent arg0) {
+				if (isActivated()) {
+					_listener.actionPerformed(null);
+				}
+			}
 		});
 	}
 
@@ -65,22 +69,15 @@ public abstract class SingleParameterAlgorithm extends CompressionAlgorithm
 	 * Parse the text field to get parameter
 	 * @return parameter given as double
 	 */
-	protected double getParameter()
-	{
-		double param = 0.0;
-		try {
-			// Parse from string
-			param = Double.parseDouble(_parameterField.getText());
-		}
-		catch (NumberFormatException nfe) {} // ignore, param stays zero
-		return param;
+	protected double getParameter() {
+		return _parameterField.getValue();
 	}
 
 	/**
 	 * @return String to save in Settings
 	 */
 	public String getSettingsString() {
-		return _activateCheckBox.isSelected() ? _parameterField.getText() : "";
+		return _activateCheckBox.isSelected() ? ("" + _parameterField.getValue()) : "";
 	}
 
 	/**
@@ -90,7 +87,7 @@ public abstract class SingleParameterAlgorithm extends CompressionAlgorithm
 	{
 		super.applySettingsString(inSettings);
 		if (isActivated()) {
-			_parameterField.setText(inSettings);
+			_parameterField.setValue(inSettings);
 		}
 	}
 }


=====================================
tim/prune/function/estimate/EstimateTime.java
=====================================
@@ -4,8 +4,6 @@ import java.awt.BorderLayout;
 import java.awt.Component;
 import java.awt.Font;
 import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.util.Calendar;
@@ -190,7 +188,7 @@ public class EstimateTime extends GenericFunction
 		_flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
 		_flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
 		paramsGrid.add(_flatSpeedLabel);
-		_flatSpeedField = new DecimalNumberField();
+		_flatSpeedField = new DecimalNumberField(); // positive only
 		_flatSpeedField.addKeyListener(paramChangeListener);
 		paramsGrid.add(_flatSpeedField);
 		JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
@@ -206,12 +204,12 @@ public class EstimateTime extends GenericFunction
 		_climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
 		_climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
 		paramsGrid.add(_climbParamLabel);
-		_gentleClimbField = new DecimalNumberField();
+		_gentleClimbField = new DecimalNumberField(true); // negative numbers allowed
 		_gentleClimbField.addKeyListener(paramChangeListener);
 		paramsGrid.add(_gentleClimbField);
 		paramsGrid.add(new JLabel(minsLabel.getText()));
 		// Steep climb
-		_steepClimbField = new DecimalNumberField();
+		_steepClimbField = new DecimalNumberField(true); // negative numbers allowed
 		_steepClimbField.addKeyListener(paramChangeListener);
 		paramsGrid.add(_steepClimbField);
 		paramsGrid.add(new JLabel(minsLabel.getText()));
@@ -259,21 +257,12 @@ public class EstimateTime extends GenericFunction
 		buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
 		buttonPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
 		_applyButton = new JButton(I18nManager.getText("button.apply"));
-		_applyButton.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent e) {
-				applyTimestampsToRange();
-			}
-		});
+		_applyButton.addActionListener(e -> applyTimestampsToRange());
 		buttonPanel.add(_applyButton);
 		buttonPanel.add(Box.createGlue());
 		// Close (and save parameters)
 		JButton closeButton = new JButton(I18nManager.getText("button.close"));
-		closeButton.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent e)
-			{
-				finishDialog();
-			}
-		});
+		closeButton.addActionListener(e -> finishDialog());
 		closeButton.addKeyListener(new KeyAdapter() {
 			public void keyPressed(KeyEvent inE) {
 				if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
@@ -282,11 +271,7 @@ public class EstimateTime extends GenericFunction
 		buttonPanel.add(closeButton);
 		// cancel
 		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-		cancelButton.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent e) {
-				_dialog.dispose();
-			}
-		});
+		cancelButton.addActionListener(e -> _dialog.dispose());
 		buttonPanel.add(cancelButton);
 		dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
 		return dialogPanel;
@@ -318,26 +303,24 @@ public class EstimateTime extends GenericFunction
 		_steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
 
 		// Try to get parameters from config
-		EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
-
-		String[] paramValues = estParams.getStrings();
-		if (paramValues == null || paramValues.length != 5)
-		{
-			estParams.resetToDefaults();
-			paramValues = estParams.getStrings();
+		EstimationParameters estParams = EstimationParameters.fromConfigString(
+			Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+		if (estParams == null) {
+			estParams = EstimationParameters.DEFAULT_PARAMS;
 		}
-		// Flat time is either for 5 km, 3 miles or 3 nm
+
+		// Flat time is either for 5 km, 3 miles or 3 nautical miles
 		_flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
 			" " + EstimationParameters.getStandardDistance() + ": ");
-		_flatSpeedField.setText(paramValues[0]);
+		_flatSpeedField.setValue(estParams.getFlatMinutesLocal());
 
 		final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
 		_climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
-		_gentleClimbField.setText(paramValues[1]);
-		_steepClimbField.setText(paramValues[2]);
+		_gentleClimbField.setValue(estParams.getGentleClimbMinutesLocal());
+		_steepClimbField.setValue(estParams.getSteepClimbMinutesLocal());
 		_descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
-		_gentleDescentField.setText(paramValues[3]);
-		_steepDescentField.setText(paramValues[4]);
+		_gentleDescentField.setValue(estParams.getGentleDescentMinutesLocal());
+		_steepDescentField.setValue(estParams.getSteepDescentMinutesLocal());
 
 		// Use the entered parameters to estimate the time
 		calculateEstimatedTime();
@@ -361,10 +344,10 @@ public class EstimateTime extends GenericFunction
 	private void calculateEstimatedTime()
 	{
 		// Populate an EstimationParameters object from the four strings
-		EstimationParameters params = new EstimationParameters();
-		params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
-			_steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
-		if (!params.isValid()) {
+		EstimationParameters params = EstimationParameters.fromLocalUnits(_flatSpeedField.getValue(),
+			_gentleClimbField.getValue(), _steepClimbField.getValue(),
+			_gentleDescentField.getValue(), _steepDescentField.getValue());
+		if (params == null) {
 			_estimatedDurationLabel.setText("- - -");
 			_applyButton.setEnabled(false);
 		}
@@ -383,11 +366,11 @@ public class EstimateTime extends GenericFunction
 	 */
 	private void finishDialog()
 	{
-		// Make estimation parameters from entered strings, if valid save to config
-		EstimationParameters params = new EstimationParameters();
-		params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
-			_steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
-		if (params.isValid()) {
+		// Make estimation parameters from entered values, if valid save to config
+		EstimationParameters params = EstimationParameters.fromLocalUnits(_flatSpeedField.getValue(),
+			_gentleClimbField.getValue(), _steepClimbField.getValue(),
+			_gentleDescentField.getValue(), _steepDescentField.getValue());
+		if (params != null) {
 			Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
 		}
 		_dialog.dispose();
@@ -398,9 +381,9 @@ public class EstimateTime extends GenericFunction
 	 */
 	private void showTip()
 	{
-		EstimationParameters currParams = new EstimationParameters(
+		EstimationParameters currParams = EstimationParameters.fromConfigString(
 			Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
-		if (currParams.sameAsDefaults()) {
+		if (currParams == null || currParams.sameAsDefaults()) {
 			_app.showTip(TipManager.Tip_LearnTimeParams);
 		}
 	}
@@ -411,30 +394,28 @@ public class EstimateTime extends GenericFunction
 	 */
 	private void applyTimestampsToRange()
 	{
-		// Populate an EstimationParameters object from the four strings
-		EstimationParameters params = new EstimationParameters();
-		params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
-			_steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
-		if (params.isValid())
+		// Populate an EstimationParameters object from the five values
+		EstimationParameters params = EstimationParameters.fromLocalUnits(
+			_flatSpeedField.getValue(), _gentleClimbField.getValue(),
+			_steepClimbField.getValue(), _gentleDescentField.getValue(), _steepDescentField.getValue());
+
+		// Make undo object to store existing timestamps
+		UndoApplyTimestamps undo = new UndoApplyTimestamps(_app.getTrackInfo());
+		long startMillis = getTimeAtMidnight();
+		// Loop over all points in selected range
+		RangeStatsWithGradients stats = new RangeStatsWithGradients();
+		Selection selection = _app.getTrackInfo().getSelection();
+		for (int pointIdx=selection.getStart(); pointIdx <= selection.getEnd(); pointIdx++)
 		{
-			// Make undo object to store existing timestamps
-			UndoApplyTimestamps undo = new UndoApplyTimestamps(_app.getTrackInfo());
-			long startMillis = getTimeAtMidnight();
-			// Loop over all points in selected range
-			RangeStatsWithGradients stats = new RangeStatsWithGradients();
-			Selection selection = _app.getTrackInfo().getSelection();
-			for (int pointIdx=selection.getStart(); pointIdx <= selection.getEnd(); pointIdx++)
-			{
-				DataPoint point = _app.getTrackInfo().getTrack().getPoint(pointIdx);
-				if (point.isWaypoint()) continue;
-				stats.addPoint(point);
-				final double numSeconds = params.applyToStats(stats) * 60.0;
-				final long pointMillis = startMillis + (long) (numSeconds * 1000.0);
-				point.setFieldValue(Field.TIMESTAMP, "" + pointMillis, false);
-			}
-			UpdateMessageBroker.informSubscribers();
-			_app.completeFunction(undo, I18nManager.getText("confirm.applytimestamps"));
+			DataPoint point = _app.getTrackInfo().getTrack().getPoint(pointIdx);
+			if (point.isWaypoint()) continue;
+			stats.addPoint(point);
+			final double numSeconds = params.applyToStats(stats) * 60.0;
+			final long pointMillis = startMillis + (long) (numSeconds * 1000.0);
+			point.setFieldValue(Field.TIMESTAMP, "" + pointMillis, false);
 		}
+		UpdateMessageBroker.informSubscribers();
+		_app.completeFunction(undo, I18nManager.getText("confirm.applytimestamps"));
 	}
 
 	/**


=====================================
tim/prune/function/estimate/EstimationParameters.java
=====================================
@@ -1,6 +1,5 @@
 package tim.prune.function.estimate;
 
-import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.util.Locale;
 
@@ -18,197 +17,153 @@ import tim.prune.data.UnitSetLibrary;
 public class EstimationParameters
 {
 	/** Minutes required for flat travel, fixed metric distance */
-	private double _flatMins = 0.0;
+	private final double _flatMins;
 	/** Minutes required for climbing, fixed metric distance */
-	private double _gentleClimbMins = 0.0, _steepClimbMins;
+	private final double _gentleClimbMins;
+	private final double _steepClimbMins;
 	/** Minutes required for descending, fixed metric distance */
-	private double _gentleDescentMins = 0.0, _steepDescentMins;
-	/** True if parsing from a string failed */
-	private boolean _parseFailed = false;
+	private final double _gentleDescentMins;
+	private final double _steepDescentMins;
 
 	/** Kilometres unit for comparison */
 	private static final Unit KILOMETRES = UnitSetLibrary.UNITS_KILOMETRES;
 
+	/** Default parameters */
+	public static final EstimationParameters DEFAULT_PARAMS = new EstimationParameters(60.0, 12.0, 18.0, 0.0, 12.0);
+
 
 	/**
-	 * Bare constructor using default values
+	 * Populate with double metric values, for example the results of a Learning process
+	 * @param inFlat time for 5km on the flat
+	 * @param inGClimb time for 100m gentle climb
+	 * @param inSClimb time for 100m steep climb
+	 * @param inGDescent time for 100m gentle descent
+	 * @param inSDescent time for 100m steep descent
 	 */
-	public EstimationParameters()
+	private EstimationParameters(double inFlat,
+		double inGClimb, double inSClimb, double inGDescent, double inSDescent)
 	{
-		resetToDefaults();
+		_flatMins = inFlat;
+		_gentleClimbMins = inGClimb;
+		_steepClimbMins  = inSClimb;
+		_gentleDescentMins = inGDescent;
+		_steepDescentMins  = inSDescent;
 	}
 
 	/**
 	 * Constructor from config string
-	 * @param inConfigString single, semicolon-separated string from config
+	 * @param inString single, semicolon-separated string from config with five params
+	 * @return parameters object, or null if parsing failed
 	 */
-	public EstimationParameters(String inConfigString)
+	public static EstimationParameters fromConfigString(String inString)
 	{
-		populateWithString(inConfigString);
-		if (_parseFailed) {
-			resetToDefaults();
+		if (inString == null || inString.isBlank()) {
+			return null;
 		}
-	}
-
-	/**
-	 * Reset all the values to their hardcoded defaults
-	 */
-	public void resetToDefaults()
-	{
-		_flatMins = 60.0;
-		_gentleClimbMins = 12.0; _steepClimbMins = 18.0;
-		_gentleDescentMins = 0.0; _steepDescentMins = 12.0;
-		_parseFailed = false;
-	}
-
-	/**
-	 * @return true if this set of parameters is the same as the default set
-	 */
-	public boolean sameAsDefaults()
-	{
-		EstimationParameters defaultParams = new EstimationParameters();
-		return _flatMins == defaultParams._flatMins
-			&& _gentleClimbMins == defaultParams._gentleClimbMins
-			&& _steepClimbMins == defaultParams._steepClimbMins
-			&& _gentleDescentMins == defaultParams._gentleDescentMins
-			&& _steepDescentMins  == defaultParams._steepDescentMins;
-	}
-
-	/**
-	 * Populate the values from the config, which means all values are metric
-	 * @param inString semicolon-separated string of five parameters
-	 */
-	private void populateWithString(String inString)
-	{
-		if (inString != null && !inString.trim().equals(""))
+		String[] params = inString.trim().split(";");
+		if (params == null || params.length != 5) {
+			return null;
+		}
+		try
 		{
-			String[] params = inString.trim().split(";");
-			_parseFailed = (params == null || params.length != 5);
-			if (!_parseFailed)
-			{
-				for (String p : params)
-				{
-					if (!isParamStringValid(p)) {
-						_parseFailed = true;
-					}
-				}
-			}
-			if (!_parseFailed)
-			{
-				try
-				{
-					// Use fixed UK locale to parse these, because of fixed "." formatting
-					NumberFormat twoDpFormatter = NumberFormat.getNumberInstance(Locale.UK);
-					_flatMins          = twoDpFormatter.parse(params[0]).doubleValue();
-					_gentleClimbMins   = twoDpFormatter.parse(params[1]).doubleValue();
-					_steepClimbMins    = twoDpFormatter.parse(params[2]).doubleValue();
-					_gentleDescentMins = twoDpFormatter.parse(params[3]).doubleValue();
-					_steepDescentMins  = twoDpFormatter.parse(params[4]).doubleValue();
-				}
-				catch (Exception e) {
-					_parseFailed = true;
-				}
-			}
+			// Use fixed UK locale to parse these, because of fixed "." formatting
+			NumberFormat twoDpFormatter = NumberFormat.getNumberInstance(Locale.UK);
+			final double flatMins          = twoDpFormatter.parse(params[0]).doubleValue();
+			final double gentleClimbMins   = twoDpFormatter.parse(params[1]).doubleValue();
+			final double steepClimbMins    = twoDpFormatter.parse(params[2]).doubleValue();
+			final double gentleDescentMins = twoDpFormatter.parse(params[3]).doubleValue();
+			final double steepDescentMins  = twoDpFormatter.parse(params[4]).doubleValue();
+			return new EstimationParameters(flatMins, gentleClimbMins, steepClimbMins, gentleDescentMins, steepDescentMins);
+		}
+		catch (Exception e) {
+			return null;
 		}
-		else _parseFailed = true;
 	}
 
 	/**
-	 * Populate the values using five user-entered strings (now Units-specific!)
+	 * Populate the values using five user-entered values (now Units-specific!)
 	 * @param inFlat minutes for flat
 	 * @param inGClimb minutes for gentle climb
 	 * @param inSClimb minutes for steep climb
 	 * @param inGDescent minutes for gentle descent
 	 * @param inSDescent minutes for steep descent
 	 */
-	public void populateWithStrings(String inFlat, String inGClimb, String inSClimb, String inGDescent, String inSDescent)
+	public static EstimationParameters fromLocalUnits(double inFlat,
+		double inGClimb, double inSClimb, double inGDescent, double inSDescent)
 	{
-		if (isParamStringValid(inFlat) && isParamStringValid(inGClimb) && isParamStringValid(inSClimb)
-			&& isParamStringValid(inGDescent) && isParamStringValid(inSDescent))
-		{
-			Unit distUnit = Config.getUnitSet().getDistanceUnit();
-			Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
-			final double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
-			final double altFactor  = (altUnit.isStandard()  ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
-			NumberFormat localFormatter = NumberFormat.getNumberInstance();
-			try
-			{
-				_flatMins = localFormatter.parse(inFlat).doubleValue() * distFactor;
-				_gentleClimbMins = localFormatter.parse(inGClimb).doubleValue() * altFactor;
-				_steepClimbMins  = localFormatter.parse(inSClimb).doubleValue() * altFactor;
-				_gentleDescentMins = localFormatter.parse(inGDescent).doubleValue() * altFactor;
-				_steepDescentMins  = localFormatter.parse(inSDescent).doubleValue() * altFactor;
-			}
-			catch (Exception e) {_parseFailed = true;}
-		}
-		else _parseFailed = true;
+		Unit distUnit = Config.getUnitSet().getDistanceUnit();
+		Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
+		final double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
+		final double altFactor  = (altUnit.isStandard()  ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+
+		double flatMins = inFlat * distFactor;
+		double gentleClimbMins = inGClimb * altFactor;
+		double steepClimbMins  = inSClimb * altFactor;
+		double gentleDescentMins = inGDescent * altFactor;
+		double steepDescentMins  = inSDescent * altFactor;
+		return new EstimationParameters(flatMins, gentleClimbMins, steepClimbMins,
+				gentleDescentMins, steepDescentMins);
 	}
 
 	/**
-	 * Populate with double metric values, for example the results of a Learning process
-	 * @param inFlat time for 5km on the flat
-	 * @param inGClimb time for 100m gentle climb
-	 * @param inSClimb time for 100m steep climb
-	 * @param inGDescent time for 100m gentle descent
-	 * @param inSDescent time for 100m steep descent
+	 * Populate the values using five calculated values (metric)
+	 * @param inFlat minutes for flat
+	 * @param inGClimb minutes for gentle climb
+	 * @param inSClimb minutes for steep climb
+	 * @param inGDescent minutes for gentle descent
+	 * @param inSDescent minutes for steep descent
 	 */
-	public void populateWithMetrics(double inFlat, double inGClimb, double inSClimb, double inGDescent, double inSDescent)
+	public static EstimationParameters fromMetricUnits(double inFlat,
+		double inGClimb, double inSClimb, double inGDescent, double inSDescent)
 	{
-		_flatMins = inFlat;
-		_gentleClimbMins = inGClimb;
-		_steepClimbMins  = inSClimb;
-		_gentleDescentMins = inGDescent;
-		_steepDescentMins  = inSDescent;
+		return new EstimationParameters(inFlat, inGClimb, inSClimb, inGDescent, inSDescent);
 	}
 
 	/**
-	 * @param inString parameter string to check
-	 * @return true if it looks valid (no letters, at least one digit)
+	 * @return true if this set of parameters is the same as the default set
 	 */
-	private static boolean isParamStringValid(String inString)
-	{
-		if (inString == null) {return false;}
-		boolean hasDigit = false, currPunc = false, prevPunc = false;
-		for (int i=0; i<inString.length(); i++)
-		{
-			char c = inString.charAt(i);
-			if (Character.isLetter(c)) {return false;} // no letters allowed
-			currPunc = (c == '.' || c == ',');
-			if (currPunc && prevPunc) {return false;} // no consecutive . or , allowed
-			prevPunc = currPunc;
-			hasDigit = hasDigit || Character.isDigit(c);
-		}
-		return hasDigit; // must have at least one digit!
+	public boolean sameAsDefaults() {
+		return toConfigString().equals(DEFAULT_PARAMS.toConfigString());
 	}
 
-	/**
-	 * @return true if the parameters are valid, with no parsing errors
-	 */
-	public boolean isValid()
-	{
-		return !_parseFailed;
+
+	/** @return number of minutes for flat travel, local distance units */
+	public double getFlatMinutesLocal() {
+		return convertDistanceToLocal(_flatMins);
 	}
 
-	/**
-	 * @return five strings for putting in text fields for editing / display
-	 */
-	public String[] getStrings()
-	{
+	/** @return the given number of minutes converted to local distance units */
+	private static double convertDistanceToLocal(double minutes) {
 		Unit distUnit = Config.getUnitSet().getDistanceUnit();
-		Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
 		double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
-		double altFactor  = (altUnit.isStandard()  ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
-		// Use locale-specific number formatting, eg commas for german
-		NumberFormat numFormatter = NumberFormat.getNumberInstance();
-		if (numFormatter instanceof DecimalFormat) {
-			((DecimalFormat) numFormatter).applyPattern("0.00");
-		}
-		// Conversion between units
-		return new String[] {
-			numFormatter.format(_flatMins / distFactor),
-			numFormatter.format(_gentleClimbMins / altFactor), numFormatter.format(_steepClimbMins / altFactor),
-			numFormatter.format(_gentleDescentMins / altFactor), numFormatter.format(_steepDescentMins / altFactor)
-		};
+		return minutes / distFactor;
+	}
+
+	/** @return number of minutes for gentle climb, local distance units */
+	public double getGentleClimbMinutesLocal() {
+		return convertAltitudeToLocal(_gentleClimbMins);
+	}
+
+	/** @return number of minutes for steep climb, local distance units */
+	public double getSteepClimbMinutesLocal() {
+		return convertAltitudeToLocal(_steepClimbMins);
+	}
+
+	/** @return number of minutes for gentle descent, local distance units */
+	public double getGentleDescentMinutesLocal() {
+		return convertAltitudeToLocal(_gentleDescentMins);
+	}
+
+	/** @return number of minutes for steep descent, local distance units */
+	public double getSteepDescentMinutesLocal() {
+		return convertAltitudeToLocal(_steepDescentMins);
+	}
+
+	/** @return the given number of minutes converted to local altitude units */
+	private static double convertAltitudeToLocal(double minutes) {
+		Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
+		double altFactor  = (altUnit.isStandard() ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+		return minutes / altFactor;
 	}
 
 	/**
@@ -242,11 +197,13 @@ public class EstimationParameters
 	 * @param inNum number to output
 	 * @return formatted string to two decimal places, with decimal point
 	 */
-	private static String twoDp(double inNum)
+	public static String twoDp(double inNum)
 	{
-		if (inNum < 0.0) return "-" + twoDp(-inNum);
+		if (inNum < 0.0) {
+			return "-" + twoDp(-inNum);
+		}
 		int hundreds = (int) (inNum * 100 + 0.5);
-		return "" + (hundreds / 100) + "." + (hundreds % 100);
+		return "" + (hundreds / 100) + "." + ((hundreds/10) % 10) + (hundreds % 10);
 	}
 
 	/**
@@ -277,17 +234,19 @@ public class EstimationParameters
 	 */
 	public EstimationParameters combine(EstimationParameters inOther, double inFraction)
 	{
-		if (inFraction < 0.0 || inFraction > 1.0 || inOther == null) {
-			return null;
+		if (inFraction >= 1.0 || inOther == null) {
+			return this;
+		}
+		if (inFraction <= 0.0) {
+			return inOther;
 		}
 		// inFraction is the weight of this one, weight of the other one is 1-inFraction
 		final double fraction2 = 1 - inFraction;
-		EstimationParameters combined = new EstimationParameters();
-		combined._flatMins = inFraction * _flatMins + fraction2 * inOther._flatMins;
-		combined._gentleClimbMins = inFraction * _gentleClimbMins + fraction2 * inOther._gentleClimbMins;
-		combined._gentleDescentMins = inFraction * _gentleDescentMins + fraction2 * inOther._gentleDescentMins;
-		combined._steepClimbMins = inFraction * _steepClimbMins + fraction2 * inOther._steepClimbMins;
-		combined._steepDescentMins = inFraction * _steepDescentMins + fraction2 * inOther._steepDescentMins;
-		return combined;
+		double flatMins = inFraction * _flatMins + fraction2 * inOther._flatMins;
+		double gentleClimbMins = inFraction * _gentleClimbMins + fraction2 * inOther._gentleClimbMins;
+		double gentleDescentMins = inFraction * _gentleDescentMins + fraction2 * inOther._gentleDescentMins;
+		double steepClimbMins = inFraction * _steepClimbMins + fraction2 * inOther._steepClimbMins;
+		double steepDescentMins = inFraction * _steepDescentMins + fraction2 * inOther._steepDescentMins;
+		return new EstimationParameters(flatMins, gentleClimbMins, steepClimbMins, gentleDescentMins, steepDescentMins);
 	}
 }


=====================================
tim/prune/function/estimate/LearnParameters.java
=====================================
@@ -446,9 +446,8 @@ public class LearnParameters extends GenericFunction implements Runnable
 		}
 		// Copy results to an EstimationParameters object
 		MatrixResults result = new MatrixResults();
-		result._parameters = new EstimationParameters();
-		result._parameters.populateWithMetrics(answer.get(0, 0) * 5, // convert from 1km to 5km
-			answer.get(1, 0) * 100.0, answer.get(2, 0) * 100.0,      // convert from m to 100m
+		result._parameters = EstimationParameters.fromMetricUnits(answer.get(0, 0) * 5, // convert from 1km to 5km
+			answer.get(1, 0) * 100.0, answer.get(2, 0) * 100.0,                         // convert from m to 100m
 			answer.get(3, 0) * 100.0, answer.get(4, 0) * 100.0);
 		result._averageErrorPc = averageErrorPc;
 		return result;
@@ -481,7 +480,10 @@ public class LearnParameters extends GenericFunction implements Runnable
 	private EstimationParameters calculateCombinedParameters()
 	{
 		final double fraction1 = 1 - 0.1 * _weightSlider.getValue(); // slider left = value 0 = fraction 1 = keep current
-		EstimationParameters oldParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+		EstimationParameters oldParams = EstimationParameters.fromConfigString(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+		if (oldParams == null) {
+			return _calculatedParams;
+		}
 		return oldParams.combine(_calculatedParams, fraction1);
 	}
 
@@ -516,6 +518,9 @@ public class LearnParameters extends GenericFunction implements Runnable
 	private void combineAndFinish()
 	{
 		EstimationParameters params = calculateCombinedParameters();
+		if (params == null) {
+			params = EstimationParameters.DEFAULT_PARAMS;
+		}
 		Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
 		_dialog.dispose();
 	}


=====================================
tim/prune/function/estimate/ParametersPanel.java
=====================================
@@ -2,6 +2,8 @@ package tim.prune.function.estimate;
 
 import java.awt.Component;
 import java.awt.GridLayout;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
 
 import javax.swing.BorderFactory;
 import javax.swing.JLabel;
@@ -17,7 +19,7 @@ import tim.prune.gui.DisplayUtils;
 public class ParametersPanel extends JPanel
 {
 	/** Flag for whether average error should be shown */
-	private boolean _showAverageError = false;
+	private final boolean _showAverageError;
 	/** Labels for calculated parameters */
 	private JLabel _fsUnitsLabel = null, _flatSpeedLabel = null;
 	private JLabel _climbUnitsLabel = null;
@@ -25,14 +27,13 @@ public class ParametersPanel extends JPanel
 	private JLabel _descentUnitsLabel = null;
 	private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
 	private JLabel _averageErrorLabel = null;
-
+	private final NumberFormat _numberFormatter;
 
 	/**
 	 * Constructor
 	 * @param inTitleKey key to use for title of panel
 	 */
-	public ParametersPanel(String inTitleKey)
-	{
+	public ParametersPanel(String inTitleKey) {
 		this(inTitleKey, false);
 	}
 
@@ -44,6 +45,10 @@ public class ParametersPanel extends JPanel
 	public ParametersPanel(String inTitleKey, boolean inShowAvgError)
 	{
 		super();
+		_numberFormatter = NumberFormat.getNumberInstance();
+		if (_numberFormatter instanceof DecimalFormat) {
+			((DecimalFormat) _numberFormatter).applyPattern("0.00");
+		}
 		_showAverageError = inShowAvgError;
 		if (inTitleKey != null) {
 			setBorder(BorderFactory.createTitledBorder(I18nManager.getText(inTitleKey)));
@@ -52,6 +57,7 @@ public class ParametersPanel extends JPanel
 		addLabels();
 	}
 
+
 	private void addLabels()
 	{
 		// flat speed
@@ -102,7 +108,7 @@ public class ParametersPanel extends JPanel
 	 */
 	private void updateParameters(EstimationParameters inParams, double inAverageError, boolean inShowError)
 	{
-		if (inParams == null || !inParams.isValid())
+		if (inParams == null)
 		{
 			_flatSpeedLabel.setText("");
 			_gentleClimbLabel.setText(""); _steepClimbLabel.setText("");
@@ -111,38 +117,39 @@ public class ParametersPanel extends JPanel
 		else
 		{
 			final String minsText = " " + I18nManager.getText("units.minutes");
-			String[] values = inParams.getStrings(); // these strings are already formatted locally
 			_fsUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
 				" " + EstimationParameters.getStandardDistance() + ": ");
-			_flatSpeedLabel.setText(values[0] + minsText);
+			_flatSpeedLabel.setText(formatMinutes(inParams.getFlatMinutesLocal()) + minsText);
 			final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
 			_climbUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
-			_gentleClimbLabel.setText(values[1] + minsText);
-			_steepClimbLabel.setText(values[2] + minsText);
+			_gentleClimbLabel.setText(formatMinutes(inParams.getGentleClimbMinutesLocal()) + minsText);
+			_steepClimbLabel.setText(formatMinutes(inParams.getSteepClimbMinutesLocal()) + minsText);
 			_descentUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
-			_gentleDescentLabel.setText(values[3] + minsText);
-			_steepDescentLabel.setText(values[4] + minsText);
+			_gentleDescentLabel.setText(formatMinutes(inParams.getGentleDescentMinutesLocal()) + minsText);
+			_steepDescentLabel.setText(formatMinutes(inParams.getSteepDescentMinutesLocal()) + minsText);
 		}
 		// Average error
 		if (_averageErrorLabel != null)
 		{
-			if (inParams == null || !inParams.isValid() || !inShowError)
-			{
+			if (inParams == null || !inShowError) {
 				_averageErrorLabel.setText("");
 			}
-			else
-			{
+			else {
 				_averageErrorLabel.setText(DisplayUtils.formatOneDp(inAverageError) + " %");
 			}
 		}
 	}
 
+	/** @return the number formatted using the current locale (and 2 decimal places) */
+	private String formatMinutes(double inMinutes) {
+		return _numberFormatter.format(inMinutes);
+	}
+
 	/**
 	 * Just show the parameters, with no average error
 	 * @param inParams parameters to show
 	 */
-	public void updateParameters(EstimationParameters inParams)
-	{
+	public void updateParameters(EstimationParameters inParams) {
 		updateParameters(inParams, 0.0, false);
 	}
 
@@ -151,8 +158,7 @@ public class ParametersPanel extends JPanel
 	 * @param inParams parameters to show
 	 * @param inAverageError average error as percentage
 	 */
-	public void updateParameters(EstimationParameters inParams, double inAverageError)
-	{
+	public void updateParameters(EstimationParameters inParams, double inAverageError) {
 		updateParameters(inParams, inAverageError, true);
 	}
 }


=====================================
tim/prune/gui/DecimalNumberField.java
=====================================
@@ -1,19 +1,28 @@
 package tim.prune.gui;
 
 import java.awt.Dimension;
+import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
 
 import javax.swing.JTextField;
 import javax.swing.text.AttributeSet;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.PlainDocument;
 
+
 /**
  * Text field for holding a decimal number with validation
  * - doesn't allow certain characters such as a-z to be entered
  */
 public class DecimalNumberField extends JTextField
 {
+	/** Are negative numbers allowed or not */
+	private final boolean _allowNegative;
+	/** formatter responsible for parsing and formatting */
+	private final NumberFormat _localFormatter;
+
 	/**
 	 * Inner class to act as document for validation
 	 */
@@ -21,6 +30,7 @@ public class DecimalNumberField extends JTextField
 	{
 		private final boolean _allowNegative;
 		private final char _decimalPoint;
+		private static final int MAX_LENGTH = 10;
 
 		/** constructor */
 		DecimalNumberDocument(boolean inAllowNegative)
@@ -40,6 +50,9 @@ public class DecimalNumberField extends JTextField
 		public void insertString(int offs, String str, AttributeSet a)
 			throws BadLocationException
 		{
+			if (getLength() > MAX_LENGTH) {
+				return;
+			}
 			StringBuilder buffer = new StringBuilder();
 			for (int i = 0; i < str.length(); i++)
 			{
@@ -70,45 +83,64 @@ public class DecimalNumberField extends JTextField
 	public DecimalNumberField(boolean inAllowNegative)
 	{
 		super(6);
+		_allowNegative = inAllowNegative;
 		setDocument(new DecimalNumberDocument(inAllowNegative));
+		_localFormatter = NumberFormat.getNumberInstance();
+		if (_localFormatter instanceof DecimalFormat) {
+			((DecimalFormat) _localFormatter).applyPattern("0.00");
+		}
+	}
+
+	@Override
+	public String getText() {
+		throw new IllegalArgumentException("Should not be called, use getValue instead");
 	}
 
 	/**
 	 * @return double value
 	 */
-	public double getValue() {
-		return parseValue(getText());
+	public double getValue()
+	{
+		double value = 0.0;
+		try {
+			value = _localFormatter.parse(super.getText()).doubleValue();
+		}
+		catch (NumberFormatException | ParseException e) {
+			// value stays zero
+		}
+		if (!_allowNegative) {
+			value = Math.max(0.0, value);
+		}
+		return value;
 	}
 
 	/**
 	 * @return true if there is no text in the field
 	 */
 	public boolean isEmpty() {
-		return getText().isEmpty();
+		return super.getText().isEmpty();
 	}
 
 	/**
 	 * @param inValue value to set
 	 */
-	public void setValue(double inValue) {
-		setText("" + inValue);
+	public void setValue(double inValue)
+	{
+		double valueToSet = _allowNegative ? inValue : Math.max(0.0, inValue);
+		setText(_localFormatter.format(valueToSet));
 	}
 
 	/**
-	 * @param inText text to parse
-	 * @return value as double
+	 * @param inValue String value to set, using regular (non-local) formatting
 	 */
-	private static double parseValue(String inText)
+	public void setValue(String inValue)
 	{
-		double value = 0.0;
 		try {
-			value = Double.parseDouble(inText);
+			setValue(Double.parseDouble(inValue));
 		}
-		catch (NumberFormatException nfe) {}
-		if (value < 0) {
-			value = 0;
+		catch (Exception e) {
+			setValue(0.0);
 		}
-		return value;
 	}
 
 	/**


=====================================
tim/prune/gui/WholeNumberField.java
=====================================
@@ -68,6 +68,13 @@ public class WholeNumberField extends JTextField
 		});
 	}
 
+	/**
+	 * @return true if there is no text in the field
+	 */
+	public boolean isEmpty() {
+		return getText().isEmpty();
+	}
+
 	/**
 	 * @return integer value
 	 */


=====================================
tim/prune/jpeg/JpegData.java
=====================================
@@ -1,7 +1,5 @@
 package tim.prune.jpeg;
 
-import java.util.ArrayList;
-
 /**
  * Class to hold the GPS data extracted from a Jpeg including position and time
  */
@@ -22,7 +20,6 @@ public class JpegData
 	private int _orientationCode = -1;
 	private byte[] _thumbnail = null;
 	private double _bearing = -1.0;
-	private ArrayList<String> _errors = null;
 
 
 	/**
@@ -32,6 +29,7 @@ public class JpegData
 	{
 		_exifDataPresent = true;
 	}
+
 	/**
 	 * @return true if exif data found
 	 */
@@ -216,39 +214,4 @@ public class JpegData
 			&& (_longitudeRef == 'E' || _longitudeRef == 'e' || _longitudeRef == 'W' || _longitudeRef == 'w')
 			&& _longitude != null;
 	}
-
-	/**
-	 * Add the given error message to the list of errors
-	 * @param inError String containing error message
-	 */
-	public void addError(String inError)
-	{
-		if (_errors == null) _errors = new ArrayList<String>();
-		_errors.add(inError);
-	}
-
-	/**
-	 * @return the number of errors, if any
-	 */
-	public int getNumErrors()
-	{
-		if (_errors == null) return 0;
-		return _errors.size();
-	}
-
-	/**
-	 * @return true if errors occurred
-	 */
-	public boolean hasErrors()
-	{
-		return getNumErrors() > 0;
-	}
-
-	/**
-	 * @return all errors as a list
-	 */
-	public ArrayList<String> getErrors()
-	{
-		return _errors;
-	}
 }


=====================================
tim/prune/lang/prune-texts_es.properties
=====================================
@@ -84,6 +84,7 @@ function.exportgpx=Exportar GPX
 function.exportpov=Exportar POV
 function.exportimage=Exportar imagen
 function.editwaypointname=Editar nombre de waypoint
+function.togglesegmentflag=Activar/desactivar el inicio del segmento
 function.compress=Comprimir track
 function.deletemarked=Eliminar puntos marcados
 function.marklifts=Marcar telef\u00e9ricos
@@ -114,15 +115,19 @@ function.splitsegments=Segmentar el track
 function.sewsegments=Ensamblar los segmentos
 function.createmarkerwaypoints=Crear waypoints marcadores
 function.lookupsrtm=Obtener altitudes de SRTM
+function.configuresrtmsources=Configurar las fuentes de SRTM
 function.getwikipedia=Obtener art\u00edculos de Wikipedia cercanos
 function.searchwikipedianames=Buscar en Wikipedia por nombre
 function.searchosmpois=Buscar en OSM cercanos
 function.searchopencachingde=Buscar en OpenCaching.de
 function.downloadosm=Descargar datos OSM del \u00e1rea
+function.truncatecoords=Truncar las coordenadas
 function.duplicatepoint=Duplicar punto
 function.projectpoint=Proyectar punto
-function.setcolours=Establecer color
+function.projectcircle=Dibujar un c\u00edrculo alrededor del punto
+function.setcolours=Configurar los colores
 function.setdisplaysettings=Establecer opciones para la visualizaci\u00f3n
+function.setwaypointdisplay=Opciones para los waypoints
 function.setlanguage=Establecer lenguaje
 function.connecttopoint=Conectar con punto
 function.disconnectfrompoint=Desconectar de punto
@@ -298,6 +303,7 @@ dialog.pointnameedit.name=Nombre de waypoint
 dialog.pointnameedit.uppercase=May\u00fasculas
 dialog.pointnameedit.lowercase=min\u00fasculas
 dialog.pointnameedit.titlecase=Mezcla
+dialog.truncatecoords.numdigits=N\u00famero de d\u00edgitos decimales
 dialog.truncatecoords.preview=Previsi\u00f3n
 dialog.addtimeoffset.add=A\u00f1adir tiempo
 dialog.addtimeoffset.subtract=Sustraer tiempo
@@ -537,6 +543,7 @@ dialog.deletefieldvalues.intro=Seleccionar el campo a eliminar para el rango act
 dialog.deletefieldvalues.nofields=No hay campos a eliminar para el rango actual
 dialog.displaysettings.linewidth=Anchura de las l\u00edneas para los recorridos (1-4)
 dialog.displaysettings.antialias=Usar antialiasing
+dialog.displaysettings.allowosscaling=Permitir la ampliaci\u00f3n de las im\u00e1genes de los mapas
 dialog.displaysettings.waypointicons=Iconos de los waypoints
 dialog.displaysettings.wpicon.default=Por defecto
 dialog.displaysettings.wpicon.ringpt=Marcador redondeado
@@ -546,6 +553,8 @@ dialog.displaysettings.wpicon.pin=Chincheta
 dialog.displaysettings.size.small=Peque\u00f1os
 dialog.displaysettings.size.medium=Medianos
 dialog.displaysettings.size.large=Grandes
+dialog.waypointsettings.usecolours=Pintar seg\u00fan el tipo
+dialog.waypointsettings.saltvalue=Grupo de colores
 dialog.displaysettings.windowstyle=Estilo de la ventana
 dialog.displaysettings.windowstyle.default=Por defecto
 dialog.displaysettings.windowstyle.nimbus=Nimbus
@@ -598,10 +607,13 @@ dialog.markers.half.climb=Mitad de cuesta
 dialog.markers.half.descent=Mitad de descenso
 dialog.projectpoint.desc=Introduzca la direcci\u00f3n y la distancia para proyectar este punto
 dialog.projectpoint.bearing=Azimut (grados desde el Norte)
+dialog.configuresrtm.threesecond=Datos de baja resoluci\u00f3n (3 segundos)
+dialog.configuresrtm.onesecond=Datos de alta resoluci\u00f3n (1 segundo)
 
 ## 3d window
 dialog.3d.title=GpsPrune vista 3-D
 dialog.3d.altitudefactor=Factor de exageraci\u00f3n de altura
+dialog.3d.symbolscalefactor=Factor de escala para las formas
 
 ## Confirm messages
 confirm.loadfile=Dato cargado de
@@ -683,6 +695,7 @@ button.addnew=A\u00f1adir nuevo
 button.delete=Eliminar
 button.manage=Administrar
 button.combine=Combinar
+button.apply=Aplicar
 
 ## File types
 filetype.txt=Archivos TXT


=====================================
tim/prune/lang/prune-texts_fr.properties
=====================================
@@ -155,6 +155,7 @@ function.selecttimezone=Choisir le fuseau horaire
 # Dialogs
 dialog.exit.confirm.title=Quitter GpsPrune
 dialog.exit.unsaveddata.text=Les donn\u00e9es ont \u00e9t\u00e9 modifi\u00e9es. Souhaitez-vous quitter GpsPrune sans les enregistrer ?
+dialog.exit.unsavedsettings.text=Il est recommand\u00e9 d'enregistrer les pr\u00e9f\u00e9rences avant de quitter. Sortir quand m\u00eame ?
 dialog.openappend.title=Ajouter aux donn\u00e9es existantes
 dialog.openappend.text=Ajouter aux donn\u00e9es d\u00e9j\u00e0 charg\u00e9es ?
 dialog.deletepoint.title=Effacer le point
@@ -217,6 +218,7 @@ dialog.gpsbabel.filter.simplify.maxerror=ou erreur <
 dialog.gpsbabel.filter.simplify.crosstrack=d\u00e9viation
 dialog.gpsbabel.filter.simplify.length=changement de longeur
 dialog.gpsbabel.filter.simplify.relative=par rapport \u00e0 la pr\u00e9cision
+dialog.gpsbabel.filter.distance.intro=Supprimer les points s'ils sont proches des points pr\u00e9c\u00e9dents
 dialog.gpsbabel.filter.distance.distance=Si la distance <
 dialog.gpsbabel.filter.distance.time=et difference de temps <
 dialog.gpsbabel.filter.interpolate.intro=Ajouter des points suppl\u00e9mentaires
@@ -299,6 +301,7 @@ dialog.pointnameedit.name=Nom de waypoint
 dialog.pointnameedit.uppercase=CASSE MAJUSCULES
 dialog.pointnameedit.lowercase=casse minuscules
 dialog.pointnameedit.titlecase=Casse Phrase
+dialog.truncatecoords.intro=S\u00e9lectionnez le format des coordonn\u00e9es et le nombre de chiffres d\u00e9cimaux.
 dialog.truncatecoords.numdigits=Nombre de chiffres d\u00e9cimaux
 dialog.truncatecoords.preview=Aper\u00e7u
 dialog.addtimeoffset.add=Retarder l'heure
@@ -551,6 +554,7 @@ dialog.displaysettings.size.medium=Moyen
 dialog.displaysettings.size.large=Grand
 dialog.waypointsettings.usecolours=Colorier selon type
 dialog.waypointsettings.saltvalue=S\u00e9rie de couleurs
+dialog.waypointsettings.notypesintrack=La piste actuelle ne contient pas de points avec des types.
 dialog.displaysettings.windowstyle=Style de fen\u00eatre (apr\u00e8s red\u00e9marrage)
 dialog.displaysettings.windowstyle.default=D\u00e9faut
 dialog.downloadosm.desc=Confirmer le t\u00e9l\u00e9chargement des donn\u00e9es OSM brutes pour la zone indiqu\u00e9e :
@@ -602,9 +606,12 @@ dialog.markers.half.descent=Demi-descente
 dialog.projectpoint.desc=Saisissez la direction et la distance de la projection
 dialog.projectpoint.bearing=Azimut (degr\u00e8s du nord)
 dialog.projectcircle.desc=Saisissez le rayon du cercle
+dialog.configuresrtm.intro1=Les altitudes du SRTM peuvent provenir soit des donn\u00e9es basse r\u00e9solution sans mot de passe,
+dialog.configuresrtm.intro2=soit en s'enregistrant aupr\u00e8s de la NASA pour les donn\u00e9es haute r\u00e9solution.
 dialog.configuresrtm.threesecond=Donn\u00e9es \u00e0 basse r\u00e9solution (3 secondes d'arc)
 dialog.configuresrtm.threesecond.desc=Les donn\u00e9es \u00e0 basse r\u00e9solution sont toujours activ\u00e9es
 dialog.configuresrtm.onesecond=Donn\u00e9es \u00e0 haute r\u00e9solution (1 seconde d'arc)
+dialog.configuresrtm.onesecond.desc1=Les donn\u00e9es haute r\u00e9solution n\u00e9cessitent un enregistrement \u00e0 NASA Earthdata.
 dialog.configuresrtm.onesecond.desc2=Cr\u00e9er un compte sur https://urs.earthdata.nasa.gov/users/new
 dialog.configuresrtm.showregistrationwebsite=Aller maintenant sur le site de la NASA pour l'inscription ?
 dialog.configuresrtm.userid=Nom d'utilisateur \u00e0 NASA Earthdata
@@ -655,13 +662,16 @@ confirm.deletefieldvalues=Valeurs effac\u00e9es
 confirm.audioload=Fichiers audio ajout\u00e9s
 confirm.correlateaudios.single=fichier audio a \u00e9t\u00e9 corr\u00e9l\u00e9
 confirm.correlateaudios.multi=fichiers audio ont \u00e9t\u00e9 corr\u00e9l\u00e9s
+confirm.applytimestamps=Les horodatages ont \u00e9t\u00e9 appliqu\u00e9s \u00e0 la s\u00e9lection
 
 # Tips
 tip.title=Astuce
 tip.useamapcache=Si vous configurez un cache (Pr\u00e9f\u00e9rences -> Enregistrer les cartes sur le disque)\nl'affichage sera plus rapide et les t\u00e9l\u00e9chargements seront r\u00e9duits.
 tip.learntimeparams=Les r\u00e9sultats seront plus pr\u00e9cis si GpsPrune peut\napprender la vitesse de vos traces\n(Trace -> Apprentissage de l'estimation).
+tip.downloadsrtm=Vous pouvez acc\u00e9l\u00e9rer ce processus en cr\u00e9ant un cache disque\npour sauvegarder les donn\u00e9es SRTM localement.
 tip.usesrtmfor3d=Cette trace n'a pas d'altitudes.\nEn utilisant le SRTM, il est possible d'obtenir\ndes altitudes approximatives.
 tip.manuallycorrelateone=En corr\u00e9lant manuellement au moins une photo, le d\u00e9calage de temps peut \u00eatre calcul\u00e9 pour vous.
+tip.nonstandardconfigfilename=En choisissant un nom de fichier non standard pour les param\u00e8tres,\nvous devrez dire \u00e0 GpsPrune au d\u00e9marrage comment le trouver\nen utilisant le param\u00e8tre "--configfile".
 
 # Buttons
 button.ok=OK
@@ -905,5 +915,6 @@ error.playaudiofailed=\u00c9chec de la lecture du fichier audio
 error.cache.notthere=Le dossier du cache n'a pas \u00e9t\u00e9 trouv\u00e9
 error.cache.empty=Le dossier du cache est vide
 error.cache.cannotdelete=Effacement des dalles impossible
+error.learnestimationparams.failed=Impossible d'apprendre les param\u00e8tres de cette piste.\nEssayez de charger d'autres pistes.
 error.tracksplit.nosplit=Impossible de s\u00e9parer les segments
 error.sewsegments.nothingdone=Aucune fusion n'a \u00e9t\u00e9 possible.\nIl y a %d segments dans la trace.


=====================================
tim/prune/lang/prune-texts_it.properties
=====================================
@@ -84,6 +84,7 @@ function.exportgpx=Esporta in GPX
 function.exportpov=Esporta in POV
 function.exportimage=Esporta come immagine
 function.editwaypointname=Modifica nome waypoint
+function.togglesegmentflag=Accendere/spegnere il segmento
 function.compress=Comprimi la traccia
 function.marklifts=Marca seggiovie
 function.deleterange=Cancella la serie
@@ -122,6 +123,7 @@ function.truncatecoords=Tronca le coordinate
 function.duplicatepoint=Duplica punto corrente in coda
 function.setcolours=Scegli colori
 function.setdisplaysettings=Opzioni di visualizzazione
+function.setwaypointdisplay=Opzioni per i waypoint
 function.setlanguage=Scegli la lingua
 function.connecttopoint=Collega al punto
 function.disconnectfrompoint=Scollega dal punto
@@ -148,6 +150,7 @@ function.getweatherforecast=Ottieni previsioni del tempo
 function.setaltitudetolerance=Configura tolleranza di altitudini
 function.selecttimezone=Seleziona fuso orario
 function.projectpoint=Proiettare il punto
+function.projectcircle=Disegnare un cerchio intorno al punto
 
 # Dialogs
 dialog.exit.confirm.title=Esci da GpsPrune
@@ -546,6 +549,7 @@ dialog.displaysettings.wpicon.pin=Spillo da lavagna
 dialog.displaysettings.size.small=Piccole
 dialog.displaysettings.size.medium=Medie
 dialog.displaysettings.size.large=Grandi
+dialog.waypointsettings.usecolours=Vernice secondo il tipo
 dialog.waypointsettings.saltvalue=Serie di colori
 dialog.displaysettings.windowstyle=Stile della finestra (\u00e8 necessario il riavvio)
 dialog.displaysettings.windowstyle.default=Normale
@@ -606,6 +610,7 @@ dialog.configuresrtm.password=Password per NASA Earthdata
 # 3d window
 dialog.3d.title=Visione GpsPrune in 3D
 dialog.3d.altitudefactor=Fattore di moltiplicazione della quota
+dialog.3d.symbolscalefactor=Fattore di scala per le forme
 
 # Confirm messages
 confirm.loadfile=Dati caricati da file


=====================================
tim/prune/lang/prune-texts_pl.properties
=====================================
@@ -12,7 +12,6 @@ menu.track=\u015acie\u017cka
 menu.track.undo=Cofnij
 menu.track.clearundo=Wyczy\u015b\u0107 list\u0119 zmian
 menu.track.markrectangle=Zaznaczenie prostok\u0105tne
-function.deletemarked=Usu\u0144 zaznaczone punkty
 menu.range=Zakres
 menu.range.all=Zaznacz wszystko
 menu.range.none=Usu\u0144 zaznaczenie
@@ -86,6 +85,7 @@ function.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015bredniego
 function.compress=Kompresuj \u015bcie\u017ck\u0119
 function.marklifts=Zaznaczenie wyci\u0105g narciarski
 function.deleterange=Usu\u0144 zakres
+function.deletemarked=Usu\u0144 zaznaczone punkty
 function.croptrack=Przytnij \u015bcie\u017ck\u0119
 function.interpolate=Wstaw pomi\u0119dzy punkty
 function.deletebydate=Usu\u0144 punkty wed\u0142ug daty
@@ -115,13 +115,16 @@ function.lookupsrtm=Pobierz wysoko\u015bci z SRTM
 function.configuresrtmsources=\u0179r\u00f3d\u0142a SRTM
 function.getwikipedia=Szukaj w Wikipedii o okolicy
 function.searchwikipedianames=Szukaj nazwy w Wikipedii
+function.searchosmpois=Uzyska\u0107 punkty w pobli\u017Cu z OSM
 function.searchopencachingde=Szukaj w OpenCaching.de
 function.downloadosm=Za\u0142aduj dane obszaru z OSM
+function.truncatecoords=Skr\u00f3ci\u0107 wsp\u00f3\u0142rz\u0119dne
 function.duplicatepoint=Duplikuj plik
 function.projectpoint=Rzucaj plik
 function.projectcircle=Narysuj okr\u0105g wok\u00f3\u0142 punktu
 function.setcolours=Ustaw kolory
-function.setdisplaysettings=Ustawienia wy\u015bwietlacza
+function.setdisplaysettings=Opcje dla wy\u015Bwietlacza
+function.setwaypointdisplay=Opcje dla punkt\u00f3w trasy
 function.setlanguage=Zmie\u0144 j\u0119zyk
 function.connecttopoint=Przy\u0142\u0105cz do punktu
 function.disconnectfrompoint=Od\u0142\u0105cz od punktu
@@ -539,6 +542,8 @@ dialog.displaysettings.antialias=U\u017Cyj antyaliasingu
 dialog.displaysettings.size.small=Ma\u0142e
 dialog.displaysettings.size.medium=\u015arednie
 dialog.displaysettings.size.large=Du\u017ce
+dialog.waypointsettings.usecolours=U\u017Cyj koloru dla ka\u017Cdego typu
+dialog.waypointsettings.saltvalue=Grupa kolor\u00f3w
 dialog.displaysettings.windowstyle=Styl okna (wymaga to restartu)
 dialog.displaysettings.windowstyle.default=Domy\u015Blny
 dialog.downloadosm.desc=Potwierd\u017a \u015bci\u0105gni\u0119cie danych dla tego obszaru z OSM:
@@ -587,6 +592,8 @@ dialog.markers.half.descent=Po\u0142owa zjazdu
 dialog.projectpoint.bearing=Kierunek (stopnie od p\u00f3\u0142nocy)
 dialog.configuresrtm.threesecond=Dane o niskiej rozdzielczo\u015bci (3 arcsekundy)
 dialog.configuresrtm.onesecond=Dane o wysokiej rozdzielczo\u015bci (1 arcsekunda)
+dialog.configuresrtm.userid=Nazwa u\u017Cytkownika w NASA Earthdata
+dialog.configuresrtm.password=Has\u0142o w NASA Earthdata
 
 # 3d window
 dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
@@ -672,6 +679,7 @@ button.addnew=Dodaj nowy
 button.delete=Usu\u0144
 button.manage=Zarz\u0105dzaj
 button.combine=Po\u0142\u0105cz
+button.apply=Zastosowa\u0107
 
 # File types
 filetype.txt=Pliki TXT


=====================================
tim/prune/load/babel/DistanceFilter.java
=====================================
@@ -70,7 +70,7 @@ public class DistanceFilter extends FilterDefinition
 	 */
 	public boolean isFilterValid()
 	{
-		if (_distField.getText() == null || _distField.getText().trim().equals("")) {
+		if (_distField.isEmpty()) {
 			return false; // no distance given
 		}
 		final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;


=====================================
tim/prune/load/babel/InterpolateFilter.java
=====================================
@@ -93,8 +93,8 @@ public class InterpolateFilter extends FilterDefinition
 	{
 		if (!isFilterValid()) return null;
 		StringBuilder builder = new StringBuilder();
-		final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
-		final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+		final boolean distGiven = !_distField.isEmpty();
+		final boolean timeGiven = !_secondsField.isEmpty();
 		if (distGiven)
 		{
 			// Get the distance


=====================================
tim/prune/load/babel/SimplifyFilter.java
=====================================
@@ -91,8 +91,8 @@ public class SimplifyFilter extends FilterDefinition
 	 */
 	public boolean isFilterValid()
 	{
-		final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
-		final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+		final boolean countGiven = !_maxPointsField.isEmpty();
+		final boolean distGiven = !_distField.isEmpty();
 		if ((!countGiven && !distGiven) || (countGiven && distGiven)) {
 			return false; // only one or the other allowed
 		}
@@ -114,18 +114,14 @@ public class SimplifyFilter extends FilterDefinition
 		if (!isFilterValid()) return null;
 		StringBuilder builder = new StringBuilder();
 		// type
-		final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
-		final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+		final boolean countGiven = !_maxPointsField.isEmpty();
+		final boolean distGiven = !_distField.isEmpty();
 		if (countGiven) {
 			builder.append(",count=").append(_maxPointsField.getValue());
 		}
 		else if (distGiven)
 		{
-			double dValue = 1.0;
-			try {
-				dValue = Double.parseDouble(_distField.getText());
-			}
-			catch (Exception e) {} // shouldn't happen, otherwise validation would have failed
+			double dValue = _distField.getValue();
 			builder.append(",error=").append(dValue);
 			// units of distance (miles by default)
 			if (_distUnitsCombo.getSelectedIndex() == 0) {


=====================================
tim/prune/readme.txt
=====================================
@@ -1,5 +1,5 @@
-GpsPrune version 22
-===================
+GpsPrune version 22.2
+=====================
 
 GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting, 3d visualisation, audio and photo correlation, and online resource lookup.
@@ -17,7 +17,7 @@ Running
 =======
 
 To run GpsPrune from the jar file, simply call it from a command prompt or shell:
-   java -jar gpsprune_22.jar
+   java -jar gpsprune_22.2.jar
 
 If the jar file is saved in a different directory, you will need to include the path.
 Depending on your system settings, you may be able to click or double-click on the jar file
@@ -25,7 +25,18 @@ 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 gpsprune_22.jar --lang=DE
+   java -jar gpsprune_22.2.jar --lang=DE
+
+
+New with version 22.2
+=====================
+  - Fix for a bug in the 3d shape scaling when locale uses decimal commas (Issue #62)
+  - Fix for a bug with applying calculated timestamps to the selected range
+
+New with version 22.1
+=====================
+  - Removal of Yahoo maps and Inlinemap (from the "map in a browser window" menu)
+  - Removal of the built-in Mapillary function, and replacement with a browser call
 
 
 New with version 22


=====================================
tim/prune/save/PovExporter.java
=====================================
@@ -228,7 +228,7 @@ public class PovExporter extends Export3dFunction
 		altFactorLabel.setHorizontalAlignment(SwingConstants.TRAILING);
 		centralPanel.add(altFactorLabel);
 		_altitudeFactorField = new DecimalNumberField();
-		_altitudeFactorField.setText("1.0");
+		_altitudeFactorField.setValue(1.0);
 		centralPanel.add(_altitudeFactorField);
 		// Symbol scaling
 		JLabel symbolScaleLabel = new JLabel(I18nManager.getText("dialog.3d.symbolscalefactor"));
@@ -385,6 +385,7 @@ public class PovExporter extends Export3dFunction
 				_symbolScaleField.setValue(1.0);
 			}
 			_symbolScaleFactor = SymbolScaleFactor.validateFactor(_symbolScaleField.getValue());
+			_symbolScaleField.setValue(_symbolScaleFactor);
 
 			// Write base image if necessary
 			ImageDefinition imageDef = _baseImagePanel.getImageDefinition();


=====================================
tim/prune/undo/UndoApplyTimestamps.java
=====================================
@@ -28,14 +28,14 @@ public class UndoApplyTimestamps implements UndoOperation
 		Track track = inTrackInfo.getTrack();
 		Selection selection = inTrackInfo.getSelection();
 		_selectionStart = selection.getStart();
-		int numPoints = selection.getEnd() - selection.getStart() + 1;
+		int numPoints = selection.getEnd() - _selectionStart + 1;
 		_timestamps = new String[numPoints];
-		for (int i=selection.getStart(); i<=selection.getEnd(); i++)
+		for (int i=_selectionStart; i<=selection.getEnd(); i++)
 		{
 			DataPoint point = track.getPoint(i);
 			if (point.isWaypoint()) continue;
 			if (point.hasTimestamp()) {
-				_timestamps[i] = point.getFieldValue(Field.TIMESTAMP);
+				_timestamps[i-_selectionStart] = point.getFieldValue(Field.TIMESTAMP);
 			}
 		}
 	}



View it on GitLab: https://salsa.debian.org/debian-gis-team/gpsprune/-/commit/1279e93488096d51b4c099c4840647f9406eaa28

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/gpsprune/-/commit/1279e93488096d51b4c099c4840647f9406eaa28
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20221003/f1103c09/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list