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;
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;
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;
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;
{
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
if (_fileSaver == null) {
_fileSaver = new FileSaver(this, _frame, _track);
}
- _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter());
+ char delim = ',';
+ if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
+ _fileSaver.showDialog(delim);
}
}
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
}
}
+ /**
+ * 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
*/
}
+ /**
+ * 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
if (success)
{
_undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.rearrangewaypoints"));
}
else
{
}
+ /**
+ * 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
*/
* @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();
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
{
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"));
+ }
}
}
// 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++;
*/
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");
+ }
}
/**
--- /dev/null
+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;
+ }
+}
--- /dev/null
+package tim.prune;
+
+/**
+ * Exception thrown when something went wrong with the config
+ */
+public class ConfigException extends Exception
+{
+
+}
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;
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();
}
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);
// 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
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;
/**
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;
/**
// 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
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;
}
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;
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;
url = url + "(" + currPoint.getWaypointName() + ")";
}
}
+ //System.out.println(url);
return url;
}
--- /dev/null
+
+== 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
+
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++)
{
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
{
try
{
+ _stringValue = inString;
_value = (int) Double.parseDouble(inString.trim());
_format = inFormat;
_valid = true;
*/
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)
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
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;
}
// 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)
{
if (cardinal == NO_CARDINAL) {
cardinal = getCardinal(inLastChar);
}
- // use default from concrete subclass
- if (cardinal == NO_CARDINAL) {
- cardinal = getDefaultCardinal();
- }
return cardinal;
}
_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);
}
--- /dev/null
+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;
+ }
+}
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;
/**
_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;
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;
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
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
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
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);
}
* @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);
}
import tim.prune.UpdateMessageBroker;
import tim.prune.edit.FieldEdit;
import tim.prune.edit.FieldEditList;
+import tim.prune.gui.map.MapUtils;
/**
// 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;
// (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;
}
+ /**
+ * 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
}
+ /**
+ * 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
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
*/
// 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++)
{
_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;
}
+ /**
+ * 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
*/
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);
}
/**
*/
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
return point;
}
}
+ return null;
}
/**
/**
* 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;
}
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));
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") + " : "),
--- /dev/null
+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");
+ }
+ }
+ }
+}
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;
// 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
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") + ": ";
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("");
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();
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)
{
}
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);
{
_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();
{
// no photo, hide details
_photoLabel.setText(I18nManager.getText("details.nophoto"));
+ _photoTimestampLabel.setText("");
_photoConnectedLabel.setText("");
_photoThumbnail.setVisible(false);
}
{
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")));
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";
}
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;
--- /dev/null
+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));
+ }
+}
+++ /dev/null
-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
- }
-}
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;
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;
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;
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;
};
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() {
});
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"));
_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"));
});
_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)
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
});
_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);
}
});
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;
{
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);
_selectNoneItem.setEnabled(hasData);
if (_show3dItem != null)
_show3dItem.setEnabled(hasData);
- _showOsmMapItem.setEnabled(hasData);
_browserMapMenu.setEnabled(hasData);
// is undo available?
boolean hasUndo = !_app.getUndoStack().isEmpty();
_editPointButton.setEnabled(hasPoint);
_editWaypointNameItem.setEnabled(hasPoint);
_deletePointItem.setEnabled(hasPoint);
+ _deletePointButton.setEnabled(hasPoint);
_selectStartItem.setEnabled(hasPoint);
_selectStartButton.setEnabled(hasPoint);
_selectEndItem.setEnabled(hasPoint);
_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
// 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)));
}
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
_thread = new Thread(this);
_thread.start();
}
- // TODO: Emphasize status bar when text set, eg change colour, make bold or something
}
/**
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}
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();
}
/**
*/
public Dimension getMinimumSize()
{
- final Dimension minSize = new Dimension(512, 512);
+ final Dimension minSize = new Dimension(512, 300);
return minSize;
}
{
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();
+ }
}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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)));
+ }
+}
+++ /dev/null
-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;
- }
-}
# 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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
# 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
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
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
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
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
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
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
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
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
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
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
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
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
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.
# 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
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
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
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
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)
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
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ä
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
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
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.
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
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
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
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
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
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
# 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
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
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
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
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
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
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
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
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
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
confirm.photo.disconnect=Foto desconectado
confirm.correlate.single=foto fue correlada
confirm.correlate.multi=fotos fueron correladas
+confirm.createpoint=
# Buttons
button.ok=Aceptar
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
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
fieldname.custom=Personalizado
fieldname.prefix=Campo
fieldname.distance=Distancia
+fieldname.movingdistance=
fieldname.duration=Duración
# Measurement units
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
# 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
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
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
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
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
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
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
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
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
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
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
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
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
fieldname.custom=Personnalisé
fieldname.prefix=Champ
fieldname.distance=Distance
+fieldname.movingdistance=
fieldname.duration=Durée
# Measurement units
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
--- /dev/null
+# 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.
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
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
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
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
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
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
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
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
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
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
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=
import javax.swing.JOptionPane;
import tim.prune.App;
+import tim.prune.Config;
import tim.prune.I18nManager;
import tim.prune.load.xml.XmlFileLoader;
*/
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)
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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);
+ }
+}
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;
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;
{
_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
}
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);
}
}
_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);
// 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]++;
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);
}
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())
{
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;
- }
}
_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));
// 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);
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
}
super.endElement(uri, localName, qName);
}
+
+ /**
+ * @return The Xml handler used for the parsing
+ */
+ public XmlHandler getHandler()
+ {
+ return _handler;
+ }
}
-Prune version 5
+Prune version 6
===============
Prune is an application for viewing, editing and managing coordinate data from GPS systems,
=======
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
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
==================
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;
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;
/**
// 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");
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();
{
try
{
- buffer.append(point.getAltitude().getValue(altitudeFormat));
+ buffer.append(point.getAltitude().getStringValue(altitudeFormat));
}
catch (NullPointerException npe) {}
}
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")
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;
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;
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
private JDialog _dialog = null;
private JTextField _nameField = null;
private JTextField _descriptionField = null;
+ private JCheckBox _timestampsCheckbox = null;
private JFileChooser _fileChooser = null;
private File _exportFile = null;
_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
});
buttonPanel.add(cancelButton);
dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
return dialogPanel;
}
{
// 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
// 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")
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(""))
{
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;
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));
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));
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;
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
{
// 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())
{
// 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")
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");
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");
// 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");
--- /dev/null
+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;
+ }
+}
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;
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;
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
/**
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
_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"));
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;
}
// 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;
if (exportFile(file))
{
// file saved
+ // Store directory in config for later
+ Config.setWorkingDirectory(file.getParentFile());
}
else
{
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")
" 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",
/**
- * 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:");
}
+ /**
+ * 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
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;
+ }
}
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;
_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();
}
}
+ /**
+ * 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
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;
/**
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);
}
}
*/
public abstract class WindowFactory
{
- private static ThreeDWindow _window = null;
+ private static Java3DWindow _window = null;
/**
* Get a Window object
{
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;
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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();
+ }
+}