From 52bf9e8686c916be37a26a0b75340393d4478b05 Mon Sep 17 00:00:00 2001 From: activityworkshop Date: Sat, 14 Feb 2015 15:09:17 +0100 Subject: [PATCH] Version 6, October 2008 --- tim/prune/App.java | 222 ++++- tim/prune/Config.java | 147 +++ tim/prune/ConfigException.java | 9 + tim/prune/GpsPruner.java | 71 +- tim/prune/I18nManager.java | 40 +- tim/prune/browser/UrlGenerator.java | 9 +- tim/prune/configuration.txt | 62 ++ tim/prune/correlate/PhotoCorrelator.java | 4 +- tim/prune/data/Altitude.java | 23 +- tim/prune/data/Coordinate.java | 13 +- tim/prune/data/DataPoint.java | 9 +- tim/prune/data/LatLonRectangle.java | 50 + tim/prune/data/Selection.java | 50 +- tim/prune/data/Timestamp.java | 21 +- tim/prune/data/Track.java | 170 +++- tim/prune/data/TrackInfo.java | 4 +- tim/prune/gui/AboutScreen.java | 4 +- tim/prune/gui/CheckVersionScreen.java | 97 ++ tim/prune/gui/DetailsDisplay.java | 50 +- tim/prune/gui/GenericChart.java | 2 +- tim/prune/gui/IconManager.java | 65 ++ tim/prune/gui/MapChart.java | 636 ------------ tim/prune/gui/MenuManager.java | 108 ++- tim/prune/gui/SelectorDisplay.java | 1 + tim/prune/gui/StatusBar.java | 1 - tim/prune/gui/TimeOffsetDialog.java | 162 ++++ tim/prune/gui/WholeNumberField.java | 88 ++ tim/prune/gui/images/autopan.gif | Bin 0 -> 203 bytes tim/prune/gui/images/autopan_on.gif | Bin 0 -> 225 bytes tim/prune/gui/images/cut_and_move.gif | Bin 0 -> 932 bytes tim/prune/gui/images/delete_point_icon.gif | Bin 0 -> 598 bytes tim/prune/gui/images/delete_range_icon.gif | Bin 0 -> 417 bytes tim/prune/gui/images/link.gif | Bin 0 -> 920 bytes tim/prune/gui/images/map_icon.gif | Bin 0 -> 362 bytes tim/prune/gui/images/map_icon_on.gif | Bin 0 -> 406 bytes tim/prune/gui/images/points_connected.gif | Bin 0 -> 922 bytes tim/prune/gui/images/points_disconnected.gif | Bin 0 -> 897 bytes tim/prune/gui/images/zoom_in.gif | Bin 0 -> 348 bytes tim/prune/gui/images/zoom_out.gif | Bin 0 -> 342 bytes tim/prune/gui/map/MapCanvas.java | 913 +++++++++++++++--- tim/prune/gui/map/MapPosition.java | 272 ++++++ tim/prune/gui/map/MapTileCacher.java | 196 ++++ tim/prune/gui/map/MapUtils.java | 48 + tim/prune/gui/map/MapWindow.java | 64 -- tim/prune/lang/prune-texts.properties | 68 +- tim/prune/lang/prune-texts_de.properties | 184 ++-- tim/prune/lang/prune-texts_de_CH.properties | 104 +- tim/prune/lang/prune-texts_es.properties | 62 +- tim/prune/lang/prune-texts_fr.properties | 62 +- tim/prune/lang/prune-texts_it.properties | 425 ++++++++ tim/prune/lang/prune-texts_pl.properties | 242 +++-- tim/prune/load/FileLoader.java | 14 + tim/prune/load/GenericFileFilter.java | 123 +++ tim/prune/load/GpsLoader.java | 295 ++++++ tim/prune/load/JpegLoader.java | 79 +- tim/prune/load/TextFileLoader.java | 4 + tim/prune/load/xml/XmlFileLoader.java | 8 + tim/prune/readme.txt | 19 +- tim/prune/save/FileSaver.java | 19 +- tim/prune/save/GpxExporter.java | 52 +- tim/prune/save/KmlExporter.java | 32 +- tim/prune/save/ModelSegment.java | 64 ++ tim/prune/save/PovExporter.java | 288 +++++- tim/prune/threedee/Java3DWindow.java | 22 +- tim/prune/threedee/ThreeDModel.java | 7 +- tim/prune/threedee/WindowFactory.java | 9 +- tim/prune/undo/UndoAddTimeOffset.java | 51 + tim/prune/undo/UndoConnectPhotoWithClone.java | 41 + tim/prune/undo/UndoCreatePoint.java | 37 + tim/prune/undo/UndoCutAndMove.java | 95 ++ 70 files changed, 4734 insertions(+), 1283 deletions(-) create mode 100644 tim/prune/Config.java create mode 100644 tim/prune/ConfigException.java create mode 100644 tim/prune/configuration.txt create mode 100644 tim/prune/data/LatLonRectangle.java create mode 100644 tim/prune/gui/CheckVersionScreen.java create mode 100644 tim/prune/gui/IconManager.java delete mode 100644 tim/prune/gui/MapChart.java create mode 100644 tim/prune/gui/TimeOffsetDialog.java create mode 100644 tim/prune/gui/WholeNumberField.java create mode 100644 tim/prune/gui/images/autopan.gif create mode 100644 tim/prune/gui/images/autopan_on.gif create mode 100644 tim/prune/gui/images/cut_and_move.gif create mode 100644 tim/prune/gui/images/delete_point_icon.gif create mode 100644 tim/prune/gui/images/delete_range_icon.gif create mode 100644 tim/prune/gui/images/link.gif create mode 100644 tim/prune/gui/images/map_icon.gif create mode 100644 tim/prune/gui/images/map_icon_on.gif create mode 100644 tim/prune/gui/images/points_connected.gif create mode 100644 tim/prune/gui/images/points_disconnected.gif create mode 100644 tim/prune/gui/images/zoom_in.gif create mode 100644 tim/prune/gui/images/zoom_out.gif create mode 100644 tim/prune/gui/map/MapPosition.java create mode 100644 tim/prune/gui/map/MapTileCacher.java create mode 100644 tim/prune/gui/map/MapUtils.java delete mode 100644 tim/prune/gui/map/MapWindow.java create mode 100644 tim/prune/lang/prune-texts_it.properties create mode 100644 tim/prune/load/GenericFileFilter.java create mode 100644 tim/prune/load/GpsLoader.java create mode 100644 tim/prune/save/ModelSegment.java create mode 100644 tim/prune/undo/UndoAddTimeOffset.java create mode 100644 tim/prune/undo/UndoConnectPhotoWithClone.java create mode 100644 tim/prune/undo/UndoCreatePoint.java create mode 100644 tim/prune/undo/UndoCutAndMove.java diff --git a/tim/prune/App.java b/tim/prune/App.java index cb532db..ecde548 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -11,8 +11,12 @@ import tim.prune.browser.BrowserLauncher; import tim.prune.browser.UrlGenerator; import tim.prune.correlate.PhotoCorrelator; import tim.prune.correlate.PointPair; +import tim.prune.data.Coordinate; import tim.prune.data.DataPoint; import tim.prune.data.Field; +import tim.prune.data.LatLonRectangle; +import tim.prune.data.Latitude; +import tim.prune.data.Longitude; import tim.prune.data.Photo; import tim.prune.data.PhotoList; import tim.prune.data.Track; @@ -21,9 +25,10 @@ import tim.prune.edit.FieldEditList; import tim.prune.edit.PointEditor; import tim.prune.edit.PointNameEditor; import tim.prune.gui.MenuManager; +import tim.prune.gui.TimeOffsetDialog; import tim.prune.gui.UndoManager; -import tim.prune.gui.map.MapWindow; import tim.prune.load.FileLoader; +import tim.prune.load.GpsLoader; import tim.prune.load.JpegLoader; import tim.prune.save.ExifSaver; import tim.prune.save.FileSaver; @@ -33,9 +38,13 @@ import tim.prune.save.PovExporter; import tim.prune.threedee.ThreeDException; import tim.prune.threedee.ThreeDWindow; import tim.prune.threedee.WindowFactory; +import tim.prune.undo.UndoAddTimeOffset; import tim.prune.undo.UndoCompress; import tim.prune.undo.UndoConnectPhoto; +import tim.prune.undo.UndoConnectPhotoWithClone; import tim.prune.undo.UndoCorrelatePhotos; +import tim.prune.undo.UndoCreatePoint; +import tim.prune.undo.UndoCutAndMove; import tim.prune.undo.UndoDeleteDuplicates; import tim.prune.undo.UndoDeletePhoto; import tim.prune.undo.UndoDeletePoint; @@ -65,13 +74,14 @@ public class App private MenuManager _menuManager = null; private FileLoader _fileLoader = null; private JpegLoader _jpegLoader = null; + private GpsLoader _gpsLoader = null; private FileSaver _fileSaver = null; private KmlExporter _kmlExporter = null; private GpxExporter _gpxExporter = null; private PovExporter _povExporter = null; private BrowserLauncher _browserLauncher = null; private Stack _undoStack = null; - private boolean _reversePointsConfirmed = false; + private boolean _mangleTimestampsConfirmed = false; // Constants public static final int REARRANGE_TO_START = 0; @@ -146,9 +156,18 @@ public class App { if (_jpegLoader == null) _jpegLoader = new JpegLoader(this, _frame); - _jpegLoader.openDialog(); + _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange())); } + /** + * Start a load from Gps + */ + public void beginLoadFromGps() + { + if (_gpsLoader == null) + _gpsLoader = new GpsLoader(this, _frame); + _gpsLoader.openDialog(); + } /** * Save the file in the selected format @@ -165,7 +184,9 @@ public class App if (_fileSaver == null) { _fileSaver = new FileSaver(this, _frame, _track); } - _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter()); + char delim = ','; + if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();} + _fileSaver.showDialog(delim); } } @@ -569,11 +590,11 @@ public class App int selStart = _trackInfo.getSelection().getStart(); int selEnd = _trackInfo.getSelection().getEnd(); if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd) - || _reversePointsConfirmed + || _mangleTimestampsConfirmed || (JOptionPane.showConfirmDialog(_frame, I18nManager.getText("dialog.confirmreversetrack.text"), I18nManager.getText("dialog.confirmreversetrack.title"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true))) + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true))) { UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd); // call track to reverse range @@ -586,6 +607,43 @@ public class App } } + /** + * Trigger the dialog to add a time offset to the current selection + */ + public void beginAddTimeOffset() + { + int selStart = _trackInfo.getSelection().getStart(); + int selEnd = _trackInfo.getSelection().getEnd(); + if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)) { + JOptionPane.showMessageDialog(_frame, + I18nManager.getText("dialog.addtimeoffset.notimestamps"), + I18nManager.getText("dialog.addtimeoffset.title"), JOptionPane.ERROR_MESSAGE); + } + else { + TimeOffsetDialog timeDialog = new TimeOffsetDialog(this, _frame); + timeDialog.showDialog(); + } + } + + /** + * Complete the add time offset function with the specified offset + * @param inTimeOffset time offset to add (+ve for add, -ve for subtract) + */ + public void finishAddTimeOffset(long inTimeOffset) + { + // Construct undo information + int selStart = _trackInfo.getSelection().getStart(); + int selEnd = _trackInfo.getSelection().getEnd(); + UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset); + if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset)) + { + _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset")); + } + } + + /** * Merge the track segments within the current selection */ @@ -632,6 +690,27 @@ public class App } + /** + * Create a new point at the given lat/long coordinates + * @param inLat latitude + * @param inLong longitude + */ + public void createPoint(double inLat, double inLong) + { + // create undo object + UndoCreatePoint undo = new UndoCreatePoint(); + // create point and add to track + DataPoint point = new DataPoint(new Latitude(inLat, Coordinate.FORMAT_NONE), new Longitude(inLong, Coordinate.FORMAT_NONE), null); + point.setSegmentStart(true); + _track.appendPoints(new DataPoint[] {point}); + _trackInfo.getSelection().selectPoint(_trackInfo.getTrack().getNumPoints()-1); + // add undo object to stack + _undoStack.add(undo); + // update listeners + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint")); + } + + /** * Rearrange the waypoints into track order * @param inFunction nearest point, all to end or all to start @@ -653,6 +732,7 @@ public class App if (success) { _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.rearrangewaypoints")); } else { @@ -662,6 +742,46 @@ public class App } + /** + * Cut the current selection and move it to before the currently selected point + */ + public void cutAndMoveSelection() + { + int startIndex = _trackInfo.getSelection().getStart(); + int endIndex = _trackInfo.getSelection().getEnd(); + int pointIndex = _trackInfo.getSelection().getCurrentPointIndex(); + // If timestamps would be mangled by cut/move, confirm + if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex) + || _mangleTimestampsConfirmed + || (JOptionPane.showConfirmDialog(_frame, + I18nManager.getText("dialog.confirmcutandmove.text"), + I18nManager.getText("dialog.confirmcutandmove.title"), + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true))) + { + // Find points to set segment flags + DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex); + DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1); + DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex); + // Make undo object + UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex); + // Call track info to move track section + if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex)) + { + // Set segment start flags (first track point, next track point, move to point) + if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);} + if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);} + if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);} + + // Add undo object to stack, set confirm message + _undoStack.add(undo); + _trackInfo.getSelection().deselectRange(); + UpdateMessageBroker.informSubscribers(); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove")); + } + } + } + + /** * Open a new window with the 3d view */ @@ -716,6 +836,20 @@ public class App * @param inFilename filename used */ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename) + { + informDataLoaded(inFieldArray, inDataArray, inAltFormat, inFilename, false); + } + + /** + * Receive loaded data and optionally merge with current Track + * @param inFieldArray array of fields + * @param inDataArray array of data + * @param inAltFormat altitude format + * @param inFilename filename used + * @param inOverrideAppend true to override append question and always append + */ + public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, + String inFilename, boolean inOverrideAppend) { // Check whether loaded array can be properly parsed into a Track Track loadedTrack = new Track(); @@ -732,10 +866,13 @@ public class App if (_track != null && _track.getNumPoints() > 0) { // ask whether to replace or append - int answer = JOptionPane.showConfirmDialog(_frame, - I18nManager.getText("dialog.openappend.text"), - I18nManager.getText("dialog.openappend.title"), - JOptionPane.YES_NO_CANCEL_OPTION); + int answer = JOptionPane.YES_OPTION; + if (!inOverrideAppend) { + answer = JOptionPane.showConfirmDialog(_frame, + I18nManager.getText("dialog.openappend.text"), + I18nManager.getText("dialog.openappend.title"), + JOptionPane.YES_NO_CANCEL_OPTION); + } if (answer == JOptionPane.YES_OPTION) { // append data to current Track @@ -826,14 +963,39 @@ public class App { Photo photo = _trackInfo.getCurrentPhoto(); DataPoint point = _trackInfo.getCurrentPoint(); - if (photo != null && point != null && point.getPhoto() == null) + if (photo != null && point != null) { - // connect - _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); - photo.setDataPoint(point); - point.setPhoto(photo); - UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); - UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect")); + if (point.getPhoto() != null) + { + // point already has a photo, confirm cloning of new point + if (JOptionPane.showConfirmDialog(_frame, + I18nManager.getText("dialog.connectphoto.clonepoint"), + I18nManager.getText("dialog.connect.title"), + JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) + { + // Create undo, clone point and attach + int pointIndex = _trackInfo.getSelection().getCurrentPointIndex() + 1; + // insert new point after current one + point = point.clonePoint(); + UndoConnectPhotoWithClone undo = new UndoConnectPhotoWithClone( + point, photo.getFile().getName(), pointIndex); + _track.insertPoint(point, pointIndex); + photo.setDataPoint(point); + point.setPhoto(photo); + _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect")); + } + } + else + { + // point doesn't currently have a photo, so just connect it + _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); + photo.setDataPoint(point); + point.setPhoto(photo); + UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect")); + } } } @@ -990,6 +1152,8 @@ public class App // link photo to point pointToAdd.setPhoto(pair.getPhoto()); pair.getPhoto().setDataPoint(pointToAdd); + // set to start of segment so not joined in track + pointToAdd.setSegmentStart(true); // add to point array addedPoints[pointNum] = pointToAdd; pointNum++; @@ -1126,19 +1290,17 @@ public class App */ public void showHelp() { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.help.help"), - I18nManager.getText("menu.help"), - JOptionPane.INFORMATION_MESSAGE); - } - - /** - * Show an OSM map window - */ - public void showOsmMap() - { - MapWindow map = new MapWindow(_track); - map.pack(); - map.show(); + // show the dialog and offer to open home page + Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")}; + if (JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.help.help"), + I18nManager.getText("menu.help"), JOptionPane.YES_NO_OPTION, + JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1]) + == JOptionPane.YES_OPTION) + { + // User selected to launch home page + if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();} + _browserLauncher.launchBrowser("http://activityworkshop.net/software/prune/index.html"); + } } /** diff --git a/tim/prune/Config.java b/tim/prune/Config.java new file mode 100644 index 0000000..d667b59 --- /dev/null +++ b/tim/prune/Config.java @@ -0,0 +1,147 @@ +package tim.prune; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +/** + * Abstract class to hold application-wide configuration + */ +public abstract class Config +{ + /** Working directory for loading and saving */ + private static File _workingDir = null; + /** Default language */ + private static String _langCode = null; + /** GPS device name */ + private static String _gpsDevice = null; + /** GPS format name */ + private static String _gpsFormat = null; + /** Font to use for povray */ + private static String _povrayFont = null; + /** True to use metric units */ + private static boolean _metricUnits = true; + + + /** Default config file */ + private static final File DEFAULT_CONFIG_FILE = new File(".pruneconfig"); + + /** Key for working directory */ + private static final String KEY_WORKING_DIR = "prune.directory"; + /** Key for language code */ + private static final String KEY_LANGUAGE_CODE = "prune.languagecode"; + /** Key for GPS device */ + private static final String KEY_GPS_DEVICE = "prune.gpsdevice"; + /** Key for GPS format */ + private static final String KEY_GPS_FORMAT = "prune.gpsformat"; + /** Key for Povray font */ + private static final String KEY_POVRAY_FONT = "prune.povrayfont"; + /** Key for metric/imperial */ + private static final String KEY_METRIC_UNITS = "prune.metricunits"; + // TODO: Save config file location so save possible + + + /** + * @return working directory for loading and saving + */ + public static File getWorkingDirectory() + { + return _workingDir; + } + + /** + * @param inDirectory working directory to use + */ + public static void setWorkingDirectory(File inDirectory) + { + _workingDir = inDirectory; + } + + /** + * Load the default configuration file + */ + public static void loadDefaultFile() + { + try + { + loadFile(DEFAULT_CONFIG_FILE); + } + catch (ConfigException ce) {} // ignore + } + + + /** + * Load configuration from file + * @param inFile file to load + */ + public static void loadFile(File inFile) throws ConfigException + { + // Start with default properties + Properties props = getDefaultProperties(); + // Try to load the file into a properties object + boolean loadFailed = false; + try + { + props.load(new FileInputStream(inFile)); + } + catch (Exception e) + { + loadFailed = true; + } + // Save the properties we know about, ignore the rest + _langCode = props.getProperty(KEY_LANGUAGE_CODE); + String dir = props.getProperty(KEY_WORKING_DIR); + if (dir != null) {setWorkingDirectory(new File(dir));} + _gpsDevice = props.getProperty(KEY_GPS_DEVICE); + _gpsFormat = props.getProperty(KEY_GPS_FORMAT); + _povrayFont = props.getProperty(KEY_POVRAY_FONT); + String useMetric = props.getProperty(KEY_METRIC_UNITS); + _metricUnits = (useMetric == null || useMetric.equals("") || useMetric.toLowerCase().equals("y")); + if (loadFailed) { + throw new ConfigException(); + } + } + + /** + * @return Properties object containing default values + */ + private static Properties getDefaultProperties() + { + Properties props = new Properties(); + // Fill in defaults + props.put(KEY_GPS_DEVICE, "usb:"); + props.put(KEY_GPS_FORMAT, "garmin"); + props.put(KEY_POVRAY_FONT, "crystal.ttf"); // alternative: DejaVuSans-Bold.ttf + return props; + } + + /** @return language code */ + public static String getLanguageCode() + { + return _langCode; + } + + /** @return gps device */ + public static String getGpsDevice() + { + return _gpsDevice; + } + + /** @return gps format */ + public static String getGpsFormat() + { + return _gpsFormat; + } + + /** @return povray font */ + public static String getPovrayFont() + { + return _povrayFont; + } + + /** @return true to use metric units */ + public static boolean getUseMetricUnits() + { + return _metricUnits; + } +} diff --git a/tim/prune/ConfigException.java b/tim/prune/ConfigException.java new file mode 100644 index 0000000..edf6bee --- /dev/null +++ b/tim/prune/ConfigException.java @@ -0,0 +1,9 @@ +package tim.prune; + +/** + * Exception thrown when something went wrong with the config + */ +public class ConfigException extends Exception +{ + +} diff --git a/tim/prune/GpsPruner.java b/tim/prune/GpsPruner.java index 7208eb7..eafbf19 100644 --- a/tim/prune/GpsPruner.java +++ b/tim/prune/GpsPruner.java @@ -3,30 +3,31 @@ package tim.prune; import java.awt.event.WindowAdapter; import java.awt.BorderLayout; import java.awt.event.WindowEvent; +import java.io.File; import java.util.Locale; - -import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JSplitPane; import javax.swing.JToolBar; import javax.swing.WindowConstants; import tim.prune.gui.DetailsDisplay; -import tim.prune.gui.MapChart; +import tim.prune.gui.IconManager; import tim.prune.gui.MenuManager; import tim.prune.gui.ProfileChart; import tim.prune.gui.SelectorDisplay; import tim.prune.gui.StatusBar; +import tim.prune.gui.map.MapCanvas; /** * Tool to visualize, edit and prune GPS data * Please see the included readme.txt or http://activityworkshop.net + * This software is copyright activityworkshop.net and made available through the Gnu GPL */ public class GpsPruner { - // Final build of version 5 - public static final String VERSION_NUMBER = "5"; - public static final String BUILD_NUMBER = "100"; + // Final build of version 6 + public static final String VERSION_NUMBER = "6"; + public static final String BUILD_NUMBER = "117"; private static App APP = null; @@ -37,23 +38,63 @@ public class GpsPruner public static void main(String[] args) { Locale locale = null; - if (args.length == 1) + String langFilename = null; + String configFilename = null; + boolean showUsage = false; + for (int i=0; i used to specify a configuration file" + + "\n --lang= or --locale= used to specify language" + + "\n --langfile= used to specify an alternative language file\n"); + } + // Initialise configuration if selected + try + { + if (configFilename != null) { + Config.loadFile(new File(configFilename)); + } + else { + Config.loadDefaultFile(); + } + } + catch (ConfigException ce) { + System.err.println("Failed to load config file: " + configFilename); + } + // Set locale according to Config's language property + String langCode = Config.getLanguageCode(); + if (locale == null && langCode != null) { + Locale configLocale = getLanguage(langCode); + if (configLocale != null) {locale = configLocale;} + } I18nManager.init(locale); + if (langFilename != null) { + I18nManager.addLanguageFile(langFilename); + } + // Set up the window and go launch(); } @@ -100,7 +141,7 @@ public class GpsPruner UpdateMessageBroker.addSubscriber(leftPanel); DetailsDisplay rightPanel = new DetailsDisplay(APP.getTrackInfo()); UpdateMessageBroker.addSubscriber(rightPanel); - MapChart mapDisp = new MapChart(APP, APP.getTrackInfo()); + MapCanvas mapDisp = new MapCanvas(APP, APP.getTrackInfo()); UpdateMessageBroker.addSubscriber(mapDisp); ProfileChart profileDisp = new ProfileChart(APP.getTrackInfo()); UpdateMessageBroker.addSubscriber(profileDisp); @@ -131,7 +172,7 @@ public class GpsPruner // set icon try { - frame.setIconImage(new ImageIcon(GpsPruner.class.getResource("gui/images/window_icon.png")).getImage()); + frame.setIconImage(IconManager.getImageIcon(IconManager.WINDOW_ICON).getImage()); } catch (Exception e) {} // ignore diff --git a/tim/prune/I18nManager.java b/tim/prune/I18nManager.java index 5af9898..87d1df4 100644 --- a/tim/prune/I18nManager.java +++ b/tim/prune/I18nManager.java @@ -1,7 +1,11 @@ package tim.prune; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.util.Locale; import java.util.MissingResourceException; +import java.util.Properties; import java.util.ResourceBundle; /** @@ -15,7 +19,10 @@ public abstract class I18nManager private static final Locale BACKUP_LOCALE = new Locale("en", "GB"); private static ResourceBundle EnglishTexts = null; - private static ResourceBundle ExtraTexts = null; + private static ResourceBundle LocalTexts = null; + + /** External properties file for developer testing */ + private static Properties ExternalPropsFile = null; /** @@ -30,16 +37,32 @@ public abstract class I18nManager // Get bundle for selected locale, if any if (inLocale != null) { - ExtraTexts = ResourceBundle.getBundle(BUNDLE_NAME, inLocale); + LocalTexts = ResourceBundle.getBundle(BUNDLE_NAME, inLocale); } else { // locale is null so just use the system default - ExtraTexts = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault()); + LocalTexts = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault()); } } + /** + * Add a language file + * @param inFilename filename of file + */ + public static void addLanguageFile(String inFilename) + { + try + { + File file = new File(inFilename); + ExternalPropsFile = new Properties(); + ExternalPropsFile.load(new FileInputStream(file)); + } + catch (IOException ioe) {} + } + + /** * Lookup the given key and return the associated text * @param inKey key to lookup @@ -48,12 +71,19 @@ public abstract class I18nManager public static String getText(String inKey) { String value = null; + // look in external props file if available + if (ExternalPropsFile != null) + { + value = ExternalPropsFile.getProperty(inKey); + if (value != null && !value.equals("")) + return value; + } // look in extra texts if available - if (ExtraTexts != null) + if (LocalTexts != null) { try { - value = ExtraTexts.getString(inKey); + value = LocalTexts.getString(inKey); if (value != null && !value.equals("")) return value; } diff --git a/tim/prune/browser/UrlGenerator.java b/tim/prune/browser/UrlGenerator.java index 07b8ff1..aa78a22 100644 --- a/tim/prune/browser/UrlGenerator.java +++ b/tim/prune/browser/UrlGenerator.java @@ -1,6 +1,8 @@ package tim.prune.browser; import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; import tim.prune.I18nManager; import tim.prune.data.DataPoint; @@ -14,7 +16,11 @@ import tim.prune.data.TrackInfo; public abstract class UrlGenerator { /** Number formatter for five dp */ - public static final DecimalFormat FIVE_DP = new DecimalFormat("0.00000"); + public static final NumberFormat FIVE_DP = NumberFormat.getNumberInstance(Locale.UK); + // Select the UK locale for this formatter so that decimal point is always used (not comma) + static { + if (FIVE_DP instanceof DecimalFormat) ((DecimalFormat) FIVE_DP).applyPattern("0.00000"); + } /** Constant for Google Maps */ public static final int MAP_SOURCE_GOOGLE = 0; @@ -66,6 +72,7 @@ public abstract class UrlGenerator url = url + "(" + currPoint.getWaypointName() + ")"; } } + //System.out.println(url); return url; } diff --git a/tim/prune/configuration.txt b/tim/prune/configuration.txt new file mode 100644 index 0000000..56e7caa --- /dev/null +++ b/tim/prune/configuration.txt @@ -0,0 +1,62 @@ + +== Prune configuration == +========================= + +Starting with version 6 of Prune, it's possible to create a configuration file which controls a few basic configuration options. Currently there isn't a nice gui to let you select these options, although this may be added in the future. Instead, you can just create a text file with the configuration, and save it with any regular text editor. Then Prune will load these options when it starts up. + +== File location == +By default, Prune will try to load a file called .pruneconfig from the current directory (note the dot at the start of the filename). If this file isn't there, it doesn't matter, it'll just be ignored and Prune will use default settings as before. The file won't be created for you if it doesn't exist. +If you create this file in your home directory and you also launch Prune from the same directory, Prune will find the file and use whatever configuration is specified inside. + +If you want to use another location for this file, or another name, you'll have to tell Prune where it is. You can do this with an extra commandline parameter, like this: + java -jar prune.jar --configfile=h:/gps/pruneconfig.txt +You can obviously set this in any shortcuts or aliases that you're currently using so you don't have to type it in every time. If you specify a file like this and Prune can't find it or can't read it, you'll get a warning message in the console. + +== File structure == +The configuration file is a simple text file, so you can edit it with any editor like Notepad, Kate or gedit. Each line in the file represents a configuration setting, with the format: + key=value +The key should match one of the recognised keys given below. Any unrecognised keys will just be ignored. +The file can contain any number of settings, in any order. You can have just one in there or all possible settings. + +== Possible configuration settings == +The following settings can be defined in the file, in any order: + +== Working directory == +This setting defines the default directory when you start Prune. If your track files are always in a certain directory, you can set this here so that Prune starts from that directory. Example: + prune.directory=/home/user/gps/ + +== Language code == +This setting defines the language to use for Prune's interface. By default, Prune will try to use the language set in your system. If you use the --lang= command line parameter, you can override this to use a different one. Or you can put a line in your configuration file to define it there instead. Possible codes are currently EN, DE, DE_ch, ES, FR, IT and PL. Example: + prune.languagecode=ES +If you use the --lang= command line parameter, this takes priority over what's in the configuration file. + +== Metric/imperial units == +By default Prune uses metric units, displaying altitudes in metres and distances in kilometres. This setting allows you to specify whether you want metric or imperial units by default. Example: + prune.metricunits=n +By using a value of "n" (for "no"), Prune then displays values in imperial units of feet and miles. + +== Povray font == +When Prune exports to Pov format, it specifies a font to use for the cardinals N, S, E, W. Usually this is "crystal.ttf" because this comes as standard with povray. This setting allows you to change that default if you always want to use a specific font installed on your system. Example: + prune.povrayfont=DejaVuSans-Bold.ttf +The available fonts will depend on what you have installed. See the povray documentation for more on this. + +== GPS device == +When Prune invokes Gpsbabel to load data, it needs to specify the GPS device to load from. The name of this will depend on your GPS receiver and your connection (eg serial/usb). If you call gpsbabel from the command line, it's the -f parameter. This setting allows you to specify the default value used by Prune when you select the load from gps function. Example: + prune.gpsdevice=usb: +A GPS connected to the serial port may appear as /dev/ttyS0. + +== GPS format == +This setting controls the default value of the format parameter to gpsbabel. If you call gpsbabel from the command line, it's the -i parameter. Example: + prune.gpsformat=garmin + +== Example file == +The following can be used as a template to make your own file. + +# Prune configuration file +prune.directory=/home/user/gps/ +prune.languagecode=ES +prune.metricunits=y +prune.povrayfont=crystal.ttf +prune.gpsdevice=usb: +prune.gpsformat=garmin + diff --git a/tim/prune/correlate/PhotoCorrelator.java b/tim/prune/correlate/PhotoCorrelator.java index f2c1fc8..ccf5345 100644 --- a/tim/prune/correlate/PhotoCorrelator.java +++ b/tim/prune/correlate/PhotoCorrelator.java @@ -536,8 +536,8 @@ public class PhotoCorrelator private static PointPair getPointPairForPhoto(Track inTrack, Photo inPhoto, TimeDifference inOffset) { PointPair pair = new PointPair(inPhoto); - // Add offet to photo timestamp - Timestamp photoStamp = inPhoto.getTimestamp().subtractOffset(inOffset); + // Add/subtract offet to photo timestamp + Timestamp photoStamp = inPhoto.getTimestamp().createMinusOffset(inOffset); int numPoints = inTrack.getNumPoints(); for (int i=0; i 1) { // Check for cardinal character either at beginning or end + boolean hasCardinal = true; _cardinal = getCardinal(inString.charAt(0), inString.charAt(strLen-1)); + if (_cardinal == NO_CARDINAL) { + hasCardinal = false; + // use default from concrete subclass + _cardinal = getDefaultCardinal(); + } + // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s int numFields = 0; boolean inNumeric = false; @@ -93,7 +100,7 @@ public abstract class Coordinate } // parse fields according to number found _degrees = (int) fields[0]; - _originalFormat = FORMAT_DEG; + _originalFormat = hasCardinal?FORMAT_DEG:FORMAT_DEG_WITHOUT_CARDINAL; _fracDenom = 10; if (numFields == 2) { @@ -147,10 +154,6 @@ public abstract class Coordinate if (cardinal == NO_CARDINAL) { cardinal = getCardinal(inLastChar); } - // use default from concrete subclass - if (cardinal == NO_CARDINAL) { - cardinal = getDefaultCardinal(); - } return cardinal; } diff --git a/tim/prune/data/DataPoint.java b/tim/prune/data/DataPoint.java index 38ec19a..4cf7c82 100644 --- a/tim/prune/data/DataPoint.java +++ b/tim/prune/data/DataPoint.java @@ -70,8 +70,13 @@ public class DataPoint _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_DEG_MIN_SEC); _longitude = inLongitude; _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_DEG_MIN_SEC); - _altitude = inAltitude; - if (inAltitude != null) {_fieldValues[2] = "" + inAltitude.getValue(Altitude.FORMAT_METRES);} + if (inAltitude == null) { + _altitude = Altitude.NONE; + } + else { + _altitude = inAltitude; + _fieldValues[2] = "" + inAltitude.getValue(Altitude.FORMAT_METRES); // units are ignored + } _timestamp = new Timestamp(null); } diff --git a/tim/prune/data/LatLonRectangle.java b/tim/prune/data/LatLonRectangle.java new file mode 100644 index 0000000..74c9855 --- /dev/null +++ b/tim/prune/data/LatLonRectangle.java @@ -0,0 +1,50 @@ +package tim.prune.data; + +/** + * Class to hold a rectangle of latitude/longitude + * with minimum and maximum values for each + */ +public class LatLonRectangle +{ + private DoubleRange _latRange = null; + private DoubleRange _lonRange = null; + + + /** + * Constructor + * @param inLatRange latitude range + * @param inLonRange longitude range + */ + public LatLonRectangle(DoubleRange inLatRange, DoubleRange inLonRange) + { + _latRange = inLatRange; + _lonRange = inLonRange; + // TODO: Expand range by certain percentage + } + + /** + * @return true if the range is empty + */ + public boolean isEmpty() + { + return _latRange == null || _lonRange == null + || !_latRange.hasData() || !_lonRange.hasData(); + } + + /** + * Check if a point is within the rectangle + * @param inPoint point to check + * @return true if point within rectangle + */ + public boolean containsPoint(DataPoint inPoint) + { + if (inPoint != null && !isEmpty()) + { + double pointLat = inPoint.getLatitude().getDouble(); + double pointLon = inPoint.getLongitude().getDouble(); + return (pointLat >= _latRange.getMinimum() && pointLat <= _latRange.getMaximum() + && pointLon >= _lonRange.getMinimum() && pointLon <= _lonRange.getMaximum()); + } + return false; + } +} diff --git a/tim/prune/data/Selection.java b/tim/prune/data/Selection.java index cdbe58e..95e0cfb 100644 --- a/tim/prune/data/Selection.java +++ b/tim/prune/data/Selection.java @@ -17,8 +17,8 @@ public class Selection private IntegerRange _altitudeRange = null; private int _climb = -1, _descent = -1; private int _altitudeFormat = Altitude.FORMAT_NONE; - private long _seconds = 0L; - private double _angDistance = -1.0; //, _averageSpeed = -1.0; + private long _totalSeconds = 0L, _movingSeconds = 0L; + private double _angDistance = -1.0, _angMovingDistance = -1.0; /** @@ -117,8 +117,10 @@ public class Selection _descent = 0; Altitude altitude = null; Timestamp time = null, startTime = null, endTime = null; + Timestamp previousTime = null; DataPoint lastPoint = null, currPoint = null; - _angDistance = 0.0; + _angDistance = 0.0; _angMovingDistance = 0.0; + _totalSeconds = 0L; _movingSeconds = 0L; int altValue = 0; int lastAltValue = 0; boolean foundAlt = false; @@ -147,26 +149,30 @@ public class Selection time = currPoint.getTimestamp(); if (time.isValid()) { - if (startTime == null) startTime = time; - endTime = time; + if (startTime == null || startTime.isAfter(time)) startTime = time; + if (endTime == null || time.isAfter(endTime)) endTime = time; + // add moving time + if (!currPoint.getSegmentStart() && previousTime != null && time.isAfter(previousTime)) { + _movingSeconds += time.getSecondsSince(previousTime); + } + previousTime = time; } // Calculate distances, again ignoring waypoints if (!currPoint.isWaypoint()) { if (lastPoint != null) { - _angDistance += DataPoint.calculateRadiansBetween(lastPoint, currPoint); + double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint); + _angDistance += radians; + if (!currPoint.getSegmentStart()) { + _angMovingDistance += radians; + } } lastPoint = currPoint; } } - if (endTime != null) - { - _seconds = endTime.getSecondsSince(startTime); - } - else - { - _seconds = 0L; + if (endTime != null) { + _totalSeconds = endTime.getSecondsSince(startTime); } } _valid = true; @@ -236,9 +242,17 @@ public class Selection public long getNumSeconds() { if (!_valid) recalculate(); - return _seconds; + return _totalSeconds; } + /** + * @return number of seconds spanned by segments within selection + */ + public long getMovingSeconds() + { + if (!_valid) recalculate(); + return _movingSeconds; + } /** * @param inUnits distance units to use, from class Distance @@ -249,6 +263,14 @@ public class Selection return Distance.convertRadiansToDistance(_angDistance, inUnits); } + /** + * @param inUnits distance units to use, from class Distance + * @return moving distance of Selection in specified units + */ + public double getMovingDistance(int inUnits) + { + return Distance.convertRadiansToDistance(_angMovingDistance, inUnits); + } /** * Clear selected point and range diff --git a/tim/prune/data/Timestamp.java b/tim/prune/data/Timestamp.java index 334bf49..6b7f77b 100644 --- a/tim/prune/data/Timestamp.java +++ b/tim/prune/data/Timestamp.java @@ -166,6 +166,14 @@ public class Timestamp return _valid; } + /** + * @param inOther other Timestamp + * @return true if this one is after the other + */ + public boolean isAfter(Timestamp inOther) + { + return _seconds > inOther._seconds; + } /** * Calculate the difference between two Timestamps in seconds @@ -177,13 +185,22 @@ public class Timestamp return _seconds - inOther._seconds; } + /** + * Add the given number of seconds offset + * @param inOffset number of seconds to add/subtract + */ + public void addOffset(long inOffset) + { + _seconds += inOffset; + _text = null; + } /** * Add the given TimeDifference to this Timestamp * @param inOffset TimeDifference to add * @return new Timestamp object */ - public Timestamp addOffset(TimeDifference inOffset) + public Timestamp createPlusOffset(TimeDifference inOffset) { return new Timestamp((_seconds + inOffset.getTotalSeconds()) * 1000L); } @@ -194,7 +211,7 @@ public class Timestamp * @param inOffset TimeDifference to subtract * @return new Timestamp object */ - public Timestamp subtractOffset(TimeDifference inOffset) + public Timestamp createMinusOffset(TimeDifference inOffset) { return new Timestamp((_seconds - inOffset.getTotalSeconds()) * 1000L); } diff --git a/tim/prune/data/Track.java b/tim/prune/data/Track.java index 9c65437..eeb5b62 100644 --- a/tim/prune/data/Track.java +++ b/tim/prune/data/Track.java @@ -5,6 +5,7 @@ import java.util.List; import tim.prune.UpdateMessageBroker; import tim.prune.edit.FieldEdit; import tim.prune.edit.FieldEditList; +import tim.prune.gui.map.MapUtils; /** @@ -18,6 +19,8 @@ public class Track // Scaled x, y values private double[] _xValues = null; private double[] _yValues = null; + private double[] _xValuesNew = null; + private double[] _yValuesNew = null; private boolean _scaled = false; private int _numPoints = 0; private boolean _mixedData = false; @@ -137,6 +140,7 @@ public class Track // (maybe should be separate thread?) // (maybe should be in separate class?) // (maybe should be based on subtended angles instead of distances?) + // Suggestion: Find last track point, don't delete it (or maybe preserve first and last of each segment?) if (inResolution <= 0) return 0; int numCopied = 0; @@ -363,6 +367,35 @@ public class Track } + /** + * Add the given time offset to the specified range + * @param inStart start of range + * @param inEnd end of range + * @param inOffset offset to add (-ve to subtract) + * @return true on success + */ + public boolean addTimeOffset(int inStart, int inEnd, long inOffset) + { + // sanity check + if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) { + return false; + } + boolean foundTimestamp = false; + // Loop over all points within range + for (int i=inStart; i<=inEnd; i++) + { + Timestamp timestamp = _dataPoints[i].getTimestamp(); + if (timestamp != null) + { + // This point has a timestamp so add the offset to it + foundTimestamp = true; + timestamp.addOffset(inOffset); + } + } + return foundTimestamp; + } + + /** * Merge the track segments within the given range * @param inStart start index @@ -501,6 +534,61 @@ public class Track } + /** + * Cut and move the specified section + * @param inSectionStart start index of section + * @param inSectionEnd end index of section + * @param inMoveTo index of move to point + * @return true if move successful + */ + public boolean cutAndMoveSection(int inSectionStart, int inSectionEnd, int inMoveTo) + { + // Check that indices make sense + if (inSectionStart > 0 && inSectionEnd > inSectionStart && inMoveTo > 0 + && (inMoveTo < inSectionStart || inMoveTo > (inSectionEnd+1))) + { + // do the cut and move + DataPoint[] newPointArray = new DataPoint[_numPoints]; + // System.out.println("Cut/move section (" + inSectionStart + " - " + inSectionEnd + ") to before point " + inMoveTo); + // Is it a forward copy or a backward copy? + if (inSectionStart > inMoveTo) + { + int sectionLength = inSectionEnd - inSectionStart + 1; + // move section to earlier point + if (inMoveTo > 0) + System.arraycopy(_dataPoints, 0, newPointArray, 0, inMoveTo); // unchanged points before + System.arraycopy(_dataPoints, inSectionStart, newPointArray, inMoveTo, sectionLength); // moved bit + // after insertion point, before moved bit + if (inSectionStart > (inMoveTo + 1)) + System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo + sectionLength, inSectionStart - inMoveTo); + // after moved bit + if (inSectionEnd < (_numPoints - 1)) + System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionEnd+1, _numPoints - inSectionEnd - 1); + } + else + { + // Move section to later point + if (inSectionStart > 0) + System.arraycopy(_dataPoints, 0, newPointArray, 0, inSectionStart); // unchanged points before + // from end of section to move to point + if (inMoveTo > (inSectionEnd + 1)) + System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionStart, inMoveTo - inSectionEnd - 1); + // moved bit + System.arraycopy(_dataPoints, inSectionStart, newPointArray, inSectionStart + inMoveTo - inSectionEnd - 1, + inSectionEnd - inSectionStart + 1); + // unchanged bit after + if (inSectionEnd < (_numPoints - 1)) + System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo, _numPoints - inMoveTo); + } + // Copy array references + _dataPoints = newPointArray; + _scaled = false; + return true; + } + return false; + } + + /** * Interpolate extra points between two selected ones * @param inStartIndex start index of interpolation @@ -631,6 +719,26 @@ public class Track return _yValues[inPointNum]; } + /** + * @param inPointNum point index, starting at 0 + * @return scaled x value of specified point + */ + public double getXNew(int inPointNum) + { + if (!_scaled) scalePoints(); + return _xValuesNew[inPointNum]; + } + + /** + * @param inPointNum point index, starting at 0 + * @return scaled y value of specified point + */ + public double getYNew(int inPointNum) + { + if (!_scaled) scalePoints(); + return _yValuesNew[inPointNum]; + } + /** * @return the master field list */ @@ -765,6 +873,8 @@ public class Track // Loop over points and calculate scales _xValues = new double[getNumPoints()]; _yValues = new double[getNumPoints()]; + _xValuesNew = new double[getNumPoints()]; + _yValuesNew = new double[getNumPoints()]; _xRange = new DoubleRange(); _yRange = new DoubleRange(); for (p=0; p < getNumPoints(); p++) @@ -774,8 +884,10 @@ public class Track { _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor; _xRange.addValue(_xValues[p]); + _xValuesNew[p] = MapUtils.getXFromLongitude(point.getLongitude().getDouble()); _yValues[p] = (point.getLatitude().getDouble() - latMedian); _yRange.addValue(_yValues[p]); + _yValuesNew[p] = MapUtils.getYFromLatitude(point.getLatitude().getDouble()); } } _scaled = true; @@ -817,6 +929,40 @@ public class Track } + /** + * Find the nearest point to the specified x and y coordinates + * or -1 if no point is within the specified max distance + * @param inX x coordinate + * @param inY y coordinate + * @param inMaxDist maximum distance from selected coordinates + * @param inJustTrackPoints true if waypoints should be ignored + * @return index of nearest point or -1 if not found + */ + public int getNearestPointIndexNew(double inX, double inY, double inMaxDist, boolean inJustTrackPoints) + { + int nearestPoint = 0; + double nearestDist = -1.0; + double currDist; + for (int i=0; i < getNumPoints(); i++) + { + if (!inJustTrackPoints || !_dataPoints[i].isWaypoint()) + { + currDist = Math.abs(_xValuesNew[i] - inX) + Math.abs(_yValuesNew[i] - inY); + if (currDist < nearestDist || nearestDist < 0.0) + { + nearestPoint = i; + nearestDist = currDist; + } + } + } + // Check whether it's within required distance + if (nearestDist > inMaxDist && inMaxDist > 0.0) + { + return -1; + } + return nearestPoint; + } + /** * Get the next track point starting from the given index * @param inStartIndex index to start looking from @@ -824,7 +970,18 @@ public class Track */ public DataPoint getNextTrackPoint(int inStartIndex) { - return getNextTrackPoint(inStartIndex, 1); + return getNextTrackPoint(inStartIndex, _numPoints, true); + } + + /** + * Get the next track point in the given range + * @param inStartIndex index to start looking from + * @param inEndIndex index to stop looking + * @return next track point, or null if end of data reached + */ + public DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex) + { + return getNextTrackPoint(inStartIndex, inEndIndex, true); } /** @@ -834,19 +991,21 @@ public class Track */ public DataPoint getPreviousTrackPoint(int inStartIndex) { - return getNextTrackPoint(inStartIndex, -1); + return getNextTrackPoint(inStartIndex, _numPoints, false); } /** * Get the next track point starting from the given index * @param inStartIndex index to start looking from - * @param inIncrement increment to add to point index, +1 for next, -1 for previous + * @param inEndIndex index to stop looking (inclusive) + * @param inCountUp true for next, false for previous * @return next track point, or null if end of data reached */ - private DataPoint getNextTrackPoint(int inStartIndex, int inIncrement) + private DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex, boolean inCountUp) { // Loop forever over points - for (int i=inStartIndex; ; i+=inIncrement) + int increment = inCountUp?1:-1; + for (int i=inStartIndex; i<=inEndIndex; i+=increment) { DataPoint point = getPoint(i); // Exit if end of data reached - there wasn't a track point @@ -856,6 +1015,7 @@ public class Track return point; } } + return null; } /** diff --git a/tim/prune/data/TrackInfo.java b/tim/prune/data/TrackInfo.java index bbbdefb..4f3ce27 100644 --- a/tim/prune/data/TrackInfo.java +++ b/tim/prune/data/TrackInfo.java @@ -282,15 +282,15 @@ public class TrackInfo /** * Interpolate extra points between two selected ones - * @param inStartIndex start index of interpolation * @param inNumPoints num points to insert * @return true if successful */ public boolean interpolate(int inNumPoints) { boolean success = _track.interpolate(_selection.getStart(), inNumPoints); - if (success) + if (success) { _selection.selectRangeEnd(_selection.getEnd() + inNumPoints); + } return success; } diff --git a/tim/prune/gui/AboutScreen.java b/tim/prune/gui/AboutScreen.java index 02c37f7..0d1ef5b 100644 --- a/tim/prune/gui/AboutScreen.java +++ b/tim/prune/gui/AboutScreen.java @@ -77,6 +77,8 @@ public class AboutScreen extends JDialog descBuffer.append("

").append(I18nManager.getText("dialog.about.summarytext1")).append("

"); descBuffer.append("

").append(I18nManager.getText("dialog.about.summarytext2")).append("

"); descBuffer.append("

").append(I18nManager.getText("dialog.about.summarytext3")).append("

"); + descBuffer.append("

").append(I18nManager.getText("dialog.about.languages")).append(" : ") + .append("deutsch, english, español, français, italiano, polski, schwiizerdüütsch").append("

"); descBuffer.append("

").append(I18nManager.getText("dialog.about.translatedby")).append("

"); JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString()); descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); @@ -161,7 +163,7 @@ public class AboutScreen extends JDialog new JLabel(I18nManager.getText("dialog.about.credits.translators") + " : "), 0, 3); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel("Ramon, Miguel, Inés, Piotr, Petrovsk"), + new JLabel("Ramon, Miguel, Inés, Piotr, Petrovsk, Josatoc"), 1, 3); addToGridBagPanel(creditsPanel, gridBag, constraints, new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "), diff --git a/tim/prune/gui/CheckVersionScreen.java b/tim/prune/gui/CheckVersionScreen.java new file mode 100644 index 0000000..e57b724 --- /dev/null +++ b/tim/prune/gui/CheckVersionScreen.java @@ -0,0 +1,97 @@ +package tim.prune.gui; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Properties; +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +import tim.prune.GpsPruner; +import tim.prune.I18nManager; +import tim.prune.browser.BrowserLauncher; + +/** + * Class to check the version of Prune + * and show an appropriate dialog + */ +public abstract class CheckVersionScreen +{ + /** + * Show the check version dialog + * @param inParent parent frame + */ + public static void show(JFrame inParent) + { + final String filePathStart = "http://activityworkshop.net/software/prune/prune_versioncheck_"; + final String filePathEnd = ".txt"; + String latestVer = null; + String nextVersion = null; + String releaseDate = null; + Properties props = new Properties(); + try + { + // Load properties from the url on the server + InputStream inStream = new URL(filePathStart + GpsPruner.VERSION_NUMBER + filePathEnd).openStream(); + props.load(inStream); + + // Extract the three fields we want, ignore others + latestVer = props.getProperty("prune.latestversion"); + nextVersion = props.getProperty("prune.nextversion"); + releaseDate = props.getProperty("prune.releasedate"); + } + catch (IOException ioe) { + System.err.println(ioe.getClass().getName() + " - " + ioe.getMessage()); + } + + if (latestVer == null) { + // Couldn't get version number, show error message + JOptionPane.showMessageDialog(inParent, I18nManager.getText("dialog.checkversion.error"), + I18nManager.getText("dialog.checkversion.title"), JOptionPane.ERROR_MESSAGE); + } + else if (latestVer.equals(GpsPruner.VERSION_NUMBER)) + { + // Version on the server is the same as this one + String displayMessage = I18nManager.getText("dialog.checkversion.uptodate"); + if (nextVersion != null && !nextVersion.equals("")) + { + displayMessage += "\n\n" + nextVersion; + } + // Show information message that the current version is already running + JOptionPane.showMessageDialog(inParent, displayMessage, + I18nManager.getText("dialog.checkversion.title"), JOptionPane.INFORMATION_MESSAGE); + } + else + { + // A new version is available! + String displayMessage = I18nManager.getText("dialog.checkversion.newversion1") + " " + latestVer + + " " + I18nManager.getText("dialog.checkversion.newversion2"); + try + { + if (releaseDate != null && !releaseDate.equals("")) { + displayMessage += "\n\n" + I18nManager.getText("dialog.checkversion.releasedate1") + " " + + DateFormat.getDateInstance(DateFormat.LONG).format(new SimpleDateFormat("y-M-d").parse(releaseDate)) + + " " + I18nManager.getText("dialog.checkversion.releasedate2"); + } + } + catch (ParseException pe) { + System.err.println("Oops, couldn't parse date: '" + releaseDate + "'"); + } + displayMessage += "\n\n" + I18nManager.getText("dialog.checkversion.download"); + + // Show information message to download the new version + Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")}; + if (JOptionPane.showOptionDialog(inParent, displayMessage, + I18nManager.getText("dialog.checkversion.title"), JOptionPane.YES_NO_OPTION, + JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1]) + == JOptionPane.YES_OPTION) + { + // User selected to launch home page + new BrowserLauncher().launchBrowser("http://activityworkshop.net/software/prune/download.html"); + } + } + } +} diff --git a/tim/prune/gui/DetailsDisplay.java b/tim/prune/gui/DetailsDisplay.java index 7773062..e153a6c 100644 --- a/tim/prune/gui/DetailsDisplay.java +++ b/tim/prune/gui/DetailsDisplay.java @@ -15,6 +15,8 @@ import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.EtchedBorder; + +import tim.prune.Config; import tim.prune.DataSubscriber; import tim.prune.I18nManager; import tim.prune.data.Altitude; @@ -41,13 +43,15 @@ public class DetailsDisplay extends GenericDisplay // Range details private JLabel _rangeLabel = null; - private JLabel _distanceLabel = null, _durationLabel = null; + private JLabel _distanceLabel = null, _movingDistanceLabel = null; + private JLabel _durationLabel = null; private JLabel _altRangeLabel = null, _updownLabel = null; - private JLabel _aveSpeedLabel = null; + private JLabel _aveSpeedLabel = null, _aveMovingSpeedLabel = null; // Photo details private JLabel _photoLabel = null; private PhotoThumbnail _photoThumbnail = null; + private JLabel _photoTimestampLabel = null; private JLabel _photoConnectedLabel = null; // Units @@ -57,15 +61,16 @@ public class DetailsDisplay extends GenericDisplay private NumberFormat _distanceFormatter = NumberFormat.getInstance(); // Cached labels - private static final String LABEL_POINT_SELECTED1 = I18nManager.getText("details.index.selected") + ": "; + private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": "; private static final String LABEL_POINT_LATITUDE = I18nManager.getText("fieldname.latitude") + ": "; private static final String LABEL_POINT_LONGITUDE = I18nManager.getText("fieldname.longitude") + ": "; private static final String LABEL_POINT_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": "; private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": "; private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": "; - private static final String LABEL_RANGE_SELECTED1 = I18nManager.getText("details.range.selected") + ": "; + private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": "; private static final String LABEL_RANGE_DURATION = I18nManager.getText("fieldname.duration") + ": "; private static final String LABEL_RANGE_DISTANCE = I18nManager.getText("fieldname.distance") + ": "; + private static final String LABEL_RANGE_MOVINGDISTANCE = I18nManager.getText("fieldname.movingdistance") + ": "; private static final String LABEL_RANGE_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": "; private static final String LABEL_RANGE_CLIMB = I18nManager.getText("details.range.climb") + ": "; private static final String LABEL_RANGE_DESCENT = ", " + I18nManager.getText("details.range.descent") + ": "; @@ -126,10 +131,14 @@ public class DetailsDisplay extends GenericDisplay rangeDetailsPanel.add(_rangeLabel); _distanceLabel = new JLabel(""); rangeDetailsPanel.add(_distanceLabel); + _movingDistanceLabel = new JLabel(""); + rangeDetailsPanel.add(_movingDistanceLabel); _durationLabel = new JLabel(""); rangeDetailsPanel.add(_durationLabel); _aveSpeedLabel = new JLabel(""); rangeDetailsPanel.add(_aveSpeedLabel); + _aveMovingSpeedLabel = new JLabel(""); + rangeDetailsPanel.add(_aveMovingSpeedLabel); _altRangeLabel = new JLabel(""); rangeDetailsPanel.add(_altRangeLabel); _updownLabel = new JLabel(""); @@ -147,6 +156,8 @@ public class DetailsDisplay extends GenericDisplay photoDetailsPanel.add(photoDetailsLabel); _photoLabel = new JLabel(I18nManager.getText("details.nophoto")); photoDetailsPanel.add(_photoLabel); + _photoTimestampLabel = new JLabel(""); + photoDetailsPanel.add(_photoTimestampLabel); _photoConnectedLabel = new JLabel(""); photoDetailsPanel.add(_photoConnectedLabel); _photoThumbnail = new PhotoThumbnail(); @@ -186,6 +197,7 @@ public class DetailsDisplay extends GenericDisplay lowerPanel.add(unitsLabel); String[] distUnits = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")}; _distUnitsDropdown = new JComboBox(distUnits); + if (!Config.getUseMetricUnits()) {_distUnitsDropdown.setSelectedIndex(1);} _distUnitsDropdown.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -223,24 +235,24 @@ public class DetailsDisplay extends GenericDisplay } else { - _indexLabel.setText(LABEL_POINT_SELECTED1 + _indexLabel.setText(LABEL_POINT_SELECTED + (currentPointIndex+1) + " " + I18nManager.getText("details.index.of") + " " + _track.getNumPoints()); _latLabel.setText(makeCoordinateLabel(LABEL_POINT_LATITUDE, currentPoint.getLatitude(), _coordFormatDropdown.getSelectedIndex())); _longLabel.setText(makeCoordinateLabel(LABEL_POINT_LONGITUDE, currentPoint.getLongitude(), _coordFormatDropdown.getSelectedIndex())); - _altLabel.setText(LABEL_POINT_ALTITUDE - + (currentPoint.hasAltitude()? - (currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat())): - "")); + _altLabel.setText(currentPoint.hasAltitude()? + (LABEL_POINT_ALTITUDE + currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat())) + :""); if (currentPoint.getTimestamp().isValid()) { if (currentPointIndex > 0 && currentPointIndex < (_trackInfo.getTrack().getNumPoints()-1)) { DataPoint prevPoint = _trackInfo.getTrack().getPoint(currentPointIndex - 1); DataPoint nextPoint = _trackInfo.getTrack().getPoint(currentPointIndex + 1); - if (prevPoint.getTimestamp().isValid() && nextPoint.getTimestamp().isValid()) { + if (prevPoint.getTimestamp().isValid() && nextPoint.getTimestamp().isValid()) + { // use total distance and total time between neighbouring points long diff = nextPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()); - if (diff < 100) { + if (diff < 1000) { double rads = DataPoint.calculateRadiansBetween(prevPoint, currentPoint) + DataPoint.calculateRadiansBetween(currentPoint, nextPoint); double dist = Distance.convertRadiansToDistance(rads, distUnits); @@ -267,24 +279,32 @@ public class DetailsDisplay extends GenericDisplay { _rangeLabel.setText(I18nManager.getText("details.norangeselection")); _distanceLabel.setText(""); + _movingDistanceLabel.setText(""); _durationLabel.setText(""); _altRangeLabel.setText(""); _updownLabel.setText(""); + _aveSpeedLabel.setText(""); + _aveMovingSpeedLabel.setText(""); } else { - _rangeLabel.setText(LABEL_RANGE_SELECTED1 + _rangeLabel.setText(LABEL_RANGE_SELECTED + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to") + " " + (selection.getEnd()+1)); _distanceLabel.setText(LABEL_RANGE_DISTANCE + roundedNumber(selection.getDistance(distUnits)) + " " + distUnitsStr); - if (selection.getNumSeconds() > 0) { + _movingDistanceLabel.setText(LABEL_RANGE_MOVINGDISTANCE + roundedNumber(selection.getMovingDistance(distUnits)) + " " + distUnitsStr); + if (selection.getNumSeconds() > 0) + { _durationLabel.setText(LABEL_RANGE_DURATION + buildDurationString(selection.getNumSeconds())); _aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": " + roundedNumber(selection.getDistance(distUnits)/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr); + _aveMovingSpeedLabel.setText(I18nManager.getText("details.range.avemovingspeed") + ": " + + roundedNumber(selection.getMovingDistance(distUnits)/selection.getMovingSeconds()*3600.0) + " " + speedUnitsStr); } else { _durationLabel.setText(""); _aveSpeedLabel.setText(""); + _aveMovingSpeedLabel.setText(""); } String altUnitsLabel = getAltitudeUnitsLabel(selection.getAltitudeFormat()); IntegerRange altRange = selection.getAltitudeRange(); @@ -309,6 +329,7 @@ public class DetailsDisplay extends GenericDisplay { // no photo, hide details _photoLabel.setText(I18nManager.getText("details.nophoto")); + _photoTimestampLabel.setText(""); _photoConnectedLabel.setText(""); _photoThumbnail.setVisible(false); } @@ -316,6 +337,7 @@ public class DetailsDisplay extends GenericDisplay { if (currentPhoto == null) {currentPhoto = currentPoint.getPhoto();} _photoLabel.setText(I18nManager.getText("details.photofile") + ": " + currentPhoto.getFile().getName()); + _photoLabel.setText(LABEL_POINT_TIMESTAMP + currentPhoto.getTimestamp().getText()); _photoConnectedLabel.setText(I18nManager.getText("details.photo.connected") + ": " + (currentPhoto.getCurrentStatus() == PhotoStatus.NOT_CONNECTED ? I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes"))); @@ -380,7 +402,7 @@ public class DetailsDisplay extends GenericDisplay if (inNumSecs < 86400L) return "" + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours") + " " + ((inNumSecs / 60) % 60) + I18nManager.getText("display.range.time.mins"); if (inNumSecs < 432000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days") - + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours"); + + " " + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours"); if (inNumSecs < 8640000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days"); return "big"; } diff --git a/tim/prune/gui/GenericChart.java b/tim/prune/gui/GenericChart.java index 916c738..71455eb 100644 --- a/tim/prune/gui/GenericChart.java +++ b/tim/prune/gui/GenericChart.java @@ -19,7 +19,7 @@ public abstract class GenericChart extends GenericDisplay implements MouseListen protected static final int BORDER_WIDTH = 8; // Colours - private static final Color COLOR_BORDER_BG = Color.GRAY; + private static final Color COLOR_BORDER_BG = Color.WHITE; private static final Color COLOR_CHART_BG = Color.WHITE; private static final Color COLOR_CHART_LINE = Color.BLACK; private static final Color COLOR_NODATA_TEXT = Color.GRAY; diff --git a/tim/prune/gui/IconManager.java b/tim/prune/gui/IconManager.java new file mode 100644 index 0000000..8930d68 --- /dev/null +++ b/tim/prune/gui/IconManager.java @@ -0,0 +1,65 @@ +package tim.prune.gui; + +import javax.swing.ImageIcon; + +/** + * Class to manage the loading of icons + * for toolbars and map buttons + */ +public abstract class IconManager +{ + + /** Icon for window */ + public static final String WINDOW_ICON = "window_icon.png"; + + /** Icon for map button on main map display */ + public static final String MAP_BUTTON = "map_icon.gif"; + /** Icon for map button on main map display when selected */ + public static final String MAP_BUTTON_ON = "map_icon_on.gif"; + /** Icon for autopan button on main map display */ + public static final String AUTOPAN_BUTTON = "autopan.gif"; + /** Icon for autopan button on main map display when selected */ + public static final String AUTOPAN_BUTTON_ON = "autopan_on.gif"; + /** Icon for points connected icon on main map display */ + public static final String POINTS_CONNECTED_BUTTON = "points_connected.gif"; + /** Icon for points disconnected icon on main map display */ + public static final String POINTS_DISCONNECTED_BUTTON = "points_disconnected.gif"; + /** Icon for zoom in button on main map display */ + public static final String ZOOM_IN_BUTTON = "zoom_in.gif"; + /** Icon for zoom out button on main map display */ + public static final String ZOOM_OUT_BUTTON = "zoom_out.gif"; + + /** Icon for open file */ + public static final String OPEN_FILE = "add_textfile_icon.png"; + /** Icon for add photo */ + public static final String ADD_PHOTO = "add_photo_icon.png"; + /** Icon for save */ + public static final String SAVE_FILE = "save_icon.gif"; + /** Icon for undo */ + public static final String UNDO = "undo_icon.gif"; + /** Icon for edit point */ + public static final String EDIT_POINT = "edit_point_icon.gif"; + /** Icon for delete point */ + public static final String DELETE_POINT = "delete_point_icon.gif"; + /** Icon for delete range */ + public static final String DELETE_RANGE = "delete_range_icon.gif"; + /** Icon for set range start */ + public static final String SET_RANGE_START = "set_start_icon.png"; + /** Icon for set range end */ + public static final String SET_RANGE_END = "set_end_icon.png"; + /** Icon for connect point to photo */ + public static final String CONNECT_PHOTO = "link.gif"; + /** Icon for cut range and move */ + public static final String CUT_AND_MOVE = "cut_and_move.gif"; + + + /** + * Get the specified image + * @param inFilename filename of image (using constants) + * @return ImageIcon object containing image + */ + public static ImageIcon getImageIcon(String inFilename) + { + return new ImageIcon(IconManager.class.getResource("images/" + inFilename)); + } +} diff --git a/tim/prune/gui/MapChart.java b/tim/prune/gui/MapChart.java deleted file mode 100644 index 37f4366..0000000 --- a/tim/prune/gui/MapChart.java +++ /dev/null @@ -1,636 +0,0 @@ -package tim.prune.gui; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.awt.image.BufferedImage; - -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; - -import tim.prune.App; -import tim.prune.DataSubscriber; -import tim.prune.I18nManager; -import tim.prune.data.DataPoint; -import tim.prune.data.TrackInfo; - - -/** - * Display component for the main map - */ -public class MapChart extends GenericChart implements MouseWheelListener, KeyListener, MouseMotionListener -{ - // Constants - private static final int POINT_RADIUS = 4; - private static final int CLICK_SENSITIVITY = 10; - private static final double ZOOM_SCALE_FACTOR = 1.4; - private static final int PAN_DISTANCE = 10; - private static final int LIMIT_WAYPOINT_NAMES = 40; - - // Colours - private static final Color COLOR_BG = Color.WHITE; - private static final Color COLOR_POINT = Color.BLUE; - private static final Color COLOR_CURR_RANGE = Color.GREEN; - private static final Color COLOR_CROSSHAIRS = Color.RED; - private static final Color COLOR_WAYPT_NAME = Color.BLACK; - - // Instance variables - private App _app = null; - private BufferedImage _image = null; - private JPopupMenu _popup = null; - private JCheckBoxMenuItem _autoPanMenuItem = null; - private JCheckBoxMenuItem _connectPointsMenuItem = null; - private int _numPoints = -1; - private double _scale; - private double _offsetX, _offsetY, _zoomScale; - private int _lastSelectedPoint = -1; - private int _dragStartX = -1, _dragStartY = -1; - private int _zoomDragFromX = -1, _zoomDragFromY = -1; - private int _zoomDragToX = -1, _zoomDragToY = -1; - private boolean _zoomDragging = false; - - - /** - * Constructor - * @param inApp App object for callbacks - * @param inTrackInfo track info object - */ - public MapChart(App inApp, TrackInfo inTrackInfo) - { - super(inTrackInfo); - _app = inApp; - makePopup(); - addMouseListener(this); - addMouseWheelListener(this); - addMouseMotionListener(this); - setFocusable(true); - addKeyListener(this); - MINIMUM_SIZE = new Dimension(200, 250); - _zoomScale = 1.0; - } - - - /** - * Override track updating to refresh image - */ - public void dataUpdated(byte inUpdateType) - { - // Check if number of points has changed or data has been edited - if (_track.getNumPoints() != _numPoints || (inUpdateType & DATA_EDITED) > 0) - { - _image = null; - _lastSelectedPoint = -1; - _numPoints = _track.getNumPoints(); - } - super.dataUpdated(inUpdateType); - } - - - /** - * Override paint method to draw map - * @param inG graphics object - */ - public void paint(Graphics inG) - { - if (_track == null) - { - super.paint(inG); - return; - } - - int width = getWidth(); - int height = getHeight(); - int x, y; - - // Find x and y ranges, and scale to fit - double scaleX = (_track.getXRange().getMaximum() - _track.getXRange().getMinimum()) - / (width - 2 * (BORDER_WIDTH + POINT_RADIUS)); - double scaleY = (_track.getYRange().getMaximum() - _track.getYRange().getMinimum()) - / (height - 2 * (BORDER_WIDTH + POINT_RADIUS)); - _scale = scaleX; - if (scaleY > _scale) _scale = scaleY; - - // Autopan if necessary - int selectedPoint = _trackInfo.getSelection().getCurrentPointIndex(); - if (_autoPanMenuItem.isSelected() && selectedPoint >= 0 && selectedPoint != _lastSelectedPoint) - { - // Autopan is enabled and a point is selected - work out x and y to see if it's within range - x = width/2 + (int) ((_track.getX(selectedPoint) - _offsetX) / _scale * _zoomScale); - y = height/2 - (int) ((_track.getY(selectedPoint) - _offsetY) / _scale * _zoomScale); - if (x <= BORDER_WIDTH) - { - // autopan left - _offsetX -= (width / 4 - x) * _scale / _zoomScale; - _image = null; - } - else if (x >= (width - BORDER_WIDTH)) - { - // autopan right - _offsetX += (x - width * 3/4) * _scale / _zoomScale; - _image = null; - } - if (y <= BORDER_WIDTH) - { - // autopan up - _offsetY += (height / 4 - y) * _scale / _zoomScale; - _image = null; - } - else if (y >= (height - BORDER_WIDTH)) - { - // autopan down - _offsetY -= (y - height * 3/4) * _scale / _zoomScale; - _image = null; - } - } - _lastSelectedPoint = selectedPoint; - - // Create background if necessary - if (_image == null || width != _image.getWidth() || height != _image.getHeight()) - { - createBackgroundImage(); - } - // return if image has been set to null by other thread - if (_image == null) {return;} - - // draw buffered image onto g - inG.drawImage(_image, 0, 0, width, height, COLOR_BG, null); - - // draw selected range, if any - if (_trackInfo.getSelection().hasRangeSelected() && !_zoomDragging) - { - int rangeStart = _trackInfo.getSelection().getStart(); - int rangeEnd = _trackInfo.getSelection().getEnd(); - inG.setColor(COLOR_CURR_RANGE); - for (int i=rangeStart; i<=rangeEnd; i++) - { - x = width/2 + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale); - y = height/2 - (int) ((_track.getY(i) - _offsetY) / _scale * _zoomScale); - if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH) - && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH) - { - inG.drawRect(x - 2, y - 2, 4, 4); - } - } - } - - // Highlight selected point - if (selectedPoint >= 0 && !_zoomDragging) - { - inG.setColor(COLOR_CROSSHAIRS); - x = width/2 + (int) ((_track.getX(selectedPoint) - _offsetX) / _scale * _zoomScale); - y = height/2 - (int) ((_track.getY(selectedPoint) - _offsetY) / _scale * _zoomScale); - if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH) - && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH) - { - // Draw cross-hairs for current point - inG.drawLine(x, BORDER_WIDTH, x, height - BORDER_WIDTH); - inG.drawLine(BORDER_WIDTH, y, width - BORDER_WIDTH, y); - - // Show selected point afterwards to make sure it's on top - inG.drawOval(x - 2, y - 2, 4, 4); - inG.drawOval(x - 3, y - 3, 6, 6); - } - } - - // Draw rectangle for dragging zoom area - if (_zoomDragging) - { - inG.setColor(COLOR_CROSSHAIRS); - inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragFromX, _zoomDragToY); - inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragToX, _zoomDragFromY); - inG.drawLine(_zoomDragToX, _zoomDragFromY, _zoomDragToX, _zoomDragToY); - inG.drawLine(_zoomDragFromX, _zoomDragToY, _zoomDragToX, _zoomDragToY); - } - - // Attempt to grab keyboard focus if possible - //requestFocus(); (causes problems here) - } - - - /** - * Plot the points onto an offscreen image - * which doesn't have to be redrawn when the selection changes - */ - private void createBackgroundImage() - { - int width = getWidth(); - int height = getHeight(); - int x, y; - int lastX = 0, lastY = 0; - // Initialise image - if (_image == null || _image.getWidth() != width || _image.getHeight() != height) { - _image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - } - Graphics bufferedG = _image.getGraphics(); - super.paint(bufferedG); - - // Loop and show all points - int numPoints = _track.getNumPoints(); - bufferedG.setColor(COLOR_POINT); - int halfWidth = width/2; - int halfHeight = height/2; - boolean currPointTrackpoint = false, lastPointTrackpoint = false; - for (int i=0; i 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= 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 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 - } -} diff --git a/tim/prune/gui/MenuManager.java b/tim/prune/gui/MenuManager.java index 4b75268..e1c8cf0 100644 --- a/tim/prune/gui/MenuManager.java +++ b/tim/prune/gui/MenuManager.java @@ -4,8 +4,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; - -import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; @@ -54,10 +52,11 @@ public class MenuManager implements DataSubscriber private JMenuItem _selectStartItem = null; private JMenuItem _selectEndItem = null; private JMenuItem _reverseItem = null; + private JMenuItem _addTimeOffsetItem = null; private JMenuItem _mergeSegmentsItem = null; private JMenu _rearrangeMenu = null; + private JMenuItem _cutAndMoveItem = null; private JMenuItem _show3dItem = null; - private JMenuItem _showOsmMapItem = null; private JMenu _browserMapMenu = null; private JMenuItem _saveExifItem = null; private JMenuItem _connectPhotoItem = null; @@ -71,6 +70,8 @@ public class MenuManager implements DataSubscriber private ActionListener _saveAction = null; private ActionListener _undoAction = null; private ActionListener _editPointAction = null; + private ActionListener _deletePointAction = null; + private ActionListener _deleteRangeAction = null; private ActionListener _selectStartAction = null; private ActionListener _selectEndAction = null; private ActionListener _connectPhotoAction = null; @@ -79,6 +80,8 @@ public class MenuManager implements DataSubscriber private JButton _saveButton = null; private JButton _undoButton = null; private JButton _editPointButton = null; + private JButton _deletePointButton = null; + private JButton _deleteRangeButton = null; private JButton _selectStartButton = null; private JButton _selectEndButton = null; private JButton _connectPhotoButton = null; @@ -129,6 +132,16 @@ public class MenuManager implements DataSubscriber }; addPhotosMenuItem.addActionListener(_addPhotoAction); fileMenu.add(addPhotosMenuItem); + // Add photos + JMenuItem loadFromGpsMenuItem = new JMenuItem(I18nManager.getText("menu.file.loadfromgps")); + loadFromGpsMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.beginLoadFromGps(); + } + }); + fileMenu.add(loadFromGpsMenuItem); + fileMenu.addSeparator(); // Save _saveItem = new JMenuItem(I18nManager.getText("menu.file.save"), KeyEvent.VK_S); _saveAction = new ActionListener() { @@ -180,6 +193,7 @@ public class MenuManager implements DataSubscriber }); fileMenu.add(exitMenuItem); menubar.add(fileMenu); + // Edit menu JMenu editMenu = new JMenu(I18nManager.getText("menu.edit")); editMenu.setMnemonic(KeyEvent.VK_E); _undoItem = new JMenuItem(I18nManager.getText("menu.edit.undo")); @@ -222,21 +236,23 @@ public class MenuManager implements DataSubscriber _editWaypointNameItem.setEnabled(false); editMenu.add(_editWaypointNameItem); _deletePointItem = new JMenuItem(I18nManager.getText("menu.edit.deletepoint")); - _deletePointItem.addActionListener(new ActionListener() { + _deletePointAction = new ActionListener() { public void actionPerformed(ActionEvent e) { _app.deleteCurrentPoint(); } - }); + }; + _deletePointItem.addActionListener(_deletePointAction); _deletePointItem.setEnabled(false); editMenu.add(_deletePointItem); _deleteRangeItem = new JMenuItem(I18nManager.getText("menu.edit.deleterange")); - _deleteRangeItem.addActionListener(new ActionListener() { + _deleteRangeAction = new ActionListener() { public void actionPerformed(ActionEvent e) { _app.deleteSelectedRange(); } - }); + }; + _deleteRangeItem.addActionListener(_deleteRangeAction); _deleteRangeItem.setEnabled(false); editMenu.add(_deleteRangeItem); _deleteDuplicatesItem = new JMenuItem(I18nManager.getText("menu.edit.deleteduplicates")); @@ -276,6 +292,15 @@ public class MenuManager implements DataSubscriber }); _reverseItem.setEnabled(false); editMenu.add(_reverseItem); + _addTimeOffsetItem = new JMenuItem(I18nManager.getText("menu.edit.addtimeoffset")); + _addTimeOffsetItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.beginAddTimeOffset(); + } + }); + _addTimeOffsetItem.setEnabled(false); + editMenu.add(_addTimeOffsetItem); _mergeSegmentsItem = new JMenuItem(I18nManager.getText("menu.edit.mergetracksegments")); _mergeSegmentsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -316,6 +341,15 @@ public class MenuManager implements DataSubscriber rearrangeNearestItem.setEnabled(true); _rearrangeMenu.add(rearrangeNearestItem); editMenu.add(_rearrangeMenu); + _cutAndMoveItem = new JMenuItem(I18nManager.getText("menu.edit.cutandmove")); + _cutAndMoveItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.cutAndMoveSelection(); + } + }); + _cutAndMoveItem.setEnabled(false); + editMenu.add(_cutAndMoveItem); menubar.add(editMenu); // Select menu @@ -372,16 +406,6 @@ public class MenuManager implements DataSubscriber }); _show3dItem.setEnabled(false); viewMenu.add(_show3dItem); - // Show OSM map - _showOsmMapItem = new JMenuItem(I18nManager.getText("menu.view.showmap")); - _showOsmMapItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - _app.showOsmMap(); - } - }); - _showOsmMapItem.setEnabled(false); - viewMenu.add(_showOsmMapItem); // browser submenu _browserMapMenu = new JMenu(I18nManager.getText("menu.view.browser")); _browserMapMenu.setEnabled(false); @@ -479,6 +503,14 @@ public class MenuManager implements DataSubscriber } }); helpMenu.add(aboutItem); + JMenuItem checkVersionItem = new JMenuItem(I18nManager.getText("menu.help.checkversion")); + checkVersionItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + CheckVersionScreen.show(_parent); + } + }); + helpMenu.add(checkVersionItem); menubar.add(helpMenu); return menubar; @@ -493,45 +525,57 @@ public class MenuManager implements DataSubscriber { JToolBar toolbar = new JToolBar(); // Add text file - JButton openFileButton = new JButton(new ImageIcon(getClass().getResource("images/add_textfile_icon.png"))); + JButton openFileButton = new JButton(IconManager.getImageIcon(IconManager.OPEN_FILE)); openFileButton.setToolTipText(I18nManager.getText("menu.file.open")); openFileButton.addActionListener(_openFileAction); toolbar.add(openFileButton); // Add photo - JButton addPhotoButton = new JButton(new ImageIcon(getClass().getResource("images/add_photo_icon.png"))); + JButton addPhotoButton = new JButton(IconManager.getImageIcon(IconManager.ADD_PHOTO)); addPhotoButton.setToolTipText(I18nManager.getText("menu.file.addphotos")); addPhotoButton.addActionListener(_addPhotoAction); toolbar.add(addPhotoButton); // Save - _saveButton = new JButton(new ImageIcon(getClass().getResource("images/save_icon.gif"))); + _saveButton = new JButton(IconManager.getImageIcon(IconManager.SAVE_FILE)); _saveButton.setToolTipText(I18nManager.getText("menu.file.save")); _saveButton.addActionListener(_saveAction); _saveButton.setEnabled(false); toolbar.add(_saveButton); // Undo - _undoButton = new JButton(new ImageIcon(getClass().getResource("images/undo_icon.gif"))); + _undoButton = new JButton(IconManager.getImageIcon(IconManager.UNDO)); _undoButton.setToolTipText(I18nManager.getText("menu.edit.undo")); _undoButton.addActionListener(_undoAction); _undoButton.setEnabled(false); toolbar.add(_undoButton); // Edit point - _editPointButton = new JButton(new ImageIcon(getClass().getResource("images/edit_point_icon.gif"))); + _editPointButton = new JButton(IconManager.getImageIcon(IconManager.EDIT_POINT)); _editPointButton.setToolTipText(I18nManager.getText("menu.edit.editpoint")); _editPointButton.addActionListener(_editPointAction); _editPointButton.setEnabled(false); toolbar.add(_editPointButton); + // Delete point + _deletePointButton = new JButton(IconManager.getImageIcon(IconManager.DELETE_POINT)); + _deletePointButton.setToolTipText(I18nManager.getText("menu.edit.deletepoint")); + _deletePointButton.addActionListener(_deletePointAction); + _deletePointButton.setEnabled(false); + toolbar.add(_deletePointButton); + // Delete range + _deleteRangeButton = new JButton(IconManager.getImageIcon(IconManager.DELETE_RANGE)); + _deleteRangeButton.setToolTipText(I18nManager.getText("menu.edit.deleterange")); + _deleteRangeButton.addActionListener(_deleteRangeAction); + _deleteRangeButton.setEnabled(false); + toolbar.add(_deleteRangeButton); // Select start, end - _selectStartButton = new JButton(new ImageIcon(getClass().getResource("images/set_start_icon.png"))); + _selectStartButton = new JButton(IconManager.getImageIcon(IconManager.SET_RANGE_START)); _selectStartButton.setToolTipText(I18nManager.getText("menu.select.start")); _selectStartButton.addActionListener(_selectStartAction); _selectStartButton.setEnabled(false); toolbar.add(_selectStartButton); - _selectEndButton = new JButton(new ImageIcon(getClass().getResource("images/set_end_icon.png"))); + _selectEndButton = new JButton(IconManager.getImageIcon(IconManager.SET_RANGE_END)); _selectEndButton.setToolTipText(I18nManager.getText("menu.select.end")); _selectEndButton.addActionListener(_selectEndAction); _selectEndButton.setEnabled(false); toolbar.add(_selectEndButton); - _connectPhotoButton = new JButton(new ImageIcon(getClass().getResource("images/connect_photo_icon.png"))); + _connectPhotoButton = new JButton(IconManager.getImageIcon(IconManager.CONNECT_PHOTO)); _connectPhotoButton.setToolTipText(I18nManager.getText("menu.photo.connect")); _connectPhotoButton.addActionListener(_connectPhotoAction); _connectPhotoButton.setEnabled(false); @@ -574,7 +618,6 @@ public class MenuManager implements DataSubscriber _selectNoneItem.setEnabled(hasData); if (_show3dItem != null) _show3dItem.setEnabled(hasData); - _showOsmMapItem.setEnabled(hasData); _browserMapMenu.setEnabled(hasData); // is undo available? boolean hasUndo = !_app.getUndoStack().isEmpty(); @@ -587,6 +630,7 @@ public class MenuManager implements DataSubscriber _editPointButton.setEnabled(hasPoint); _editWaypointNameItem.setEnabled(hasPoint); _deletePointItem.setEnabled(hasPoint); + _deletePointButton.setEnabled(hasPoint); _selectStartItem.setEnabled(hasPoint); _selectStartButton.setEnabled(hasPoint); _selectEndItem.setEnabled(hasPoint); @@ -596,9 +640,9 @@ public class MenuManager implements DataSubscriber _saveExifItem.setEnabled(anyPhotos); // is there a current photo? boolean hasPhoto = anyPhotos && _selection.getCurrentPhotoIndex() >= 0; - // connect is only available when current photo is not connected to current point - boolean connectAvailable = hasPhoto && hasPoint - && _track.getPoint(_selection.getCurrentPointIndex()).getPhoto() == null; + // connect is available if photo and point selected, and photo has no point + boolean connectAvailable = hasPhoto && hasPoint && _photos.getPhoto(_selection.getCurrentPhotoIndex()) != null + && _photos.getPhoto(_selection.getCurrentPhotoIndex()).getDataPoint() == null; _connectPhotoItem.setEnabled(connectAvailable); _connectPhotoButton.setEnabled(connectAvailable); _disconnectPhotoItem.setEnabled(hasPhoto && _photos.getPhoto(_selection.getCurrentPhotoIndex()) != null @@ -608,10 +652,16 @@ public class MenuManager implements DataSubscriber // is there a current range? boolean hasRange = (hasData && _selection.hasRangeSelected()); _deleteRangeItem.setEnabled(hasRange); + _deleteRangeButton.setEnabled(hasRange); _interpolateItem.setEnabled(hasRange && (_selection.getEnd() - _selection.getStart()) == 1); _mergeSegmentsItem.setEnabled(hasRange); _reverseItem.setEnabled(hasRange); + _addTimeOffsetItem.setEnabled(hasRange); + // Is the currently selected point outside the current range? + _cutAndMoveItem.setEnabled(hasRange && hasPoint && + (_selection.getCurrentPointIndex() < _selection.getStart() + || _selection.getCurrentPointIndex() > (_selection.getEnd()+1))); } diff --git a/tim/prune/gui/SelectorDisplay.java b/tim/prune/gui/SelectorDisplay.java index 203db45..28e0a95 100644 --- a/tim/prune/gui/SelectorDisplay.java +++ b/tim/prune/gui/SelectorDisplay.java @@ -239,6 +239,7 @@ public class SelectorDisplay extends GenericDisplay if (_waypointList.getSelectedIndex() >= 0) { if (_trackInfo.getCurrentPoint() == null + || _waypointList.getSelectedIndex() >= _waypointListModel.getSize() || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint())) { // point is selected in list but different from current point - deselect diff --git a/tim/prune/gui/StatusBar.java b/tim/prune/gui/StatusBar.java index 75810db..a5462ed 100644 --- a/tim/prune/gui/StatusBar.java +++ b/tim/prune/gui/StatusBar.java @@ -63,7 +63,6 @@ public class StatusBar extends JPanel implements Runnable, DataSubscriber _thread = new Thread(this); _thread.start(); } - // TODO: Emphasize status bar when text set, eg change colour, make bold or something } /** diff --git a/tim/prune/gui/TimeOffsetDialog.java b/tim/prune/gui/TimeOffsetDialog.java new file mode 100644 index 0000000..11524fe --- /dev/null +++ b/tim/prune/gui/TimeOffsetDialog.java @@ -0,0 +1,162 @@ +package tim.prune.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.SwingConstants; + +import tim.prune.App; +import tim.prune.I18nManager; + +/** + * Class to show a dialog for adding a time offset to a track range + */ +public class TimeOffsetDialog +{ + private App _app = null; + private JFrame _parentFrame = null; + private JDialog _dialog = null; + private JRadioButton _addRadio = null, _subtractRadio = null; + private WholeNumberField _dayField = null, _hourField = null; + private WholeNumberField _minuteField = null; + + + /** + * Constructor + * @param inApp application object for callback + * @param inParentFrame parent frame + */ + public TimeOffsetDialog(App inApp, JFrame inParentFrame) + { + _app = inApp; + _parentFrame = inParentFrame; + } + + + /** + * Show the dialog to select options and export file + */ + public void showDialog() + { + // Make dialog window + if (_dialog == null) + { + _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.addtimeoffset.title"), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); + } + _dialog.show(); + } + + + /** + * Create dialog components + * @return Panel containing all gui elements in dialog + */ + private Component makeDialogComponents() + { + JPanel dialogPanel = new JPanel(); + dialogPanel.setLayout(new BorderLayout()); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + // Make a panel for the two radio buttons + JPanel radioPanel = new JPanel(); + _addRadio = new JRadioButton(I18nManager.getText("dialog.addtimeoffset.add")); + _addRadio.setSelected(true); + radioPanel.add(_addRadio); + _subtractRadio = new JRadioButton(I18nManager.getText("dialog.addtimeoffset.subtract")); + _subtractRadio.setSelected(false); + radioPanel.add(_subtractRadio); + ButtonGroup radioGroup = new ButtonGroup(); + radioGroup.add(_addRadio); + radioGroup.add(_subtractRadio); + mainPanel.add(radioPanel); + + // Make a listener to validate the text boxes during typing (to en/disable OK button) + + // Make a central panel with the text boxes + JPanel descPanel = new JPanel(); + descPanel.setLayout(new GridLayout(0, 2)); + descPanel.add(makeRightLabel("dialog.addtimeoffset.days")); + _dayField = new WholeNumberField(3); + descPanel.add(_dayField); + descPanel.add(makeRightLabel("dialog.addtimeoffset.hours")); + _hourField = new WholeNumberField(3); + descPanel.add(_hourField); + descPanel.add(makeRightLabel("dialog.addtimeoffset.minutes")); + _minuteField = new WholeNumberField(3); + descPanel.add(_minuteField); + mainPanel.add(descPanel); + dialogPanel.add(mainPanel, BorderLayout.CENTER); + + // button panel at bottom + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + JButton okButton = new JButton(I18nManager.getText("button.ok")); + ActionListener okListener = new ActionListener() { + public void actionPerformed(ActionEvent e) + { + finish(); + } + }; + okButton.addActionListener(okListener); + buttonPanel.add(okButton); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _dialog.dispose(); + } + }); + buttonPanel.add(cancelButton); + dialogPanel.add(buttonPanel, BorderLayout.SOUTH); + dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15)); + return dialogPanel; + } + + + /** + * @param inKey text key + * @return right-aligned label + */ + private static final JLabel makeRightLabel(String inKey) + { + JLabel label = new JLabel(I18nManager.getText(inKey) + " : "); + label.setHorizontalAlignment(SwingConstants.RIGHT); + return label; + } + + + /** + * Finish the dialog when OK pressed + */ + private void finish() + { + // Calculate offset to add or subtract + long offsetSecs = _minuteField.getValue() * 60L + + _hourField.getValue() * 60L * 60L + + _dayField.getValue() * 60L * 60L * 24L; + if (_subtractRadio.isSelected()) {offsetSecs = -offsetSecs;} + if (offsetSecs != 0L) + { + // Pass offset back to app and close dialog + _app.finishAddTimeOffset(offsetSecs); + _dialog.dispose(); + } + } +} diff --git a/tim/prune/gui/WholeNumberField.java b/tim/prune/gui/WholeNumberField.java new file mode 100644 index 0000000..dcf6740 --- /dev/null +++ b/tim/prune/gui/WholeNumberField.java @@ -0,0 +1,88 @@ +package tim.prune.gui; + +import javax.swing.JTextField; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +/** + * text field for holding a single integer with validation + */ +public class WholeNumberField extends JTextField +{ + /** + * Inner class to act as document for validation + */ + protected class WholeNumberDocument extends PlainDocument + { + /** Num digits to allow */ + private int _maxDigits = 0; + + /** + * Constructor + * @param inMaxDigits max digits to allow + */ + public WholeNumberDocument(int inMaxDigits) + { + _maxDigits = inMaxDigits; + } + + + /** + * Override the insert string method + * @param offs offset + * @param str string + * @param a attributes + * @throws BadLocationException on insert failure + */ + public void insertString(int offs, String str, AttributeSet a) + throws BadLocationException + { + if (getLength() >= _maxDigits) return; + char[] source = str.toCharArray(); + char[] result = new char[source.length]; + int j = 0; + for (int i = 0; i < result.length && j < _maxDigits; i++) { + if (Character.isDigit(source[i])) + result[j++] = source[i]; + } + super.insertString(offs, new String(result, 0, j), a); + } + } + + + /** + * Constructor + * @param inMaxDigits max digits to allow + */ + public WholeNumberField(int inMaxDigits) + { + super("0"); + setDocument(new WholeNumberDocument(inMaxDigits)); + } + + /** + * @return integer value + */ + public int getValue() + { + return parseValue(getText()); + } + + /** + * @param inText text to parse + * @return value as integer + */ + private static int parseValue(String inText) + { + int value = 0; + try { + value = Integer.parseInt(inText); + } + catch (NumberFormatException nfe) {} + if (value < 0) { + value = 0; + } + return value; + } +} diff --git a/tim/prune/gui/images/autopan.gif b/tim/prune/gui/images/autopan.gif new file mode 100644 index 0000000000000000000000000000000000000000..e80306b2345eda50aeed511dd12685e4d1dff352 GIT binary patch literal 203 zcmZ?wbhEHb6krfwc+A7Fs!U={h2**_$@Mi-8|tJs)k|$|kpA;@>7VCo|9!sx@7u$F zKVJSv0*XIb7#SG&8FUzc0AvOOOT~win(TYS6`BeyxPnTA3sg=uNz6@TXj~xBS}p3K z$jS9lq~lOaLxeyd3u{rMf(;u-Yl+uXB{u$pii!d*ROR7VCo|NoKy_xbw2 zZx8?dc=;a&6o0ZXGBEHn=r8~Q$P5OSz7L)=U#-Y|)y}GP<6FYPjS8&G5)UlY;Ymse zN|kU>&~YhDp31Z+Kv0LH6G(Xq2=Fvb*)YRMpydd|#urulV~;2(T4=B^y=-a-lVLe~ zKruiOsPcf~g*6NeqDt5Wg=2I~O+(2b=FCeqTm*ZVp!8xkA27 S94x+b=Zdm0`_2_)um%8W+fhFN literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/cut_and_move.gif b/tim/prune/gui/images/cut_and_move.gif new file mode 100644 index 0000000000000000000000000000000000000000..2249560383dcef3cd1c696c18c5139ca9a5a786b GIT binary patch literal 932 zcmZ?wbhEHb6krfw_|Cu}U>GG3HAgdhsZ;e@ujFd4>b1!|TZ^act!|k!XX)Ox#S&|) zCDtChx3yYgYm3Czz328GJ9lkw@wH>s_m1`4yZ8M6^QHfvul@i1-2d;_{(pb||Nr~{ zqhK@yhE53RFaQB4FEDU)GMID7>}X_SY~m1fa+t8;AsY)1t6f3F1cxSGegPSoiU$V{ zu!wOg%&@q?(9Fvw$E4uUpmLCjiQ8d|$HIjSY&chn^=W8*fuoSFU3aIE Ik&(d~0Bgt&F#rGn literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/delete_point_icon.gif b/tim/prune/gui/images/delete_point_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..c860af2c0a24cff834d719d2f5683cb241d770c8 GIT binary patch literal 598 zcmZ?wbhEHb6krfwIOfD~R9N+>g!)lQ^<$D6M-{Y>Dd?zpr{;rv{eBaqY)#Esxt;JvFm?W@dN7-TtJz-AQ-*5B7l{?SoDRIDhnr_~a3BD%|a4 zgxjeIx5q*Lr(!)ng(Q3mO?Z$PeI>QzTwdJg-wI zFU2L1vkpYfI+Q*C@YRl*FAa5HfvB-IXX)wUm1mmPoM>8irg{C@`*T*@S+-!t&I=Rw zTwk&0_>6rRafO#-rUSBsHmtQ z#J#VXomWyyikJO(y)vu#l`AaD`;?ila58d=i!muTIq+FAh>I~OTPcGmD?1JwM+P1S ziRv3AO zn|Um3Yg%<(bJmXnA*-Lx!d1>fifV0&LQVEkf~tLef>Kpd0)4UqvV8(lPP|NVe0_XU za#BE2j)##`g-@1GigU9RkWyjd;O180lj7L3M~Y9yo`H>pnS~8OGO@DXVr62z#SS8P a7&$mO7!jmBFB1b3FO-zxU=Uzoum%8JEVNkw literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/link.gif b/tim/prune/gui/images/link.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c397ec91c82132494c44471942d03efd3f6ddf0 GIT binary patch literal 920 zcmZ?wbhEHb6krfw_|Cx4nabCfDcF}KG_^o%L50MW0Q?p5-P&7vbaqTI+&$TN&s5U`Gc5Klj61X}`P91X%iBwq)+znJHSzDK+h-Sr z{09SuQ7{?;gFXZlf3h$#Fvv3KfLsd76AT=s4AvYn9t8@`Y^=guJ_i(;*;-}Qe0CH# zIJfcg3wvcm9C*~m$fXz|^1!gciCs*WVFyQolM}0$I0p-Z19M{&hfbJ;#lc6uO>9Oo xA`%DFow#^p?F?pZc;w3C!(Fz=LP5)Mk~p)#4X(ncYzzY2mw3F*avT^KtN|M7Q=|X@ literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/map_icon.gif b/tim/prune/gui/images/map_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..ec6cca4525bf483077bcc2039eaede6e71258c89 GIT binary patch literal 362 zcmZ?wbhEHb6krfwxXQo~6H^{jeln(fZOo*T0n4t1_pLGO*y1x`Q}l#2#d9{*&R!j} z=48ytlQC=8PC9vVEf75cDvl|iRDSZw$w?>6*Pfg-V@=K4ljW1v0+qx}ngk@vPoA7q zUJhiJLo_{ka&qV9;sbl<|Nryp$;q{o)ztp~|6g8S4kQ^U28usf7#SEe8FWAv8KXw*Z=hOKeIRAUA?9vcE|Jl zZMUcFn9{cQ)}-AR5|94hv13a5rJqNS6&2jRv;E@4(`Qy}zf*AeO4sc>1&?ku-M+DY z*Zl|cU%X8J@pJjd<|8lX|GBa0{)1hoKQ_O3Isg3U^PB$M`0?NM^XK#b|9<^X4WRgw zg^_{5oevcWET%mQlmYJw^%nhJ7C>LRS{%q$?jjEai5 zl9HG>4?8m>knbxaBP=8##L2uBBr7c_E6dJ#fMqKSNUx%wAP2)%W)?<8HG3CPLG~Nx nSQtfJoLoei&H)8QU7cJN1y~qa1QcC8?cKkA{{F+$k--`OX@<@s literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/points_connected.gif b/tim/prune/gui/images/points_connected.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6091c12b8fd8e7ce52299e6b5502edc0f46931a GIT binary patch literal 922 zcmZ?wbhEHb6krfw_|Cwf=#{M*RBag55>z}TyJczioMXjn&$X;Q*Ryo*l)cyH0MW5~ zYxf>oyZ7AIV?cCm>$T_C?md71{`>du|3|@S2n_WQQ2fcl$iN`Npu+$Jpgh6AQO=;r zA>%QjiJ6f>hQnsY28Ct@P6d{P4+f8put`XBSZqi*$jB&dUB}~K)X>1h#v{Ve8ML&4 z(SY5?Wk6e_$nsE2Zisv0ej Xy0n;Koo;y0UdxZGLYFc!GFSrux@8lw literal 0 HcmV?d00001 diff --git a/tim/prune/gui/images/points_disconnected.gif b/tim/prune/gui/images/points_disconnected.gif new file mode 100644 index 0000000000000000000000000000000000000000..1015169f0b547696ca9b70abd31ac1a2dc0b0933 GIT binary patch literal 897 zcmZ?wbhEHb6krfw_|Cwf=#{M*RBag5l0D~G@!E4OYtK#DduBZ)0Lbl`(^H(?Q(W9LW$BbD zJyYf^ozv1Xr>A<(lsR+e6wjGcJqO5ITD^2m_R^(Omag5pc4_h2wR6_4UAh(sw(i}! zw0P^<>aA;gwr-sRh z@80u!&mrLb^YwPhrR>wnvxCBZ)0Lbl`(^H(?Q(W9LW$BbD zJyYf^ozv1Xr>A<(lsR+e6wjGcJqO5ITD^2m_R^(Omag5pc4_h2wR6_4UAh(sw(i}! zw0P^<>aA;gwr-sRh z@80u!&mrLb^Y 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<= 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> 8) & 255; + int thirdBit = (pixelColor >> 16) & 255; + //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored + if (lowestBit < BRIGHTNESS_LIMIT || secondBit < BRIGHTNESS_LIMIT || thirdBit < BRIGHTNESS_LIMIT) return true; } - g.drawImage(icons[i].getImage(), 256*(i%2), 256*(i/2), 256, 256, null); - } - // show message if loading failed - if (loadingFailed) { - JOptionPane.showMessageDialog(this, - I18nManager.getText("error.osmimage.failed"), - I18nManager.getText("error.osmimage.dialogtitle"), - JOptionPane.ERROR_MESSAGE); - } - // red rectangle - int rectX1 = (int) (256 * ((_xRange.getMinimum() * (1<<_currZoom)) - inTileX)); - int rectX2 = (int) (256 * ((_xRange.getMaximum() * (1<<_currZoom)) - inTileX)); - int rectY1 = (int) (256 * ((_yRange.getMinimum() * (1<<_currZoom)) - inTileY)); - int rectY2 = (int) (256 * ((_yRange.getMaximum() * (1<<_currZoom)) - inTileY)); - g.setColor(Color.RED); - g.drawRect(rectX1, rectY1, rectX2-rectX1, rectY2-rectY1); - // draw points - g.setColor(Color.BLUE); - for (int i=0; i<_track.getNumPoints(); i++) - { - int px = (int) (256 * ((transformX(_track.getPoint(i).getLongitude().getDouble()) * (1<<_currZoom)) - inTileX)); - int py = (int) (256 * ((transformY(_track.getPoint(i).getLatitude().getDouble()) * (1<<_currZoom)) - inTileY)); - g.drawRect(px, py, 2, 2); - } - } - catch (MalformedURLException urle) { - _mapImage = null; + } + } + catch (NullPointerException e) { + // ignore null pointers, just return false } + return false; } + /** - * Zoom out, if not already at minimum zoom + * Inform that tiles have been updated and the map can be repainted + * @param isOK true if data loaded ok, false for error */ - public void zoomOut() + public synchronized void tilesUpdated(boolean inIsOk) { - if (_currZoom >= 2) + // Show message if loading failed (but not too many times) + if (!inIsOk && !_shownOsmErrorAlready) { - _currZoom--; - getMapTiles(-1, -1); - repaint(); + _shownOsmErrorAlready = true; + // use separate thread to show message about failing to load osm images + new Thread(new Runnable() { + public void run() { + try {Thread.sleep(500);} catch (InterruptedException ie) {} + JOptionPane.showMessageDialog(MapCanvas.this, + I18nManager.getText("error.osmimage.failed"), + I18nManager.getText("error.osmimage.dialogtitle"), + JOptionPane.ERROR_MESSAGE); + } + }).start(); } + _recalculate = true; + repaint(); } /** - * Zoom in, if not already at maximum zoom + * Zoom out, if not already at minimum zoom */ - public void zoomIn() + public void zoomOut() { - if (_currZoom < _maxZoom) - { - _currZoom++; - getMapTiles(-1, -1); - repaint(); - } + _mapPosition.zoomOut(); + _recalculate = true; + repaint(); } /** - * Transform a longitude into an x coordinate - * @param inLon longitude in degrees - * @return scaled X value from 0 to 1 + * Zoom in, if not already at maximum zoom */ - private static double transformX(double inLon) + public void zoomIn() { - return (inLon + 180.0) / 360.0; + _mapPosition.zoomIn(); + _recalculate = true; + repaint(); } /** - * Transform a latitude into a y coordinate - * @param inLat latitude in degrees - * @return scaled Y value from 0 to 1 + * Pan map + * @param inDeltaX x shift + * @param inDeltaY y shift */ - private static double transformY(double inLat) + public void panMap(int inDeltaX, int inDeltaY) { - return (1 - Math.log(Math.tan(inLat * Math.PI / 180) + 1 / Math.cos(inLat * Math.PI / 180)) / Math.PI) / 2; + _mapPosition.pan(inDeltaX, inDeltaY); + _recalculate = true; + repaint(); } /** @@ -209,7 +661,7 @@ public class MapCanvas extends JPanel */ public Dimension getMinimumSize() { - final Dimension minSize = new Dimension(512, 512); + final Dimension minSize = new Dimension(512, 300); return minSize; } @@ -220,4 +672,217 @@ public class MapCanvas extends JPanel { return getMinimumSize(); } + + + /** + * Respond to mouse click events + * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) + */ + public void mouseClicked(MouseEvent inE) + { + if (_track != null && _track.getNumPoints() > 0) + { + // select point if it's a left-click + if (!inE.isMetaDown()) + { + int pointIndex = _track.getNearestPointIndexNew( + _mapPosition.getXFromPixels(inE.getX(), getWidth()), + _mapPosition.getYFromPixels(inE.getY(), getHeight()), + _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false); + _selection.selectPoint(pointIndex); + } + else + { + // show the popup menu for right-clicks + _popupMenuX = inE.getX(); + _popupMenuY = inE.getY(); + _popup.show(this, _popupMenuX, _popupMenuY); + } + } + } + + /** + * Ignore mouse enter events + * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent) + */ + public void mouseEntered(MouseEvent inE) + { + // ignore + } + + /** + * Ignore mouse exited events + * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent) + */ + public void mouseExited(MouseEvent inE) + { + // ignore + } + + /** + * Ignore mouse pressed events + * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) + */ + public void mousePressed(MouseEvent inE) + { + // ignore + } + + /** + * Respond to mouse released events + * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) + */ + public void mouseReleased(MouseEvent inE) + { + _recalculate = true; + if (_zoomDragging && Math.abs(_dragToX - _dragFromX) > 20 && Math.abs(_dragToY - _dragFromY) > 20) + { + //System.out.println("Finished zoom: " + _dragFromX + ", " + _dragFromY + " to " + _dragToX + ", " + _dragToY); + _mapPosition.zoomToPixels(_dragFromX, _dragToX, _dragFromY, _dragToY, getWidth(), getHeight()); + } + _dragFromX = _dragFromY = -1; + _zoomDragging = false; + repaint(); + } + + /** + * Respond to mouse drag events + * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent) + */ + public void mouseDragged(MouseEvent inE) + { + if (!inE.isMetaDown()) + { + // Left mouse drag - pan map by appropriate amount + _zoomDragging = false; + if (_dragFromX != -1) + { + panMap(_dragFromX - inE.getX(), _dragFromY - inE.getY()); + _recalculate = true; + repaint(); + } + _dragFromX = inE.getX(); + _dragFromY = inE.getY(); + } + else + { + // Right-click and drag - draw rectangle and control zoom + _zoomDragging = true; + if (_dragFromX == -1) { + _dragFromX = inE.getX(); + _dragFromY = inE.getY(); + } + _dragToX = inE.getX(); + _dragToY = inE.getY(); + repaint(); + } + } + + /** + * Respond to mouse move events without button pressed + * @param inEvent ignored + */ + public void mouseMoved(MouseEvent inEvent) + { + // ignore + } + + /** + * Respond to status bar message from broker + * @param inMessage message, ignored + */ + public void actionCompleted(String inMessage) + { + // ignore + } + + /** + * Respond to data updated message from broker + * @param inUpdateType type of update + */ + public void dataUpdated(byte inUpdateType) + { + _recalculate = true; + if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) { + _checkBounds = true; + } + repaint(); + // enable or disable components + boolean hasData = _track.getNumPoints() > 0; + _topPanel.setVisible(hasData); + _sidePanel.setVisible(hasData); + // grab focus for the key presses + this.requestFocus(); + } + + /** + * Respond to key presses on the map canvas + * @param inE key event + */ + public void keyPressed(KeyEvent inE) + { + int code = inE.getKeyCode(); + // Check for meta key + if (inE.isControlDown()) + { + // Check for arrow keys to zoom in and out + if (code == KeyEvent.VK_UP) + zoomIn(); + else if (code == KeyEvent.VK_DOWN) + zoomOut(); + // Key nav for next/prev point + else if (code == KeyEvent.VK_LEFT) + _selection.selectPreviousPoint(); + else if (code == KeyEvent.VK_RIGHT) + _selection.selectNextPoint(); + } + else + { + // Check for arrow keys to pan + int upwardsPan = 0; + if (code == KeyEvent.VK_UP) + upwardsPan = -PAN_DISTANCE; + else if (code == KeyEvent.VK_DOWN) + upwardsPan = PAN_DISTANCE; + int rightwardsPan = 0; + if (code == KeyEvent.VK_RIGHT) + rightwardsPan = PAN_DISTANCE; + else if (code == KeyEvent.VK_LEFT) + rightwardsPan = -PAN_DISTANCE; + panMap(rightwardsPan, upwardsPan); + // Check for delete key to delete current point + if (code == KeyEvent.VK_DELETE && _selection.getCurrentPointIndex() >= 0) + { + _app.deleteCurrentPoint(); + } + } + } + + /** + * @param inE key released event, ignored + */ + public void keyReleased(KeyEvent e) + { + // ignore + } + + /** + * @param inE key typed event, ignored + */ + public void keyTyped(KeyEvent inE) + { + // ignore + } + + /** + * @param inE mouse wheel event indicating scroll direction + */ + public void mouseWheelMoved(MouseWheelEvent inE) + { + int clicks = inE.getWheelRotation(); + if (clicks < 0) + zoomIn(); + else if (clicks > 0) + zoomOut(); + } } diff --git a/tim/prune/gui/map/MapPosition.java b/tim/prune/gui/map/MapPosition.java new file mode 100644 index 0000000..73c7ed9 --- /dev/null +++ b/tim/prune/gui/map/MapPosition.java @@ -0,0 +1,272 @@ +package tim.prune.gui.map; + +/** + * Class to hold the current position of the map + */ +public class MapPosition +{ + /** Width and height of each tile of map */ + private static final int MAP_TILE_SIZE = 256; + + /** x position (scale depends on zoom) */ + private long _xPosition = 0L; + /** y position (scale depends on zoom) */ + private long _yPosition = 0L; + + /** Zoom level, from 2 to 15 */ + private int _zoom = 12; + /** Factor to zoom by, 2 to the power of zoom */ + private int _zoomFactor = 1 << _zoom; + + + /** + * Zoom and pan to show the selected area + * @param inMinX minimum transformed X + * @param inMaxX maximum transformed X + * @param inMinY minimum transformed Y + * @param inMaxY maximum transformed Y + * @param inWidth width of display + * @param inHeight height of display + */ + public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight) + { + // System.out.println("Zooming to " + inMinX + ", " + inMaxX + ", " + inMinY + ", " + inMaxY + "; width=" + inWidth + ", height=" + inHeight); + double diffX = Math.abs(inMaxX - inMinX); + double diffY = Math.abs(inMaxY - inMinY); + // Find out what zoom level to go to + int requiredZoom = -1; + for (int currZoom = 15; currZoom >= 2; currZoom--) + { + if (transformToPixels(diffX, currZoom) < inWidth + && transformToPixels(diffY, currZoom) < inHeight) + { + requiredZoom = currZoom; + break; + } + } + if (requiredZoom < 2) requiredZoom = 2; + // Set position + _zoom = requiredZoom; + _zoomFactor = 1 << _zoom; + _xPosition = transformToPixels((inMinX + inMaxX) / 2.0); + _yPosition = transformToPixels((inMinY + inMaxY) / 2.0); + } + + /** + * Zoom and pan to show the selected area + * @param inMinX minimum pixels X + * @param inMaxX maximum pixels X + * @param inMinY minimum pixels Y + * @param inMaxY maximum pixels Y + * @param inWidth width of display + * @param inHeight height of display + */ + public void zoomToPixels(int inMinX, int inMaxX, int inMinY, int inMaxY, int inWidth, int inHeight) + { + // System.out.println("Current position is " + _xPosition + ", " + _yPosition); + int diffX = Math.abs(inMaxX - inMinX); + int diffY = Math.abs(inMaxY - inMinY); + // Find out what zoom level to go to + int requiredZoom = -1; + int multFactor = 0; + for (int currZoom = 16; currZoom >= _zoom; currZoom--) + { + multFactor = 1 << (currZoom - _zoom); + if ((diffX * multFactor) < inWidth && (diffY * multFactor) < inHeight) + { + requiredZoom = currZoom; + break; + } + } + _zoom = requiredZoom; + _zoomFactor = 1 << _zoom; + // Set position + _xPosition = (_xPosition - inWidth/2 + (inMinX + inMaxX) / 2) * multFactor; + _yPosition = (_yPosition - inHeight/2 + (inMinY + inMaxY) / 2) * multFactor; + } + + /** + * Transform a given coordinate into pixels using the current zoom value + * @param inValue value to transform + * @return pixels + */ + private long transformToPixels(double inValue) + { + return transformToPixels(inValue, _zoom); + } + + /** + * Transform a given coordinate into pixels using the specified zoom value + * @param inValue value to transform + * @param inZoom zoom value to use + * @return pixels + */ + private static long transformToPixels(double inValue, int inZoom) + { + return (long) (inValue * MAP_TILE_SIZE * (1 << inZoom)); + } + + /** + * Convert pixels back into x coordinates + * @param inPixelX x coordinate on screen + * @param inWidth current width of window + * @return x coordinate + */ + public double getXFromPixels(int inPixelX, int inWidth) + { + return ((inPixelX - inWidth/2) + _xPosition) * 1.0 / MAP_TILE_SIZE / _zoomFactor; + } + + /** + * Convert pixels back into y coordinates + * @param inPixelY y coordinate on screen + * @param inHeight current height of window + * @return y coordinate + */ + public double getYFromPixels(int inPixelY, int inHeight) + { + return ((inPixelY - inHeight/2) + _yPosition) * 1.0 / MAP_TILE_SIZE / _zoomFactor; + } + + /** + * Get the horizontal offset from the centre + * @param inValue value to transform + * @return number of pixels right (+ve) or left (-ve) from the centre + */ + public int getXFromCentre(double inValue) + { + return (int) (transformToPixels(inValue) - _xPosition); + } + + /** + * Get the vertical offset from the centre + * @param inValue value to transform + * @return number of pixels up (+ve) or down (-ve) from the centre + */ + public int getYFromCentre(double inValue) + { + return (int) (transformToPixels(inValue) - _yPosition); + } + + /** + * Convert a pixel value into a bounds value for sensitivity + * @param inPixels number of pixels + * @return bounds value to use for x,y checking + */ + public double getBoundsFromPixels(int inPixels) { + return (inPixels * 1.0 / MAP_TILE_SIZE / _zoomFactor); + } + + /** + * Get the leftmost, rightmost, upper and lower index boundaries for the tiles to display + * @param inWidth width of window + * @param inHeight height of window + * @return tile indices as array left, right, up, down + */ + public int[] getTileIndices(int inWidth, int inHeight) + { + int[] result = new int[4]; + result[0] = getTileIndex(_xPosition - inWidth/2); + result[1] = getTileIndex(_xPosition + inWidth/2); + result[2] = getTileIndex(_yPosition - inHeight/2); + result[3] = getTileIndex(_yPosition + inHeight/2); + return result; + } + + /** + * Get the pixel offsets for the display + * @param inWidth width of window + * @param inHeight height of window + * @return offsets as x, y + */ + public int[] getDisplayOffsets(int inWidth, int inHeight) + { + int[] result = new int[2]; + result[0] = getDisplayOffset(_xPosition - inWidth/2); + result[1] = getDisplayOffset(_yPosition - inHeight/2); + return result; + } + + /** + * @return x index of the centre tile + */ + public int getCentreTileX() + { + return getTileIndex(_xPosition); + } + + /** + * @return y index of the centre tile + */ + public int getCentreTileY() + { + return getTileIndex(_yPosition); + } + + /** + * @param inPosition position of point + * @return tile index for that point + */ + private int getTileIndex(long inPosition) + { + return (int) (inPosition / MAP_TILE_SIZE); + } + + /** + * @param inPosition position of point + * @return pixel offset for that point + */ + private int getDisplayOffset(long inPosition) + { + return (int) (inPosition % MAP_TILE_SIZE); + // Maybe >> 8 would be slightly faster? + } + + /** + * Zoom in one level + */ + public void zoomIn() + { + if (_zoom < 16) + { + _zoom++; + _zoomFactor = 1 << _zoom; + _xPosition *= 2; + _yPosition *= 2; + } + } + + /** + * Zoom out one level + */ + public void zoomOut() + { + if (_zoom >= 2) + { + _zoom--; + _zoomFactor = 1 << _zoom; + _xPosition /= 2; + _yPosition /= 2; + } + } + + /** + * @return current zoom level + */ + public int getZoom() + { + return _zoom; + } + + /** + * Pan map by the specified amount + * @param inDeltaX amount to pan right + * @param inDeltaY amount to pan down + */ + public void pan(int inDeltaX, int inDeltaY) + { + // TODO: Check bounds? + _xPosition += inDeltaX; + _yPosition += inDeltaY; + } +} diff --git a/tim/prune/gui/map/MapTileCacher.java b/tim/prune/gui/map/MapTileCacher.java new file mode 100644 index 0000000..50387dd --- /dev/null +++ b/tim/prune/gui/map/MapTileCacher.java @@ -0,0 +1,196 @@ +package tim.prune.gui.map; + +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.image.ImageObserver; +import java.net.MalformedURLException; +import java.net.URL; + + +/** + * Class to handle the caching of map tiles from openstreetmap + */ +public class MapTileCacher implements ImageObserver +{ + /** Parent to be informed of updates */ + private MapCanvas _parent = null; + /** Default grid size */ + private static final int GRID_SIZE = 11; + /** Array of images to hold tiles */ + private Image[] _tiles = new Image[GRID_SIZE * GRID_SIZE]; + /** Current zoom level */ + private int _zoom = -1; + /** X coordinate of central tile */ + private int _tileX = -1; + /** Y coordinate of central tile */ + private int _tileY = -1; + /** X coord of grid centre */ + private int _gridCentreX = 0; + /** Y coord of grid centre */ + private int _gridCentreY = 0; + + + /** + * Constructor + * @param inParent parent canvas to be informed of updates + */ + public MapTileCacher(MapCanvas inParent) + { + _parent = inParent; + } + + /** + * Recentre the map and clear the cache + * @param inZoom zoom level + * @param inTileX x coord of central tile + * @param inTileY y coord of central tile + */ + public void centreMap(int inZoom, int inTileX, int inTileY) + { + if (inZoom != _zoom) + { + _zoom = inZoom; + clearAll(); + } + _gridCentreX = getCacheCoordinate(_gridCentreX + inTileX - _tileX); + _gridCentreY = getCacheCoordinate(_gridCentreY + inTileY - _tileY); + _tileX = inTileX; + _tileY = inTileY; + // Mark boundaries as invalid + for (int i=0; i 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 0; + boolean error = (infoflags & ImageObserver.ERROR) > 0; + if (loaded || error) { + _parent.tilesUpdated(loaded); + } + return !loaded; + } +} diff --git a/tim/prune/gui/map/MapUtils.java b/tim/prune/gui/map/MapUtils.java new file mode 100644 index 0000000..0c6cd1c --- /dev/null +++ b/tim/prune/gui/map/MapUtils.java @@ -0,0 +1,48 @@ +package tim.prune.gui.map; + +/** + * Class to manage coordinate conversions for maps + */ +public abstract class MapUtils +{ + /** + * Transform a longitude into an x coordinate + * @param inLon longitude in degrees + * @return scaled X value from 0 to 1 + */ + public static double getXFromLongitude(double inLon) + { + return (inLon + 180.0) / 360.0; + } + + /** + * Transform a latitude into a y coordinate + * @param inLat latitude in degrees + * @return scaled Y value from 0 to 1 + */ + public static double getYFromLatitude(double inLat) + { + return (1 - Math.log(Math.tan(inLat * Math.PI / 180) + 1 / Math.cos(inLat * Math.PI / 180)) / Math.PI) / 2; + } + + /** + * Transform an x coordinate into a longitude + * @param inX scaled X value from 0 to 1 + * @return longitude in degrees + */ + public static double getLongitudeFromX(double inX) + { + return inX * 360.0 - 180.0; + } + + /** + * Transform a y coordinate into a latitude + * @param inY scaled Y value from 0 to 1 + * @return latitude in degrees + */ + public static double getLatitudeFromY(double inY) + { + double n = Math.PI * (1 - 2 * inY); + return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); + } +} diff --git a/tim/prune/gui/map/MapWindow.java b/tim/prune/gui/map/MapWindow.java deleted file mode 100644 index f47414a..0000000 --- a/tim/prune/gui/map/MapWindow.java +++ /dev/null @@ -1,64 +0,0 @@ -package tim.prune.gui.map; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JPanel; - -import tim.prune.I18nManager; -import tim.prune.data.Track; - -/** - * Class to hold the gui functions of the map window - */ -public class MapWindow extends JFrame -{ - private MapCanvas _canvas = null; - - /** - * Constructor - * @param inTrack track object - */ - public MapWindow(Track inTrack) - { - super(I18nManager.getText("dialog.map.title")); - getContentPane().add(createComponents(inTrack)); - setResizable(false); - } - - /** - * @param inTrack track object - * @return gui components - */ - private Component createComponents(Track inTrack) - { - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - _canvas = new MapCanvas(inTrack); - panel.add(_canvas, BorderLayout.CENTER); - // Make panel for zoom buttons - JPanel buttonPanel = new JPanel(); - JButton zoomInButton = new JButton(I18nManager.getText("menu.map.zoomin")); - zoomInButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - _canvas.zoomIn(); - } - }); - buttonPanel.add(zoomInButton); - JButton zoomOutButton = new JButton(I18nManager.getText("menu.map.zoomout")); - zoomOutButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - _canvas.zoomOut(); - } - }); - buttonPanel.add(zoomOutButton); - panel.add(buttonPanel, BorderLayout.SOUTH); - return panel; - } -} diff --git a/tim/prune/lang/prune-texts.properties b/tim/prune/lang/prune-texts.properties index d186135..53a65b0 100644 --- a/tim/prune/lang/prune-texts.properties +++ b/tim/prune/lang/prune-texts.properties @@ -3,8 +3,9 @@ # Menu entries menu.file=File -menu.file.open=Open +menu.file.open=Open file menu.file.addphotos=Add photos +menu.file.loadfromgps=Load from GPS menu.file.save=Save menu.file.exportkml=Export KML menu.file.exportgpx=Export GPX @@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Delete duplicates menu.edit.compress=Compress track menu.edit.interpolate=Interpolate menu.edit.reverse=Reverse range +menu.edit.addtimeoffset=Add time offset menu.edit.mergetracksegments=Merge track segments menu.edit.rearrange=Rearrange waypoints menu.edit.rearrange.start=All to start of file menu.edit.rearrange.end=All to end of file menu.edit.rearrange.nearest=Each to nearest track point +menu.edit.cutandmove=Cut and move selection menu.select=Select menu.select.all=Select all menu.select.none=Select none @@ -39,18 +42,20 @@ menu.photo.correlate=Correlate all photos menu.photo.delete=Remove photo menu.view=View menu.view.show3d=Show in three-D -menu.view.showmap=Show map menu.view.browser=Map in browser menu.view.browser.google=Google maps menu.view.browser.openstreetmap=Openstreetmap menu.help=Help menu.help.about=About Prune +menu.help.checkversion=Check for new version # Popup menu for map menu.map.zoomin=Zoom in menu.map.zoomout=Zoom out menu.map.zoomfull=Zoom to full scale +menu.map.newpoint=Create new point menu.map.connect=Connect track points menu.map.autopan=Autopan +menu.map.showmap=Show map # Dialogs dialog.exit.confirm.title=Exit Prune @@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extract of file dialog.openoptions.altitudeunits=Altitude units dialog.jpegload.subdirectories=Include subdirectories dialog.jpegload.loadjpegswithoutcoords=Include photos without coordinates +dialog.jpegload.loadjpegsoutsidearea=Include photos outside current area dialog.jpegload.progress.title=Loading photos dialog.jpegload.progress=Please wait while the photos are searched +dialog.gpsload.title=Load from GPS +dialog.gpsload.nogpsbabel=No gpsbabel program could be found. Continue? +dialog.gpsload.device=Device name +dialog.gpsload.format=Format +dialog.gpsload.getwaypoints=Load waypoints +dialog.gpsload.gettracks=Load tracks dialog.saveoptions.title=Save file dialog.save.fieldstosave=Fields to save dialog.save.table.field=Field @@ -102,21 +114,24 @@ dialog.exportkml.text=Title for the data dialog.exportkml.altitude=Include altitudes (for aviation) dialog.exportkml.kmz=Compress to make kmz file dialog.exportkml.exportimages=Export image thumbnails to kmz -dialog.exportkml.filetype=KML, KMZ files dialog.exportgpx.title=Export GPX dialog.exportgpx.name=Name dialog.exportgpx.desc=Description -dialog.exportgpx.filetype=GPX files +dialog.exportgpx.includetimestamps=Include timestamps dialog.exportpov.title=Export POV dialog.exportpov.text=Please enter the parameters for the POV export dialog.exportpov.font=Font dialog.exportpov.camerax=Camera X dialog.exportpov.cameray=Camera Y dialog.exportpov.cameraz=Camera Z -dialog.exportpov.filetype=POV files +dialog.exportpov.modelstyle=Model style +dialog.exportpov.ballsandsticks=Balls and sticks +dialog.exportpov.tubesandwalls=Tubes and walls dialog.exportpov.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue? dialog.confirmreversetrack.title=Confirm reversal dialog.confirmreversetrack.text=This track contains timestamp information, which will be out of sequence after a reversal.\nAre you sure you want to reverse this section? +dialog.confirmcutandmove.title=Confirm cut and move +dialog.confirmcutandmove.text=This track contains timestamp information, which will be out of sequence after a move.\nAre you sure you want to move this section? dialog.interpolate.title=Interpolate points dialog.interpolate.parameter.text=Number of points to insert between selected points dialog.undo.title=Undo action(s) @@ -136,7 +151,16 @@ dialog.pointnameedit.title=Edit waypoint name dialog.pointnameedit.name=Waypoint name dialog.pointnameedit.uppercase=UPPER case dialog.pointnameedit.lowercase=lower case -dialog.pointnameedit.sentencecase=Sentence case +dialog.pointnameedit.sentencecase=Sentence Case +dialog.addtimeoffset.title=Add time offset +dialog.addtimeoffset.add=Add time +dialog.addtimeoffset.subtract=Subtract time +dialog.addtimeoffset.days=Days +dialog.addtimeoffset.hours=Hours +dialog.addtimeoffset.minutes=Minutes +dialog.addtimeoffset.notimestamps=Cannot add a time offset as this selection doesn't contain any timestamp information +dialog.connect.title=Connect photo to point +dialog.connectphoto.clonepoint=This point already has a photo.\nDo you want to make a copy of the point? dialog.saveexif.title=Save Exif dialog.saveexif.intro=Select the photos to save using the checkboxes dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save @@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=No distance limit dialog.correlate.options.distancelimit=Distance limit dialog.correlate.options.correlate=Correlate dialog.correlate.alloutsiderange=All photos are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one photo. -dialog.map.title=Prune map dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\nfor more information and user guides. dialog.about.title=About Prune dialog.about.version=Version @@ -179,6 +202,7 @@ dialog.about.build=Build dialog.about.summarytext1=Prune is a program for loading, displaying and editing data from GPS receivers. dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.
Copying, redistribution and modification are permitted and encouraged
according to the conditions in the included license.txt file. dialog.about.summarytext3=Please see http://activityworkshop.net/ for more information and user guides. +dialog.about.languages=Available languages dialog.about.translatedby=English text by activityworkshop. dialog.about.systeminfo=System info dialog.about.systeminfo.os=Operating System @@ -199,6 +223,14 @@ dialog.about.credits.devtools=Development tools dialog.about.credits.othertools=Other tools dialog.about.credits.thanks=Thanks to dialog.about.readme=Readme +dialog.checkversion.title=Check version +dialog.checkversion.error=The version number couldn't be checked.\nPlease check the internet connection. +dialog.checkversion.uptodate=You are using the latest version of Prune. +dialog.checkversion.newversion1=A new version of Prune is now available! The latest version is now version +dialog.checkversion.newversion2=. +dialog.checkversion.releasedate1=This new version was released on +dialog.checkversion.releasedate2=. +dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html. # 3d window dialog.3d.title=Prune Three-d view @@ -216,8 +248,11 @@ confirm.deleteduplicates.multi=duplicates were deleted confirm.deletepoint.single=data point was removed confirm.deletepoint.multi=data points were removed confirm.point.edit=point edited -confirm.mergetracksegments=track segments merged +confirm.mergetracksegments=Track segments merged confirm.reverserange=Range reversed +confirm.addtimeoffset=Time offset added +confirm.rearrangewaypoints=Waypoints rearranged +confirm.cutandmove=Selection moved confirm.saveexif.ok1=Saved confirm.saveexif.ok2=photo files confirm.undo.single=operation undone @@ -228,6 +263,7 @@ confirm.photo.connect=photo connected confirm.photo.disconnect=photo disconnected confirm.correlate.single=photo was correlated confirm.correlate.multi=photos were correlated +confirm.createpoint=point created # Buttons button.ok=OK @@ -251,6 +287,15 @@ button.selectall=Select all button.selectnone=Select none button.preview=Preview button.guessfields=Guess fields +button.showwebpage=Show webpage + +# File types +filetype.txt=TXT files +filetype.jpeg=JPG files +filetype.kmlkmz=KML, KMZ files +filetype.kml=KML files +filetype.gpx=GPX files +filetype.pov=POV files # Display components display.nodata=No data loaded @@ -280,6 +325,7 @@ display.range.time.mins=m display.range.time.hours=h display.range.time.days=d details.range.avespeed=Ave speed +details.range.avemovingspeed=Moving ave details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Photos details.photodetails=Photo details @@ -291,13 +337,14 @@ details.photo.connected=Connected fieldname.latitude=Latitude fieldname.longitude=Longitude fieldname.altitude=Altitude -fieldname.timestamp=Timestamp +fieldname.timestamp=Time fieldname.waypointname=Name fieldname.waypointtype=Type fieldname.newsegment=Segment fieldname.custom=Custom fieldname.prefix=Field fieldname.distance=Distance +fieldname.movingdistance=Moving distance fieldname.duration=Duration # Measurement units @@ -339,10 +386,13 @@ undo.insert=insert points undo.deleteduplicates=delete duplicates undo.reverse=reverse range undo.mergetracksegments=merge track segments +undo.addtimeoffset=add time offset undo.rearrangewaypoints=rearrange waypoints +undo.cutandmove=move section undo.connectphoto=connect photo undo.disconnectphoto=disconnect photo undo.correlate=correlate photos +undo.createpoint=create point # Error messages error.save.dialogtitle=Error saving data diff --git a/tim/prune/lang/prune-texts_de.properties b/tim/prune/lang/prune-texts_de.properties index 1f8b0d1..d01a872 100644 --- a/tim/prune/lang/prune-texts_de.properties +++ b/tim/prune/lang/prune-texts_de.properties @@ -3,34 +3,37 @@ # Menu entries menu.file=Datei -menu.file.open=Öffnen +menu.file.open=Datei öffnen menu.file.addphotos=Fotos laden +menu.file.loadfromgps=Vom GPS laden menu.file.save=Speichern menu.file.exportkml=KML exportieren menu.file.exportgpx=GPX exportieren menu.file.exportpov=POV exportieren menu.file.exit=Beenden menu.edit=Bearbeiten -menu.edit.undo=Undo -menu.edit.clearundo=Undo-Liste löschen +menu.edit.undo=Rückgängig +menu.edit.clearundo=Liste der letzten Änderungen löschen menu.edit.editpoint=Punkt bearbeiten menu.edit.editwaypointname=Waypoint Name bearbeiten menu.edit.deletepoint=Punkt löschen -menu.edit.deleterange=Spanne löschen +menu.edit.deleterange=Bereich löschen menu.edit.deleteduplicates=Duplikate löschen menu.edit.compress=Track komprimieren menu.edit.interpolate=Interpolieren -menu.edit.reverse=Spanne umkehren -menu.edit.mergetracksegments=Trackteilen verbinden +menu.edit.reverse=Bereich umkehren +menu.edit.addtimeoffset=Zeitdifferenz addieren +menu.edit.mergetracksegments=Trackteile verbinden menu.edit.rearrange=Waypoints reorganisieren -menu.edit.rearrange.start=Alle zum Anfang -menu.edit.rearrange.end=Alle zum Ende -menu.edit.rearrange.nearest=Jeder zum nächsten Trackpunkt +menu.edit.rearrange.start=Alle Waypoints zum Anfang +menu.edit.rearrange.end=Alle Waypoints zum Ende +menu.edit.rearrange.nearest=Jeden Waypoint zum nächsten Trackpunkt verschieben +menu.edit.cutandmove=Schneiden und verschieben menu.select=Markieren menu.select.all=Alles markieren menu.select.none=Nichts markieren -menu.select.start=Start setzen -menu.select.end=Stopp setzen +menu.select.start=Startpunkt setzen +menu.select.end=Endpunkt setzen menu.photo=Foto menu.photo.saveexif=Exif Daten speichern menu.photo.connect=Mit Punkt verbinden @@ -38,35 +41,37 @@ menu.photo.disconnect=Vom Punkt trennen menu.photo.correlate=Alle Fotos korrelieren menu.photo.delete=Foto entfernen menu.view=Ansicht -menu.view.show3d=In drei-D zeigen -menu.view.showmap=Karte zeigen +menu.view.show3d=In 3D zeigen menu.view.browser=Karte in Browser menu.view.browser.google=Google Maps menu.view.browser.openstreetmap=Openstreetmap menu.help=Hilfe menu.help.about=Über Prune +menu.help.checkversion=Nach neuen Versionen suchen # Popup menu for map -menu.map.zoomin=Einzoomen -menu.map.zoomout=Auszoomen -menu.map.zoomfull=Zoomen zum ganzes Bild -menu.map.connect=Trackpunkte mit Linie -menu.map.autopan=Autopan +menu.map.zoomin=Hineinzoomen +menu.map.zoomout=Herauszoomen +menu.map.zoomfull=Auf Bildschirmgröße anpassen +menu.map.newpoint=Neuen Punkt kreieren +menu.map.connect=Trackpunkte mit Linie anzeigen +menu.map.autopan=Autozentrierung +menu.map.showmap=Karte zeigen # Dialogs dialog.exit.confirm.title=Prune beenden -dialog.exit.confirm.text=Ihre Daten wurden nicht gespeichert. Wollen Sie trotzdem das Programm beenden? -dialog.openappend.title=Daten anhängen oder ersetzen -dialog.openappend.text=Häng diese Daten zu den aktuellen Daten an? +dialog.exit.confirm.text=Ihre Daten wurden nicht gespeichert. Wollen Sie das Programm trotzdem beenden? +dialog.openappend.title=an existierende Daten anhängen oder ersetzen +dialog.openappend.text=Diese Daten an die aktuellen Daten anhängen? dialog.deletepoint.title=Punkt löschen -dialog.deletepoint.deletephoto=Foto von diesem Punkt auch löschen? -dialog.deletephoto.title=Photo entfernen -dialog.deletephoto.deletepoint=Punkt von diesem Foto auch löschen? +dialog.deletepoint.deletephoto=Das zu diesem Punkt gehörende Foto ebenfalls löschen? +dialog.deletephoto.title=Foto entfernen +dialog.deletephoto.deletepoint=Den zu diesem Foto gehörenden Punkt auch löschen? dialog.deleteduplicates.title=Duplikate löschen dialog.deleteduplicates.nonefound=Keine Duplikate gefunden dialog.compresstrack.title=Track komprimieren -dialog.compresstrack.parameter.text=Parameter für Komprimierung (niedriger Nummer = höher Komprimierung) -dialog.compresstrack.nonefound=Keine Punkte konnten entfernt werden -dialog.openoptions.title=Öffnen Optionen +dialog.compresstrack.parameter.text=Parameter für Komprimierung (niedrige Nummer = höhere Komprimierung) +dialog.compresstrack.nonefound=Es konnten keine Punkte entfernt werden +dialog.openoptions.title=Öffnen dialog.openoptions.filesnippet=Extrakt von der Datei dialog.load.table.field=Feld dialog.load.table.datatype=Daten Typ @@ -74,53 +79,63 @@ dialog.load.table.description=Beschreibung dialog.delimiter.label=Feld Trennzeichen dialog.delimiter.comma=Komma , dialog.delimiter.tab=Tab -dialog.delimiter.space=Abstand +dialog.delimiter.space=Leerstelle dialog.delimiter.semicolon=Strichpunkt ; dialog.delimiter.other=Andere -dialog.openoptions.deliminfo.records=Rekords, mit +dialog.openoptions.deliminfo.records=Aufnahmen, mit dialog.openoptions.deliminfo.fields=Feldern dialog.openoptions.deliminfo.norecords=Keine Rekords dialog.openoptions.tabledesc=Extrakt von der Datei dialog.openoptions.altitudeunits=Höhe Maßeinheiten -dialog.jpegload.subdirectories=Subordnern auch durchsuchen +dialog.jpegload.subdirectories=Unterordner auch durchsuchen dialog.jpegload.loadjpegswithoutcoords=Auch Fotos ohne Koordinaten laden +dialog.jpegload.loadjpegsoutsidearea=Auch Fotos ausserhalb vom Track laden dialog.jpegload.progress.title=Fotos werden geladen dialog.jpegload.progress=Bitte warten während die Fotos durchgesucht werden +dialog.gpsload.title=Vom GPS laden +dialog.gpsload.nogpsbabel=Kein gpsbabel Programm wurde gefunden. Weiter? +dialog.gpsload.device=Device Name +dialog.gpsload.format=Format +dialog.gpsload.getwaypoints=Waypoints laden +dialog.gpsload.gettracks=Tracks laden dialog.saveoptions.title=Datei speichern -dialog.save.fieldstosave=Felder zu speichern +dialog.save.fieldstosave=Zu speichernde Felder dialog.save.table.field=Feld -dialog.save.table.hasdata=Hat Daten +dialog.save.table.hasdata=Enthält Daten dialog.save.table.save=Speichern -dialog.save.headerrow=Titel Zeile speichern +dialog.save.headerrow=Titelzeile speichern dialog.save.coordinateunits=Koordinaten Maßeinheiten dialog.save.altitudeunits=Höhe Maßeinheiten dialog.save.timestampformat=Zeitstempelformat -dialog.save.overwrite.title=Datei existiert -dialog.save.overwrite.text=Diese Datei existiert schon. Sind Sie sicher, Sie wollen die Datei überschreiben? +dialog.save.overwrite.title=Datei schon vorhanden +dialog.save.overwrite.text=Diese Datei existiert schon. Wollen Sie die vorhandene Datei überschreiben? dialog.exportkml.title=KML exportieren dialog.exportkml.text=Titel für die Daten dialog.exportkml.altitude=Auch Höheninformation (für Luftfahrt) -dialog.exportkml.kmz=Daten ins kmz Datei komprimieren -dialog.exportkml.exportimages=Bilder ins kmz exportieren -dialog.exportkml.filetype=KML, KMZ Dateien +dialog.exportkml.kmz=Daten in kmz Datei komprimieren +dialog.exportkml.exportimages=Bilder in kmz exportieren dialog.exportgpx.title=GPX exportieren dialog.exportgpx.name=Name dialog.exportgpx.desc=Beschreibung -dialog.exportgpx.filetype=GPX Dateien +dialog.exportgpx.includetimestamps=Zeitstempel exportieren dialog.exportpov.title=POV exportieren -dialog.exportpov.text=Geben Sie die Parameter ein für das POV Export +dialog.exportpov.text=Geben Sie die Parameter für den POV Export ein dialog.exportpov.font=Font dialog.exportpov.camerax=Kamera X dialog.exportpov.cameray=Kamera Y dialog.exportpov.cameraz=Kamera Z -dialog.exportpov.filetype=POV Dateien -dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nSind Sie sicher, Sie wollen fortsetzen? +dialog.exportpov.modelstyle=Modellstil +dialog.exportpov.ballsandsticks=Bälle und Stangen +dialog.exportpov.tubesandwalls=Röhren und Wände +dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nMöchten Sie den Vorgang trotzdem fortführen? dialog.confirmreversetrack.title=Umkehrung bestätigen -dialog.confirmreversetrack.text=Diese Daten enthalten Zeitstempel Informationen, die bei einer Umkehrung ausser Reihenfolge erscheinen würden.\nSind Sie sicher, Sie wollen diese Spanne umkehren? +dialog.confirmreversetrack.text=Diese Daten enthalten Zeitstempel Informationen, die bei einer Umkehrung in falscher Reihenfolge erscheinen würden.\nSind Sie sicher, dass Sie diesen Bereich umkehren wollen? +dialog.confirmcutandmove.title=Verschieben bestätigen +dialog.confirmcutandmove.text=Diese Daten enthalten Zeitstempel Informationen, die nach dem Verschieben in falscher Reihenfolge erscheinen würden.\nSind Sie sicher, dass Sie diesen Bereich verschieben wollen? dialog.interpolate.title=Punkte interpolieren -dialog.interpolate.parameter.text=Anzahl Punkte zuzufügen zwischen den selektierten Punkten -dialog.undo.title=Undo Operation(en) -dialog.undo.pretext=Selektieren die Operationen die rückgängig gemacht werden sollen. +dialog.interpolate.parameter.text=Anzahl Punkte die zwischen den gewählten Punkten eingefügt werden sollen +dialog.undo.title=Aktionen Rückgängig +dialog.undo.pretext=Auswahl der Operationen die rückgängig gemacht werden sollen. dialog.undo.none.title=Undo nicht möglich dialog.undo.none.text=Keine Operationen können rückgängig gemacht werden. dialog.clearundo.title=Undo-Liste löschen @@ -134,9 +149,18 @@ dialog.pointedit.changevalue.text=Geben Sie den neuen Wert f dialog.pointedit.changevalue.title=Feld bearbeiten dialog.pointnameedit.title=Waypoint Name bearbeiten dialog.pointnameedit.name=Waypoint Name -dialog.pointnameedit.uppercase=GROSS geschrieben +dialog.pointnameedit.uppercase=GROß geschrieben dialog.pointnameedit.lowercase=klein geschrieben dialog.pointnameedit.sentencecase=Gemischt geschrieben +dialog.addtimeoffset.title=Zeitdifferenz addieren +dialog.addtimeoffset.add=Zeit addieren +dialog.addtimeoffset.subtract=Zeit subtrahieren +dialog.addtimeoffset.days=Tage +dialog.addtimeoffset.hours=Stunde +dialog.addtimeoffset.minutes=Minute +dialog.addtimeoffset.notimestamps=Zeitdifferenz kann nicht addiert werden weil dieser Bereich keine Zeitinformation hat +dialog.connect.title=Foto mit Punkt verbinden +dialog.connectphoto.clonepoint=Dieser Punkt hat schon ein Foto.\nWollen Sie eine Kopie von dem Punkt machen? dialog.saveexif.title=Exif speichern dialog.saveexif.intro=Selektieren Sie die Fotos zu speichern dialog.saveexif.nothingtosave=Koordinaten sind nicht modifiziert, nichts zu speichern @@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Keine Distanzgrenzen dialog.correlate.options.distancelimit=Distanzgrenzen dialog.correlate.options.correlate=Korrelieren dialog.correlate.alloutsiderange=Alle Fotos sind ausserhalb vom Track Zeitraum, so können nicht korreliert werden.\nVersuchen Sie mit einem anderen Offset oder verbinden Sie manuell mindestens ein Foto. -dialog.map.title=Prune Karte dialog.help.help=Bitte sehen Sie\n http://activityworkshop.net/software/prune/\nfür weitere Information und Benutzeranleitungen. dialog.about.title=Über Prune dialog.about.version=Version @@ -179,6 +202,7 @@ dialog.about.build=Build dialog.about.summarytext1=Prune ist ein Programm für das Laden, Darstellen und Editieren von Daten von GPS Geräten. dialog.about.summarytext2=Es ist unter den Gnu GPL zur Verfügung gestellt, für frei, gratis und offen Gebrauch und Weiterentwicklung.
Kopieren, Weiterverbreitung und Veränderungen sind erlaubt und willkommen
unter die Bedingungen in der enthaltenen license.txt Datei. dialog.about.summarytext3=Bitte sehen Sie http://activityworkshop.net/ für weitere Information und Benutzeranleitungen. +dialog.about.languages=Verfügbare Sprachen dialog.about.translatedby=Deutsche Übersetzung von activityworkshop. dialog.about.systeminfo=System Information dialog.about.systeminfo.os=Betriebsystem @@ -192,20 +216,28 @@ dialog.about.no=Nein dialog.about.credits=Credits dialog.about.credits.code=Prune Code geschrieben von dialog.about.credits.exifcode=Exif Code von -dialog.about.credits.icons=Einige Ikons von +dialog.about.credits.icons=Einige Bilder von dialog.about.credits.translators=Dolmetscher dialog.about.credits.translations=Übersetzungen mit Hilfe von dialog.about.credits.devtools=Entwicklungsprogrammen dialog.about.credits.othertools=Andere Programmen dialog.about.credits.thanks=Danke an dialog.about.readme=Liesmich +dialog.checkversion.title=Versionnummer prüfen +dialog.checkversion.error=Die Versionnummer konnte nicht geprüft werden.\nBitte prüfen Sie die Internet Verbindung. +dialog.checkversion.uptodate=Sie haben schon die letzte Version vom Prune. +dialog.checkversion.newversion1=Eine neue Version vom Prune ist jetzt verfügbar! Die neue Version heißt Version +dialog.checkversion.newversion2=. +dialog.checkversion.releasedate1=Diese neue Version ist am +dialog.checkversion.releasedate2=veröffentlicht worden. +dialog.checkversion.download=Um die neue Version herunterzuladen, schauen Sie nach http://activityworkshop.net/software/prune/download.html. # 3d window -dialog.3d.title=Prune Drei-D Ansicht +dialog.3d.title=Prune 3D Ansicht dialog.3d.altitudecap=Minimum Höhenskala dialog.3dlines.title=Prune Gitterlinien dialog.3dlines.empty=Keine Linien zum anzeigen! -dialog.3dlines.intro=Hier sind die Linien für die drei-D Ansicht +dialog.3dlines.intro=Hier sind die Linien für die 3D Ansicht # Confirm messages || These are displayed as confirmation in the status bar confirm.loadfile=Daten geladen vom @@ -217,7 +249,10 @@ confirm.deletepoint.single=Punkt wurde entfernt confirm.deletepoint.multi=Punkte wurden entfernt confirm.point.edit=Punkt editiert confirm.mergetracksegments=Trackteilen verbunden -confirm.reverserange=Spanne umgekehrt +confirm.reverserange=Bereich umgekehrt +confirm.addtimeoffset=Zeitdifferenz addiert +confirm.rearrangewaypoints=Waypoints reorganisiert +confirm.cutandmove=Bereich verschoben confirm.saveexif.ok1=Es wurden confirm.saveexif.ok2=Foto Dateien geschrieben confirm.undo.single=Operation rückgängig gemacht @@ -228,6 +263,7 @@ confirm.photo.connect=Foto verbunden confirm.photo.disconnect=Foto getrennt confirm.correlate.single=Foto wurde korreliert confirm.correlate.multi=Fotos wurden korreliert +confirm.createpoint=Punkt kreiert # Buttons button.ok=OK @@ -251,6 +287,15 @@ button.selectall=Alle selektieren button.selectnone=Nichts selektieren button.preview=Vorschauen button.guessfields=Felder erraten +button.showwebpage=Webseite anzeigen + +# File types +filetype.txt=TXT Dateien +filetype.jpeg=JPG Dateien +filetype.kmlkmz=KML, KMZ Dateien +filetype.kml=KML Dateien +filetype.gpx=GPX Dateien +filetype.pov=POV Dateien # Display components display.nodata=Keine Daten geladen @@ -267,19 +312,20 @@ details.nopointselection=Nichts selektiert details.speed=Geschwindigkeit details.photofile=Foto Datei details.norangeselection=Nichts selektiert -details.rangedetails=Details von Spanne -details.range.selected=Selektiert +details.rangedetails=Details vom Bereich +details.range.selected=Markiert details.range.to=bis details.altitude.to=bis details.range.climb=Aufstieg details.range.descent=Abstieg details.coordformat=Koordinatenformat details.distanceunits=Distanz Maßeinheiten -display.range.time.secs=S -display.range.time.mins=M -display.range.time.hours=Std +display.range.time.secs=s +display.range.time.mins=m +display.range.time.hours=h display.range.time.days=T details.range.avespeed=Geschwindigkeit +details.range.avemovingspeed=Geschwindigkeit unterwegs details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Fotos details.photodetails=Details vom Foto @@ -298,21 +344,22 @@ fieldname.newsegment=Segment fieldname.custom=Custom fieldname.prefix=Feld fieldname.distance=Länge +fieldname.movingdistance=Weglänge fieldname.duration=Zeitlänge # Measurement units units.original=Original units.default=Default units.metres=Meter -units.metres.short=M +units.metres.short=m units.feet=Füße -units.feet.short=F +units.feet.short=ft units.kilometres=Kilometer -units.kilometres.short=Km -units.kmh=Km/h +units.kilometres.short=km +units.kmh=km/h units.miles=Meilen -units.miles.short=Mei -units.mph=Mei/Std +units.miles.short=mi +units.mph=mi/h units.degminsec=Grad-Min-Sek units.degmin=Grad-Min units.deg=Grad @@ -333,16 +380,19 @@ undo.loadphotos=Fotos laden undo.editpoint=Punkt bearbeiten undo.deletepoint=Punkt löschen undo.deletephoto=Photo entfernen -undo.deleterange=Spanne löschen +undo.deleterange=Bereich löschen undo.compress=Track komprimieren undo.insert=Punkte hinzufügen undo.deleteduplicates=Duplikaten löschen -undo.reverse=Spanne umdrehen -undo.mergetracksegments=Trackteilen verbinden +undo.reverse=Bereich umdrehen +undo.mergetracksegments=Trackteile verbinden +undo.addtimeoffset=zeitdifferenz addieren undo.rearrangewaypoints=Waypoints reorganisieren +undo.cutandmove=Bereich verschieben undo.connectphoto=Foto verbinden undo.disconnectphoto=Foto trennen undo.correlate=Fotos korrelieren +undo.createpoint=Punkt kreieren # Error messages error.save.dialogtitle=Fehler beim Speichern @@ -370,6 +420,6 @@ error.function.notavailable.title=Funktion nicht verf error.function.nojava3d=Diese Funktion braucht den Java3d Library,\nvon Sun.com erhältlich. error.3d.title=Fehler mit 3d Darstellung error.3d=Ein Fehler ist mit der 3d Darstellung aufgetreten -error.readme.notfound=Lies mich Datei nicht gefunden +error.readme.notfound=Liesmich Datei nicht gefunden error.osmimage.dialogtitle=Laden von Karten-Bilder fehlgeschlagen error.osmimage.failed=Laden von Karten-Bilder fehlgeschlagen. Bitte prüfen Sie die Internet-Verbindung. diff --git a/tim/prune/lang/prune-texts_de_CH.properties b/tim/prune/lang/prune-texts_de_CH.properties index 5b0c378..acd3c22 100644 --- a/tim/prune/lang/prune-texts_de_CH.properties +++ b/tim/prune/lang/prune-texts_de_CH.properties @@ -3,7 +3,8 @@ # Menu entries menu.file=Datei -menu.file.open=Öffne +menu.file.open=File öffne +menu.file.loadfromgps=uusem GPS lade menu.file.addphotos=Fötelis innätue menu.file.save=Speichere menu.file.exportkml=KML exportiere @@ -16,16 +17,18 @@ menu.edit.clearundo=Undo-Liste l menu.edit.editpoint=Punkt editiere menu.edit.editwaypointname=Waypoint Name editiere menu.edit.deletepoint=Punkt lösche -menu.edit.deleterange=Spanne lösche +menu.edit.deleterange=Beriich lösche menu.edit.deleteduplicates=Doppeldate lösche menu.edit.compress=Date komprimiere menu.edit.interpolate=Interpoliere -menu.edit.reverse=Spanne umdrähie +menu.edit.reverse=Beriich umdrähie +menu.edit.addtimeoffset=Ziitverschiebig zutue menu.edit.mergetracksegments=Track Segmänte merge menu.edit.rearrange=Waypoints reorganisiere menu.edit.rearrange.start=Alli zum Aafang menu.edit.rearrange.end=Alli zum Ände menu.edit.rearrange.nearest=Jede zum nöchsti Trackpunkt +menu.edit.cutandmove=Schniide und move menu.select=Selektiere menu.select.all=Alles selektiere menu.select.none=Nüüt selektiere @@ -39,18 +42,20 @@ menu.photo.correlate=Alli F menu.photo.delete=Föteli entfernä menu.view=Aasicht menu.view.show3d=In drüü-D zeigä -menu.view.showmap=Kate zeigä menu.view.browser=Karte inem Browser menu.view.browser.google=Google maps menu.view.browser.openstreetmap=Openstreetmap menu.help=Hilfe menu.help.about=Über Prune +menu.help.checkversion=Pruef nach ne noie Version # Popup menu for map menu.map.zoomin=Einzoome menu.map.zoomout=Uuszoome menu.map.zoomfull=Zoome zum ganzes Bild +menu.map.newpoint=Noii Punkt menu.map.connect=Trackpünktli verbindä menu.map.autopan=Autopan +menu.map.showmap=Kate zeigä # Dialogs dialog.exit.confirm.title=Prune beände @@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extrakt vom File dialog.openoptions.altitudeunits=Höchi Masseiheite dialog.jpegload.subdirectories=Subordnern au dialog.jpegload.loadjpegswithoutcoords=Au Fötelis ohni Koordinate +dialog.jpegload.loadjpegsoutsidearea=Au Fötelis uuserhalb vonem Track dialog.jpegload.progress.title=Fötelis lade dialog.jpegload.progress=Bitte warte während die Fötelis durägsucht werde +dialog.gpsload.title=Lade uusem GPS +dialog.gpsload.nogpsbabel=Kei gpsbabel Programm gfunde. Wiiter? +dialog.gpsload.device=Device Name +dialog.gpsload.format=Format +dialog.gpsload.getwaypoints=Waypoints lade +dialog.gpsload.gettracks=Tracks lade dialog.saveoptions.title=File speicherä dialog.save.fieldstosave=Fälder zu speicherä dialog.save.table.field=Fäld @@ -102,21 +114,24 @@ dialog.exportkml.text=Titel f dialog.exportkml.altitude=Au Höchiinformation (fürs Fliege) dialog.exportkml.kmz=Date ins kmz File komprimierä dialog.exportkml.exportimages=Bildli ins Kmz exportierä -dialog.exportkml.filetype=KML, KMZ Dateie dialog.exportgpx.title=GPX exportierä dialog.exportgpx.name=Name dialog.exportgpx.desc=Beschriibig -dialog.exportgpx.filetype=GPX Dateie +dialog.exportgpx.includetimestamps=Au Ziitstämpel dialog.exportpov.title=POV exportierä dialog.exportpov.text=Gäbet Sie die Parameter ii fürs POV Export dialog.exportpov.font=Font dialog.exportpov.camerax=Kamera X dialog.exportpov.cameray=Kamera Y dialog.exportpov.cameraz=Kamera Z -dialog.exportpov.filetype=POV Dateie +dialog.exportpov.modelstyle=Modellstil +dialog.exportpov.ballsandsticks=Bälle und Schtange +dialog.exportpov.tubesandwalls=Röhre und Wände dialog.exportpov.warningtracksize=Dieser Track hät mega viele Punkte, die Java3D villiicht nöd chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze? dialog.confirmreversetrack.title=Umdrehig bestätige -dialog.confirmreversetrack.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Umkehrig usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend diese Spanne umkehre? +dialog.confirmreversetrack.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Umkehrig usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend dn Beriich umkehre? +dialog.confirmcutandmove.title=Move bestätige +dialog.confirmcutandmove.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Move usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend dn Beriich move? dialog.interpolate.title=Punkte interpoliere dialog.interpolate.parameter.text=Aazahl Punkte zum innätue zwüschet den selektierten Punkten dialog.undo.title=Undo Operation(e) @@ -126,7 +141,7 @@ dialog.undo.none.text=Keini Operatione k dialog.clearundo.title=Undo-Liste löschä dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste lösche?\nAlle Undo Infos werdet verlore gah! dialog.pointedit.title=Punkt editierä -dialog.pointedit.text=Wählet Sie jäden Fäld uus zu editiere, und mitem 'Editiere' Chnopf den Wert ändere +dialog.pointedit.text=Wählet Sie jäden Fäld uus zu editiere, und mitem 'Editierä' Chnopf den Wert ändere dialog.pointedit.table.field=Fäld dialog.pointedit.table.value=Wert dialog.pointedit.table.changed=Geändert @@ -136,7 +151,16 @@ dialog.pointnameedit.title=Waypoint Name editiere dialog.pointnameedit.name=Waypoint Name dialog.pointnameedit.uppercase=GROSS gschriebe dialog.pointnameedit.lowercase=chli gschriebe -dialog.pointnameedit.sentencecase=Gmischt gschriebe +dialog.pointnameedit.sentencecase=Gmischt Gschriebe +dialog.addtimeoffset.title=Ziitverschiebig zutue +dialog.addtimeoffset.add=Ziit zutue +dialog.addtimeoffset.subtract=Ziit davo neh +dialog.addtimeoffset.days=Tage +dialog.addtimeoffset.hours=Schtunde +dialog.addtimeoffset.minutes=Minute +dialog.addtimeoffset.notimestamps=Ziitverschiebig nöd möglech wil dr Beriich kei Ziitinfo hät +dialog.connect.title=Föteli verknüpfe +dialog.connectphoto.clonepoint=Derer Punkt hät scho s Föteli.\nWend sie ne Kopie vonem Punkt machä? dialog.saveexif.title=Exif go speicherä dialog.saveexif.intro=Wählet Sie die Fötelis uus zum speicherä dialog.saveexif.nothingtosave=Koordinaten sin nöd geänderet, nüüt zum speicherä @@ -171,14 +195,14 @@ dialog.correlate.options.nodistancelimit=Kei Distanzgr dialog.correlate.options.distancelimit=Distanzgränzä dialog.correlate.options.correlate=Korrelierä dialog.correlate.alloutsiderange=Alli Fötelis sin uusserhalb vonem Track Ziitruum, so chönne nöd korreliert werdä.\nVersuechet Sie mitenem anderen Offset oder verbindet Sie manuell mindeschtens eis Föteli. -dialog.map.title=Prune Karte dialog.help.help=Bitte lueg na\n http://activityworkshop.net/software/prune/\nfür wiitere Information und Benutzeraaleitige. dialog.about.title=Über Prune dialog.about.version=Version dialog.about.build=Build -dialog.about.summarytext1=Prune isch n Programm fürs Lade, Darstelle und Editiere vo Date von GPS Geräte. -dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verfüegig gstellt,für frei, gratis und offen Gebruuch und Wiiterentwicklig.
Kopiere, Wiiterverbreitig und Veränderige sin erlaubt und willkommen
unter die Bedingunge im enthaltene license.txt File. -dialog.about.summarytext3=Bitte lueg na http://activityworkshop.net/ 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.
Kopiere, Wiiterverbreitig und Veränderige sin erlaubt und willkomme
unter die Bedingige im enthaltene license.txt File. +dialog.about.summarytext3=Bitte lueget Sie na http://activityworkshop.net/ für wiitere Informatione und Benutzeraaleitige. +dialog.about.languages=Verfüegbare Sproche dialog.about.translatedby=Schwiizerdüütschi Übersetzig vo activityworkshop. dialog.about.systeminfo=Syschtem Info dialog.about.systeminfo.os=Betriebsyschtem @@ -192,13 +216,21 @@ dialog.about.no=Nei dialog.about.credits=Credits dialog.about.credits.code=Prune Code gschriebä vo dialog.about.credits.exifcode=Exif Code vo -dialog.about.credits.icons=Einigi Ikons vo +dialog.about.credits.icons=Einigi Bilder vo dialog.about.credits.translators=Dolmätscher dialog.about.credits.translations=Übersetzige mit dr Hilfe vo dialog.about.credits.devtools=Entwicklungswärkzüüge dialog.about.credits.othertools=Anderi Wärkzüüge dialog.about.credits.thanks=Danke an dialog.about.readme=Läsmi +dialog.checkversion.title=Pruef d'Version +dialog.checkversion.error=Die Versionnummer könne nöd gefprüft werdä.\nGits ne internet Verbindig? +dialog.checkversion.uptodate=Sie han die noischti Version vonem Prune scho. +dialog.checkversion.newversion1=Ne noii Version vonem Prune isch jetzt usse! Die heisst jetzt Version +dialog.checkversion.newversion2=. +dialog.checkversion.releasedate1=Die noii Version isch am +dialog.checkversion.releasedate2=ussecho. +dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na http://activityworkshop.net/software/prune/download.html. # 3d window dialog.3d.title=Prune Drüü-d Aasicht @@ -217,7 +249,10 @@ confirm.deletepoint.single=Punkt isch entfernt worde confirm.deletepoint.multi=Punkte sin entfernt worde confirm.point.edit=Punkt editiert confirm.mergetracksegments=Segmänte gmerged -confirm.reverserange=Spanne umgdrähet +confirm.reverserange=Beriich umgdrähet +confirm.addtimeoffset=Ziitverschiebig zutue +confirm.rearrangewaypoints=Waypoints umorganisiert +confirm.cutandmove=Beriich gmoved confirm.saveexif.ok1=Es sin confirm.saveexif.ok2=Fötelis gschriebe worde confirm.undo.single=Operation rückgängig gmacht worde. @@ -228,6 +263,7 @@ confirm.photo.connect=F confirm.photo.disconnect=Föteli gtrännt confirm.correlate.single=Föteli isch korreliert worde confirm.correlate.multi=Fötelis sin korreliert worde +confirm.createpoint=Punkt kreiert worde # Buttons button.ok=OK @@ -251,6 +287,15 @@ button.selectall=Alli selektier button.selectnone=Nüüt selektierä button.preview=Vorschauä button.guessfields=Fälde erratä +button.showwebpage=Websiite aazeigä + +# File types +filetype.txt=TXT Dateie +filetype.jpeg=JPG Dateie +filetype.kmlkmz=KML, KMZ Dateie +filetype.kml=KML Dateie +filetype.gpx=GPX Dateie +filetype.pov=POV Dateie # Display components display.nodata=Kei Date glade worde @@ -267,7 +312,7 @@ details.nopointselection=N details.speed=Gschwindikeit details.photofile=Föteli Datei details.norangeselection=Nüüt selektiert -details.rangedetails=Details vo dr Spanne +details.rangedetails=Details vonem Beriich details.range.selected=Selektiert details.range.to=bis details.altitude.to=bis @@ -275,11 +320,12 @@ details.range.climb=Uufstieg details.range.descent=Abstieg details.coordformat=Koordinatenformat details.distanceunits=Distanz Masseinheite -display.range.time.secs=S -display.range.time.mins=M +display.range.time.secs=Sek +display.range.time.mins=Min display.range.time.hours=Std display.range.time.days=T details.range.avespeed=Gschwindikeit +details.range.avemovingspeed=Gschwindikeit ufem Wäg details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Fötelis details.photodetails=Details vom Föteli @@ -298,21 +344,22 @@ fieldname.newsegment=Segm fieldname.custom=Custom fieldname.prefix=Fäld fieldname.distance=Längi +fieldname.movingdistance=Weglängi fieldname.duration=Ziitlängi # Measurement units units.original=Original units.default=Default units.metres=Meter -units.metres.short=M +units.metres.short=m units.feet=Fuess -units.feet.short=F +units.feet.short=ft units.kilometres=Kilometer -units.kilometres.short=Km -units.kmh=Km/h +units.kilometres.short=km +units.kmh=km/h units.miles=Meile -units.miles.short=Mei -units.mph=Mei/Std +units.miles.short=mi +units.mph=mi/Std units.degminsec=Grad-Min-Sek units.degmin=Grad-Min units.deg=Grad @@ -333,16 +380,19 @@ undo.loadphotos=F undo.editpoint=Punkt editierä undo.deletepoint=Punkt löschä undo.deletephoto=Föteli entfärnä -undo.deleterange=Spanne löschä +undo.deleterange=Beriich löschä undo.compress=Track komprimierä undo.insert=Punkte innätuä undo.deleteduplicates=Duplikaten löschä -undo.reverse=Spanne umdrähie +undo.reverse=Beriich umdrähie undo.mergetracksegments=track segmänte merge +undo.addtimeoffset=ziitverschiebig zutue undo.rearrangewaypoints=Waypoints reorganisierä +undo.cutandmove=Selektion movä undo.connectphoto=Föteli verbindä undo.disconnectphoto=Föteli trännä undo.correlate=Fötelis korrelierä +undo.createpoint=Punkt kreierä # Error messages error.save.dialogtitle=Fähle bim Speichere diff --git a/tim/prune/lang/prune-texts_es.properties b/tim/prune/lang/prune-texts_es.properties index 1912603..cfa1a09 100644 --- a/tim/prune/lang/prune-texts_es.properties +++ b/tim/prune/lang/prune-texts_es.properties @@ -3,8 +3,9 @@ # Menu entries menu.file=Archivo -menu.file.open=Abrir +menu.file.open=Abrir archivo menu.file.addphotos=Cargar fotos +menu.file.loadfromgps=Cargar datos del GPS menu.file.save=Guardar menu.file.exportkml=Exportar KML menu.file.exportgpx=Exportar GPX @@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Eliminar duplicados menu.edit.compress=Comprimir track menu.edit.interpolate=Interpolar menu.edit.reverse=Invertir rango +menu.edit.addtimeoffset= menu.edit.mergetracksegments=Unir los segmentos de track menu.edit.rearrange=Reorganizar waypoints menu.edit.rearrange.start=Volver al comienzo menu.edit.rearrange.end=Ir al final menu.edit.rearrange.nearest=Ir al más próximo +menu.edit.cutandmove= menu.select=Seleccionar menu.select.all=Seleccionar todo menu.select.none=No seleccionar nada @@ -39,18 +42,20 @@ menu.photo.correlate=Correlacionar todas las fotos menu.photo.delete=Eliminar foto menu.view=Ver menu.view.show3d=Mostrar en 3-D -menu.view.showmap=Mostrar el mapa menu.view.browser=Mapa en un navegador menu.view.browser.google=Google maps menu.view.browser.openstreetmap=Openstreetmap menu.help=Ayuda menu.help.about=Acerca de Prune +menu.help.checkversion=Buscar una nueva versión # Popup menu for map menu.map.zoomin=Ampliar zoom menu.map.zoomout=Reducir zoom menu.map.zoomfull=Mostrar todo +menu.map.newpoint=Crear uno punto nuevo menu.map.connect=Conectar puntos de track menu.map.autopan=Posicionar automáticamente +menu.map.showmap=Mostrar el mapa # Dialogs dialog.exit.confirm.title=Salir de Prune @@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extraer archivo dialog.openoptions.altitudeunits=Unidades altitud dialog.jpegload.subdirectories=Incluir subdirectorios dialog.jpegload.loadjpegswithoutcoords=Fotos sin coordenadas tambien +dialog.jpegload.loadjpegsoutsidearea= dialog.jpegload.progress.title=Cargando fotos dialog.jpegload.progress=Por favor espere mientras se buscan las fotos +dialog.gpsload.title= +dialog.gpsload.nogpsbabel=gpsbabel program no encontrado. Desea continuar? +dialog.gpsload.device= +dialog.gpsload.format= +dialog.gpsload.getwaypoints= +dialog.gpsload.gettracks= dialog.saveoptions.title=Guardar archivo dialog.save.fieldstosave=Campos a guardar dialog.save.table.field=Campo @@ -102,21 +114,24 @@ dialog.exportkml.text=Descripci dialog.exportkml.altitude=Incluir altitudes (para aviación) dialog.exportkml.kmz=Comprimir al archivo kmz dialog.exportkml.exportimages=Exportar fotos al kmz -dialog.exportkml.filetype=Archivos KML, KMZ dialog.exportgpx.title=Exportar GPX dialog.exportgpx.name=Nombre dialog.exportgpx.desc=Descripción -dialog.exportgpx.filetype=Archivos GPX +dialog.exportgpx.includetimestamps=Tiempo tambien dialog.exportpov.title=Exportar POV dialog.exportpov.text=Introdzca los Parametros para exportar dialog.exportpov.font=Fuente dialog.exportpov.camerax=Cámara X dialog.exportpov.cameray=Cámara Y dialog.exportpov.cameraz=Cámara Z -dialog.exportpov.filetype=Archivos POV +dialog.exportpov.modelstyle= +dialog.exportpov.ballsandsticks= +dialog.exportpov.tubesandwalls= dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Está seguro de que desea continuar? dialog.confirmreversetrack.title=Confirmar inversión dialog.confirmreversetrack.text=Este track contiene información sobre la fecha, que estará fuera de secuencia después de la inversión. Esta seguro que desea invertir esta sección? +dialog.confirmcutandmove.title=Confirmar ... +dialog.confirmcutandmove.text=Este track contiene información sobre la fecha, que estará fuera de secuencia después de la .... Esta seguro que desea ... esta sección? dialog.interpolate.title=Interpolar puntos dialog.interpolate.parameter.text=Número de los puntos a insertar entre los puntos elegidos dialog.undo.title=Deshacer @@ -137,6 +152,15 @@ dialog.pointnameedit.name=Nombre de waypoint dialog.pointnameedit.uppercase=Mayúsculas dialog.pointnameedit.lowercase=minúsculas dialog.pointnameedit.sentencecase=Mezcla +dialog.addtimeoffset.title= +dialog.addtimeoffset.add= +dialog.addtimeoffset.subtract= +dialog.addtimeoffset.days=Dias +dialog.addtimeoffset.hours=Horas +dialog.addtimeoffset.minutes=Minutos +dialog.addtimeoffset.notimestamps= +dialog.connect.title=Conectar foto +dialog.connectphoto.clonepoint= dialog.saveexif.title=Guardar Exif dialog.saveexif.intro=Seleccione fotos a guardar dialog.saveexif.nothingtosave=Coordenadas no modificadas, nada que guardar @@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Sin l dialog.correlate.options.distancelimit=Límite de distancia dialog.correlate.options.correlate=Correlacionar dialog.correlate.alloutsiderange=Todas las fotos están fuera del margen horario del track, por lo que ninguna puede ser correlada.\nIntente cambiar el margen o correle manualmente al menos una foto. -dialog.map.title=Mapa Prune dialog.help.help=Por favor, ver\n http://activityworkshop.net/software/prune/\npara más información y guías del usuario. dialog.about.title=Acerca de Prune dialog.about.version=Versión @@ -179,6 +202,7 @@ dialog.about.build=Construir dialog.about.summarytext1=Prune es un programa para cargar, mostrar y editar datos de receptores GPS. dialog.about.summarytext2=Distribuido bajo el GNU GPL para uso libre y gratuito.
Se permite (y se anima) la copia, redistribución y modificación de acuerdo
a las condiciones incluidas en el archivo licence.txt. dialog.about.summarytext3=Por favor, ver http://activityworkshop.net/ para más información y guías del usuario. +dialog.about.languages= dialog.about.translatedby=Traducción al español realizada por activityworkshop y amigos muy amables! dialog.about.systeminfo=Informacion del sistema dialog.about.systeminfo.os=Sistema operativo @@ -199,6 +223,14 @@ dialog.about.credits.devtools=Herramientas de desarrollo dialog.about.credits.othertools=Otras herramientas dialog.about.credits.thanks=Gracias a dialog.about.readme=Readme +dialog.checkversion.title= +dialog.checkversion.error= +dialog.checkversion.uptodate= +dialog.checkversion.newversion1= +dialog.checkversion.newversion2= +dialog.checkversion.releasedate1= +dialog.checkversion.releasedate2= +dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html. # 3d window dialog.3d.title=Prune vista 3-D @@ -218,6 +250,9 @@ confirm.deletepoint.multi=puntos eliminados confirm.point.edit=Punto editado confirm.mergetracksegments=Segmentos unidos confirm.reverserange=Rango invertido +confirm.addtimeoffset= +confirm.rearrangewaypoints=Waypoints reorganizados +confirm.cutandmove= confirm.saveexif.ok1=Guardando confirm.saveexif.ok2=fotos confirm.undo.single=operación no realizada @@ -228,6 +263,7 @@ confirm.photo.connect=Foto conectado confirm.photo.disconnect=Foto desconectado confirm.correlate.single=foto fue correlada confirm.correlate.multi=fotos fueron correladas +confirm.createpoint= # Buttons button.ok=Aceptar @@ -251,6 +287,15 @@ button.selectall=Seleccionar todo button.selectnone=Seleccionar nada button.preview=Previsualización button.guessfields=Adivinar campos +button.showwebpage= + +# File types +filetype.txt=Archivos TXT +filetype.jpeg=Archivos JPG +filetype.kmlkmz=Archivos KML, KMZ +filetype.kml=Archivos KML +filetype.gpx=Archivos GPX +filetype.pov=Archivos POV # Display components display.nodata=Ningún dato cargado @@ -280,6 +325,7 @@ display.range.time.mins=m display.range.time.hours=h display.range.time.days=d details.range.avespeed=Velocidad media +details.range.avemovingspeed= details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Fotos details.photodetails=Detalles del Foto @@ -298,6 +344,7 @@ fieldname.newsegment=Segmento fieldname.custom=Personalizado fieldname.prefix=Campo fieldname.distance=Distancia +fieldname.movingdistance= fieldname.duration=Duración # Measurement units @@ -339,10 +386,13 @@ undo.insert=insertar puntos undo.deleteduplicates=eliminar duplicados undo.reverse=invertir rango undo.mergetracksegments=unir los segmentos de track +undo.addtimeoffset= undo.rearrangewaypoints=reordenar waypoints +undo.cutandmove= undo.connectphoto=conectar foto undo.disconnectphoto=desconectar foto undo.correlate=correlacionar fotos +undo.createpoint=crear punto # Error messages error.save.dialogtitle=Fallo al guardar datos diff --git a/tim/prune/lang/prune-texts_fr.properties b/tim/prune/lang/prune-texts_fr.properties index 1906ea7..0926a55 100644 --- a/tim/prune/lang/prune-texts_fr.properties +++ b/tim/prune/lang/prune-texts_fr.properties @@ -3,8 +3,9 @@ # Menu entries menu.file=Fichier -menu.file.open=Ouvrir +menu.file.open=Ouvrir fichier menu.file.addphotos=Ouvrir photos +menu.file.loadfromgps=Charger à partir du GPS menu.file.save=Enregistrer menu.file.exportkml=Exporter en KML menu.file.exportgpx=Exporter en GPX @@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Supprimer les doublons menu.edit.compress=Compacter la trace menu.edit.interpolate=Interpoler menu.edit.reverse=Inverser l'étendue +menu.edit.addtimeoffset= menu.edit.mergetracksegments=Fusionner les segments de trace menu.edit.rearrange=Réarranger les waypoints menu.edit.rearrange.start=Tous au début du fichier menu.edit.rearrange.end=Tous à la fin du fichier menu.edit.rearrange.nearest=Chacun au point de trace le plus proche +menu.edit.cutandmove= menu.select=Sélectionner menu.select.all=Tout sélectionner menu.select.none=Rien sélectionner @@ -39,18 +42,20 @@ menu.photo.correlate=Corr menu.photo.delete=Retirer la photo menu.view=Affichage menu.view.show3d=Montrer en 3D -menu.view.showmap=Montrer la carte menu.view.browser=Ouvrir la carte dans le navigateur menu.view.browser.google=Google maps menu.view.browser.openstreetmap=Openstreetmap menu.help=Aide menu.help.about=À propos de Prune +menu.help.checkversion= # Popup menu for map menu.map.zoomin=Zoom avant menu.map.zoomout=Zoom arrière menu.map.zoomfull=Adapter à la vue +menu.map.newpoint= menu.map.connect=Relier les points de trace menu.map.autopan=Déplacement automatique +menu.map.showmap=Montrer la carte # Dialogs dialog.exit.confirm.title=Quitter Prune @@ -84,8 +89,15 @@ dialog.openoptions.tabledesc=Extrait de fichier dialog.openoptions.altitudeunits=Unités d'altitude dialog.jpegload.subdirectories=Inclure les sous-dossiers dialog.jpegload.loadjpegswithoutcoords=Inclure les photos sans coordonnées +dialog.jpegload.loadjpegsoutsidearea= dialog.jpegload.progress.title=Chargement des photos dialog.jpegload.progress=Veuillez patienter pendant la recherche des photos +dialog.gpsload.title= +dialog.gpsload.nogpsbabel=Gpsbabel introuvable. Continuer ? +dialog.gpsload.device= +dialog.gpsload.format= +dialog.gpsload.getwaypoints= +dialog.gpsload.gettracks= dialog.saveoptions.title=Enregistrer le fichier dialog.save.fieldstosave=Champs à enregistrer dialog.save.table.field=Champ @@ -102,21 +114,24 @@ dialog.exportkml.text=Titre pour les donn dialog.exportkml.altitude=Inclure les altitudes (pour aviation) dialog.exportkml.kmz=Compresser au format kmz dialog.exportkml.exportimages=Exporter les vignettes au format kmz -dialog.exportkml.filetype=Fichiers KML, KMZ dialog.exportgpx.title=Exporter en GPX dialog.exportgpx.name=Nom dialog.exportgpx.desc=Légende -dialog.exportgpx.filetype=Fichiers GPX +dialog.exportgpx.includetimestamps= dialog.exportpov.title=Exporter en POV dialog.exportpov.text=Entrez les paramètres pour l'export POV dialog.exportpov.font=Police dialog.exportpov.camerax=Camera X dialog.exportpov.cameray=Camera Y dialog.exportpov.cameraz=Camera Z -dialog.exportpov.filetype=Fichiers POV +dialog.exportpov.modelstyle= +dialog.exportpov.ballsandsticks= +dialog.exportpov.tubesandwalls= dialog.exportpov.warningtracksize=Cette trace possède un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\nEtes-vous sûr de vouloir continuer ? dialog.confirmreversetrack.title=Confirmer l'inversion dialog.confirmreversetrack.text=Cette trace contient des informations temporelles qui seront désordonnées après une inversion.\nEtes-vous sûr de vouloir inverser cette section ? +dialog.confirmcutandmove.title=Confirmer ... +dialog.confirmcutandmove.text=Cette trace contient des informations temporelles qui seront désordonnées après une ....\nEtes-vous sûr de vouloir ... cette section ? dialog.interpolate.title=Interpoler les points dialog.interpolate.parameter.text=Nombre de points à insérer entre les points sélectionnés dialog.undo.title=Annuler les actions @@ -137,6 +152,15 @@ dialog.pointnameedit.name=Nom de waypoint dialog.pointnameedit.uppercase=CASSE MAJUSCULES dialog.pointnameedit.lowercase=casse minuscules dialog.pointnameedit.sentencecase=Casse Phrase +dialog.addtimeoffset.title= +dialog.addtimeoffset.add= +dialog.addtimeoffset.subtract= +dialog.addtimeoffset.days=Jours +dialog.addtimeoffset.hours=Heures +dialog.addtimeoffset.minutes=Minutes +dialog.addtimeoffset.notimestamps= +dialog.connect.title= +dialog.connectphoto.clonepoint= dialog.saveexif.title=Enregistrer Exif dialog.saveexif.intro=Sélectionner les photos à sauver à l'aide des cases à cocher dialog.saveexif.nothingtosave=Coordonnées inchangées, rien à enregistrer @@ -171,7 +195,6 @@ dialog.correlate.options.nodistancelimit=Pas de limite de distance dialog.correlate.options.distancelimit=Limite de distance dialog.correlate.options.correlate=Corréler dialog.correlate.alloutsiderange=Les photos ne correspondent pas à la plage de temps de la trace, aucune ne peut être corrélée.\nEssayez de modifier le décalage ou de corréler manuellement au moins une photo. -dialog.map.title=Prune carte dialog.help.help=Consultez la page\n http://activityworkshop.net/software/prune/\npour plus de détails et des manuels utilisateur. dialog.about.title=À propos de Prune dialog.about.version=Version @@ -179,6 +202,7 @@ dialog.about.build=Build dialog.about.summarytext1=Prune est un programme pour charger, afficher et éditer des données de récepteurs GPS. dialog.about.summarytext2=Distribué sous license Gnu GPL pour un usage et une amélioration libres, ouverts et mondiaux.
La copie, la redistribution et la modification sont autorisées et encouragées
selon les conditions détaillées dans le fichier license.txt inclus. dialog.about.summarytext3=Consultez la page http://activityworkshop.net/ pour plus de détails et des manuels utilisateur. +dialog.about.languages= dialog.about.translatedby=Texte en français par Petrovsk. dialog.about.systeminfo=Info Système dialog.about.systeminfo.os=Système d'exploitation @@ -199,6 +223,14 @@ dialog.about.credits.devtools=Outils de d dialog.about.credits.othertools=Autre outils dialog.about.credits.thanks=Merci à dialog.about.readme=Lisez-moi +dialog.checkversion.title= +dialog.checkversion.error= +dialog.checkversion.uptodate= +dialog.checkversion.newversion1= +dialog.checkversion.newversion2= +dialog.checkversion.releasedate1= +dialog.checkversion.releasedate2= +dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html. # 3d window dialog.3d.title=Vue 3D de Prune @@ -218,6 +250,9 @@ confirm.deletepoint.multi=points ont confirm.point.edit=point édité confirm.mergetracksegments=Segments de trace ont été fusionné confirm.reverserange=Etendue inversée +confirm.addtimeoffset= +confirm.rearrangewaypoints= +confirm.cutandmove= confirm.saveexif.ok1=Enregistrement de confirm.saveexif.ok2=fichiers photo confirm.undo.single=opération annulée @@ -228,6 +263,7 @@ confirm.photo.connect=photo reli confirm.photo.disconnect=photo détachée confirm.correlate.single=photo a été corrélée confirm.correlate.multi=photos ont été corrélées +confirm.createpoint= # Buttons button.ok=OK @@ -251,6 +287,15 @@ button.selectall=Tout s button.selectnone=Ne rien sélectionner button.preview=Aperçu button.guessfields=Deviner les champs +button.showwebpage= + +# File types +filetype.txt=Fichiers TXT +filetype.jpeg=Fichiers JPG +filetype.kmlkmz=Fichiers KML, KMZ +filetype.kml=Fichiers KML +filetype.gpx=Fichiers GPX +filetype.pov=Fichiers POV # Display components display.nodata=Pas de données chargées @@ -280,6 +325,7 @@ display.range.time.mins=m display.range.time.hours=h display.range.time.days=j details.range.avespeed=Vitesse moyenne +details.range.avemovingspeed= details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Photos details.photodetails=Détails de la photo @@ -298,6 +344,7 @@ fieldname.newsegment=Segment fieldname.custom=Personnalisé fieldname.prefix=Champ fieldname.distance=Distance +fieldname.movingdistance= fieldname.duration=Durée # Measurement units @@ -339,10 +386,13 @@ undo.insert=ins undo.deleteduplicates=effacer les doublons undo.reverse=inverser l'étendue undo.mergetracksegments=fusionner les segments de trace +undo.addtimeoffset= undo.rearrangewaypoints=réarranger les waypoints +undo.cutandmove= undo.connectphoto=relier la photo undo.disconnectphoto=détacher la photo undo.correlate=corréler les photos +undo.createpoint= # Error messages error.save.dialogtitle=Erreur à l'enregistrement des données diff --git a/tim/prune/lang/prune-texts_it.properties b/tim/prune/lang/prune-texts_it.properties new file mode 100644 index 0000000..8c4e780 --- /dev/null +++ b/tim/prune/lang/prune-texts_it.properties @@ -0,0 +1,425 @@ +# Text entries for the Prune application +# Italian entries as extra + +# Menu entries +menu.file=File +menu.file.open=Apri file +menu.file.addphotos=Aggiungi foto +menu.file.loadfromgps=Carica dati da GPS +menu.file.save=Salva +menu.file.exportkml=Esporta in KML +menu.file.exportgpx=Esporta in GPX +menu.file.exportpov=Esporta in POV +menu.file.exit=Esci +menu.edit=Edita +menu.edit.undo=Annulla +menu.edit.clearundo=Cancella la lista annulla +menu.edit.editpoint=Edita punto +menu.edit.editwaypointname=Edita nome waypoint +menu.edit.deletepoint=Cancella punto +menu.edit.deleterange=Cancella la serie +menu.edit.deleteduplicates=Cancella duplicati +menu.edit.compress=Comprimi traccia +menu.edit.interpolate=Interpola +menu.edit.reverse=Inverti la serie +menu.edit.addtimeoffset=Imposta lo scarto di orario +menu.edit.mergetracksegments=Unisci segmenti traccia +menu.edit.rearrange=Riorganizza waypoint +menu.edit.rearrange.start=Tutti all'inizio del file +menu.edit.rearrange.end=Tutti alla fine del file +menu.edit.rearrange.nearest=Sul punto più vicino +menu.edit.cutandmove=Taglia e muovi la selezione +menu.select=Seleziona +menu.select.all=Seleziona tutto +menu.select.none=Deseleziona tutto +menu.select.start=Imposta inizio serie +menu.select.end=Imposta fine serie +menu.photo=Foto +menu.photo.saveexif=Salva su Exif +menu.photo.connect=Collega al punto +menu.photo.disconnect=Scollega dal punto +menu.photo.correlate=Correla tutte le foto +menu.photo.delete=Rimuovi foto +menu.view=Visualizza +menu.view.show3d=Mostra in 3D +menu.view.browser=Mappa sul browser +menu.view.browser.google=Google maps +menu.view.browser.openstreetmap=Openstreetmap +menu.help=Aiuto +menu.help.about=Informazioni su Prune +menu.help.checkversion=Controlla gli aggiornamenti +# Popup menu for map +menu.map.zoomin=Zoom + +menu.map.zoomout=Zoom - +menu.map.zoomfull=Zoom tutto +menu.map.newpoint= +menu.map.connect=Aggancia ai punti +menu.map.autopan=Autopan +menu.map.showmap=Mostra sulla mappa + +# Dialogs +dialog.exit.confirm.title=Esci da Prune +dialog.exit.confirm.text=Le modifiche non sono state salvate. Sei sicuro di voler uscire? +dialog.openappend.title=Aggiungi ai dati esistenti +dialog.openappend.text=Aggiungi questi dati a quelli già caricati? +dialog.deletepoint.title=Cancella Punto +dialog.deletepoint.deletephoto=Cancella la foto collegata a questo punto? +dialog.deletephoto.title=Cancella Foto +dialog.deletephoto.deletepoint=Cancella il punto collegato a questa foto? +dialog.deleteduplicates.title=Cancella Duplicati +dialog.deleteduplicates.nonefound=Nessun punto duplicato trovato +dialog.compresstrack.title=Comprimi la traccia +dialog.compresstrack.parameter.text=Parametro di compressione (più basso il numero = maggiore la compressione) +dialog.compresstrack.nonefound=Nessun punto rimosso +dialog.openoptions.title=Apri opzioni +dialog.openoptions.filesnippet=Estrai dal file +dialog.load.table.field=Campo +dialog.load.table.datatype=Tipo di dato +dialog.load.table.description=Descrizione +dialog.delimiter.label=Delimitatore di campo +dialog.delimiter.comma=Virgola , +dialog.delimiter.tab=Tab +dialog.delimiter.space=Spazio +dialog.delimiter.semicolon=Punto e virgola ; +dialog.delimiter.other=Altro +dialog.openoptions.deliminfo.records=registra, con +dialog.openoptions.deliminfo.fields=campi +dialog.openoptions.deliminfo.norecords=Nessun record +dialog.openoptions.tabledesc=Estrai dal file +dialog.openoptions.altitudeunits=Unità di misura altitudine +dialog.jpegload.subdirectories=Includi sottocartelle +dialog.jpegload.loadjpegswithoutcoords=Includi foto senza coordinate +dialog.jpegload.loadjpegsoutsidearea=Includi foto fuori dall'area corrente +dialog.jpegload.progress.title=Caricamento foto +dialog.jpegload.progress=Per favore aspetta, sto cercando le foto +dialog.gpsload.title=Carica dati da GPS +dialog.gpsload.nogpsbabel=Non ho trovato il programma gpsbabel. Continuo? +dialog.gpsload.device=Nome del Dispositivo +dialog.gpsload.format=Formato +dialog.gpsload.getwaypoints=Carica waypoint +dialog.gpsload.gettracks=Carica tracce +dialog.saveoptions.title=Salva il file +dialog.save.fieldstosave=Campi da salvare +dialog.save.table.field=Campo +dialog.save.table.hasdata=Ha dati +dialog.save.table.save=Salva +dialog.save.headerrow=Regista l'intestazione delle righe +dialog.save.coordinateunits=Unità di misura coordinate +dialog.save.altitudeunits=Unità di misura altitudine +dialog.save.timestampformat=Formato della data +dialog.save.overwrite.title=File già esistente +dialog.save.overwrite.text=Questo file esiste già. Sei sicuro di volerlo sovrascrivere? +dialog.exportkml.title=Esporta in KML +dialog.exportkml.text=Titolo dei dati +dialog.exportkml.altitude=Includi altitudine (per aviazione) +dialog.exportkml.kmz=Comprimi per file kmz +dialog.exportkml.exportimages=Esporta le anteprime delle immagini per kmz +dialog.exportgpx.title=Esporta in GPX +dialog.exportgpx.name=Nome +dialog.exportgpx.desc=Descrizione +dialog.exportgpx.includetimestamps=Includi dati temporali +dialog.exportpov.title=Esporta in POV +dialog.exportpov.text=Per favore inserisci i parametri per l'esportazione in POV +dialog.exportpov.font=Font +dialog.exportpov.camerax=Camera X +dialog.exportpov.cameray=Camera Y +dialog.exportpov.cameraz=Camera Z +dialog.exportpov.modelstyle=Stile del modello +dialog.exportpov.ballsandsticks=Palle e bacchette +dialog.exportpov.tubesandwalls=Tubi e pareti +dialog.exportpov.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D può non essere in grado di visualizzarli.\nSei sicuro di voler continuare? +dialog.confirmreversetrack.title=Conferma l'inversione +dialog.confirmreversetrack.text=Questa traccia contiene informazioni sull'orario di scatto che possono essere messe fuori sequenza dopo l'inversione.\nSei sicuro di voler invertire questa sezione? +dialog.confirmcutandmove.title=Conferma il taglio e lo spostamento +dialog.confirmcutandmove.text=Questa traccia contiene informazioni sull'orario di scatto che possono essere messe fuori sequenza dopo lo spostamento\nSei sicuro di voler spostare questa sezione? +dialog.interpolate.title=Interpola i punti +dialog.interpolate.parameter.text=Numero di punti da inserire tra i punti selezionati +dialog.undo.title=Annulla l'azione(i) +dialog.undo.pretext=Per favore seleziona l'azione(i) da annullare +dialog.undo.none.title=Non è possibile annullare +dialog.undo.none.text=Nessuna operazione da annullare! +dialog.clearundo.title=Cancella la lista annulla +dialog.clearundo.text=Sei sicuro di voler cancellare la lista annulla?\nTutte le informazioni saranno perse! +dialog.pointedit.title=Edita il punto +dialog.pointedit.text=Seleziona ogni campo da editare e usa il pulsante 'Edita' per cambiare il valore +dialog.pointedit.table.field=Campo +dialog.pointedit.table.value=Valore +dialog.pointedit.table.changed=Cambiato +dialog.pointedit.changevalue.text=Inserisci il nuovo valore di questo campo +dialog.pointedit.changevalue.title=Edita il campo +dialog.pointnameedit.title=Edita il nome del waypoint +dialog.pointnameedit.name=Nome del waypoint +dialog.pointnameedit.uppercase=MAIUSCOLE +dialog.pointnameedit.lowercase=minuscole +dialog.pointnameedit.sentencecase=Iniziali Maiuscole +dialog.addtimeoffset.title=Aggiungi uno scarto temporale +dialog.addtimeoffset.add=Scarto in aggiunta +dialog.addtimeoffset.subtract=Scarto in sottrazione +dialog.addtimeoffset.days=Giorni +dialog.addtimeoffset.hours=Ore +dialog.addtimeoffset.minutes=Minuti +dialog.addtimeoffset.notimestamps=Non posso aggiungere uno scarto temporale a questa selezione perché non contiene informazioni temporali +dialog.connect.title=Collega la foto al punto +dialog.connectphoto.clonepoint=Questo punto ha già una foto collegata.\nVuoi fare una copia del punto? +dialog.saveexif.title=Salva Exif +dialog.saveexif.intro=Seleziona le foto da salvare usando le caselle di spunta +dialog.saveexif.nothingtosave=Le coordinate non sono cambiate, niente da registrare +dialog.saveexif.noexiftool=Non ho trovato il programma exiftool. Continuo? +dialog.saveexif.table.photoname=Nome della foto +dialog.saveexif.table.status=Stato +dialog.saveexif.table.save=Salva +dialog.saveexif.photostatus.connected=Collegata +dialog.saveexif.photostatus.disconnected=Scollegata +dialog.saveexif.photostatus.modified=Modificata +dialog.saveexif.overwrite=Sovrascrivi il file +dialog.correlate.title=Correla le foto +dialog.correlate.notimestamps=Non ci sono informazioni temporali tra i dati dei punti, non c'è niente per collegarli con le foto. +dialog.correlate.nouncorrelatedphotos=Non ci sono foto non correlate.\nSei sicuro di voler continuare? +dialog.correlate.photoselect.intro=Selezione una delle foto correlate da usare come scarto dell'orario +dialog.correlate.photoselect.photoname=Nome della foto +dialog.correlate.photoselect.timediff=Differenza di orario +dialog.correlate.photoselect.photolater=Foto scattata dopo il punto +dialog.correlate.options.tip=Consiglio: Con il collegamento manuale di almeno una foto, lo scarto di orario viene calcolato per te +dialog.correlate.options.intro=Selezione le opzioni per la correlazione automatica +dialog.correlate.options.offsetpanel=Scarto di orario +dialog.correlate.options.offset=Scarto +dialog.correlate.options.offset.hours=ore, +dialog.correlate.options.offset.minutes=minuti e +dialog.correlate.options.offset.seconds=secondi +dialog.correlate.options.photolater=Foto scattata dopo il punto +dialog.correlate.options.pointlater=Punto successivo allo scatto della foto +dialog.correlate.options.limitspanel=Limiti di correlamento +dialog.correlate.options.notimelimit=Nessun limite di tempo +dialog.correlate.options.timelimit=Limite di tempo +dialog.correlate.options.nodistancelimit=Nessun limite di distanza +dialog.correlate.options.distancelimit=Distanza limite +dialog.correlate.options.correlate=Correlate +dialog.correlate.alloutsiderange=Tutte le foto sono fuori dall'orario della traccia, e nessuna può essere correlata.\nProva a cambiare lo scarto o correla manualmente almeno una foto. +dialog.help.help=Per favore vedi\n http://activityworkshop.net/software/prune/\nper maggiori informazioni e per la guida utente. +dialog.about.title=Informazioni su Prune +dialog.about.version=Versione +dialog.about.build=Build +dialog.about.summarytext1=Prune è un programma per il caricamento, la visione e l'edit di dati provenienti da un GPS. +dialog.about.summarytext2=È rilasciato sotto la licenza Gnu GPL per l'uso gratuito e aperto ed il suo miglioramento, con validità mondiale.
La copia, la ridistribuzione sono permesse e incoraggiate
in accordo con i termini inclusi nel file license.txt. +dialog.about.summarytext3=Per favore vedi http://activityworkshop.net/ 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. diff --git a/tim/prune/lang/prune-texts_pl.properties b/tim/prune/lang/prune-texts_pl.properties index f3d97c3..a16c037 100644 --- a/tim/prune/lang/prune-texts_pl.properties +++ b/tim/prune/lang/prune-texts_pl.properties @@ -5,6 +5,7 @@ menu.file=Plik menu.file.open=Otw\u00F3rz menu.file.addphotos=Dodaj zdj\u0119cia +menu.file.loadfromgps= menu.file.save=Zapisz menu.file.exportkml=Eksportuj jako KML menu.file.exportgpx=Eksportuj jako GPX @@ -21,11 +22,13 @@ menu.edit.deleteduplicates=Usu\u0144 duplikaty menu.edit.compress=Skompresuj scie\u017Ck\u0119 menu.edit.interpolate=Interpoluj punkty menu.edit.reverse=Odwr\u00F3\u0107 zakres -menu.edit.mergetracksegments=Merge track segments +menu.edit.addtimeoffset= +menu.edit.mergetracksegments= menu.edit.rearrange=Zmie\u0144 kolejno\u015B\u0107 punkt\u00F3w po\u015Brednich menu.edit.rearrange.start=Wszystkie na pocz\u0105tek \u015Bcie\u017Cki menu.edit.rearrange.end=Wszystkie na koniec \u015Bcie\u017Cki menu.edit.rearrange.nearest=Do najbli\u017Cszego punktu +menu.edit.cutandmove= menu.select=Zakres menu.select.all=Zaznacz wszystko menu.select.none=Usu\u0144 zaznaczenie @@ -39,24 +42,26 @@ menu.photo.correlate=Skoreluj wszystkie zdj\u0119cia menu.photo.delete=Usu\u0144 zdj\u0119cie menu.view=Widok menu.view.show3d=Poka\u017C 3D model -menu.view.showmap=Show map -menu.view.browser=Map in browser -menu.view.browser.google=Google maps -menu.view.browser.openstreetmap=Openstreetmap +menu.view.browser= +menu.view.browser.google= +menu.view.browser.openstreetmap= menu.help=Pomoc menu.help.about=Prune - Informacje +menu.help.checkversion= # Popup menu for map menu.map.zoomin=Powi\u0119ksz menu.map.zoomout=Zmniejsz menu.map.zoomfull=Dostosuj powi\u0119kszenie +menu.map.newpoint= menu.map.connect=Po\u0142\u0105czenie track punkty menu.map.autopan=Przesuwanie mapy +menu.map.showmap= # Dialogs dialog.exit.confirm.title=Zako\u0144cz Prune -dialog.exit.confirm.text=Your data is not saved. Are you sure you want to exit? -dialog.openappend.title=Append to existing data -dialog.openappend.text=Append this data to the data already loaded? +dialog.exit.confirm.text= +dialog.openappend.title= +dialog.openappend.text= dialog.deletepoint.title=Usu\u0144 punkt dialog.deletepoint.deletephoto=Usu\u0144 zdj\u0119cie attached to this punkt? dialog.deletephoto.title=Usu\u0144 zdj\u0119cie @@ -64,7 +69,7 @@ dialog.deletephoto.deletepoint=Usu\u0144 punkt attached to this zdj\u0119cie? dialog.deleteduplicates.title=Usu\u0144 Duplicates dialog.deleteduplicates.nonefound=Brak duplikaty found dialog.compresstrack.title=Skompresuj scie\u017Ck\u0119 -dialog.compresstrack.parameter.text=Parameter for compression (lower number = more compression) +dialog.compresstrack.parameter.text= dialog.compresstrack.nonefound=No data punkty could be removed dialog.openoptions.title=Otw\u00F3rz opcje dialog.openoptions.filesnippet=Extract of plik @@ -77,75 +82,94 @@ dialog.delimiter.tab=Tabulator dialog.delimiter.space=Spacja dialog.delimiter.semicolon=\u015Arednik ; dialog.delimiter.other=Inne -dialog.openoptions.deliminfo.records=records, with +dialog.openoptions.deliminfo.records= dialog.openoptions.deliminfo.fields=pola -dialog.openoptions.deliminfo.norecords=No records +dialog.openoptions.deliminfo.norecords= dialog.openoptions.tabledesc=Extract of plik dialog.openoptions.altitudeunits=Wysoko\u015B\u0107 jednostki -dialog.jpegload.subdirectories=Include subdirectories +dialog.jpegload.subdirectories= dialog.jpegload.loadjpegswithoutcoords=Include zdj\u0119cia without koordinaty +dialog.jpegload.loadjpegsoutsidearea= dialog.jpegload.progress.title=Loading zdj\u0119cia dialog.jpegload.progress=Please wait while the zdj\u0119cia are searched +dialog.gpsload.title= +dialog.gpsload.nogpsbabel= +dialog.gpsload.device= +dialog.gpsload.format= +dialog.gpsload.getwaypoints= +dialog.gpsload.gettracks= dialog.saveoptions.title=Zapisz plik dialog.save.fieldstosave=Pola to save dialog.save.table.field=Pole -dialog.save.table.hasdata=Has data +dialog.save.table.hasdata= dialog.save.table.save=Zapisz -dialog.save.headerrow=Output header row +dialog.save.headerrow= dialog.save.coordinateunits=Wsp\u00f3\u0142rz\u0119dne jednostki dialog.save.altitudeunits=Wysoko\u015B\u0107 jednostki -dialog.save.timestampformat=Timestamp format +dialog.save.timestampformat= dialog.save.overwrite.title=Plik ju\u017C istnieje dialog.save.overwrite.text=This plik already exists. Are you sure you want to overwrite the plik? dialog.exportkml.title=Eksportuj KML dialog.exportkml.text=Tytu\u0142 for the data -dialog.exportkml.altitude=Include altitudes (for aviation) -dialog.exportkml.kmz=Compress to make kmz plik +dialog.exportkml.altitude= +dialog.exportkml.kmz= dialog.exportkml.exportimages=Eksportuj image thumbnails to kmz -dialog.exportkml.filetype=Pliki KML, KMZ dialog.exportgpx.title=Eksportuj jako GPX dialog.exportgpx.name=Nazwa dialog.exportgpx.desc=Opis -dialog.exportgpx.filetype=Pliki GPX +dialog.exportgpx.includetimestamps= dialog.exportpov.title=Eksportuj jako POV -dialog.exportpov.text=Please enter the parameters for the POV export +dialog.exportpov.text= dialog.exportpov.font=Czcionka dialog.exportpov.camerax=Camera X dialog.exportpov.cameray=Camera Y dialog.exportpov.cameraz=Camera Z -dialog.exportpov.filetype=Pliki POV +dialog.exportpov.modelstyle= +dialog.exportpov.ballsandsticks= +dialog.exportpov.tubesandwalls= dialog.exportpov.warningtracksize=This track has a large number of punkty, which Java3D might not be able to display.\nCzy chcesz kontynuowa\u0107? -dialog.confirmreversetrack.title=Confirm reversal -dialog.confirmreversetrack.text=This track contains timestamp information, which will be out of sequence after a reversal.\nAre you sure you want to reverse this section? +dialog.confirmreversetrack.title= +dialog.confirmreversetrack.text= +dialog.confirmcutandmove.title= +dialog.confirmcutandmove.text= dialog.interpolate.title=Interpoluj punkty dialog.interpolate.parameter.text=Number of punkty to insert between selected punkty dialog.undo.title=Cofnij action(s) -dialog.undo.pretext=Please select the action(s) to undo -dialog.undo.none.title=Cannot undo -dialog.undo.none.text=No operations to undo! +dialog.undo.pretext= +dialog.undo.none.title= +dialog.undo.none.text= dialog.clearundo.title=Wyczy\u015B\u0107 list\u0119 zmian -dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost! +dialog.clearundo.text= dialog.pointedit.title=Edytuj punkt -dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value +dialog.pointedit.text= dialog.pointedit.table.field=Pole -dialog.pointedit.table.value=Value +dialog.pointedit.table.value= dialog.pointedit.table.changed=Zmieniony -dialog.pointedit.changevalue.text=Enter the new value for this field +dialog.pointedit.changevalue.text= dialog.pointedit.changevalue.title=Edytuj field dialog.pointnameedit.title=Zmie\u0144 nazw\u0119 punktu po\u015Bredniego dialog.pointnameedit.name=Waypoint nazwa -dialog.pointnameedit.uppercase=UPPER case -dialog.pointnameedit.lowercase=lower case -dialog.pointnameedit.sentencecase=Sentence case +dialog.pointnameedit.uppercase= +dialog.pointnameedit.lowercase= +dialog.pointnameedit.sentencecase= +dialog.addtimeoffset.title= +dialog.addtimeoffset.add= +dialog.addtimeoffset.subtract= +dialog.addtimeoffset.days= +dialog.addtimeoffset.hours= +dialog.addtimeoffset.minutes=Minuty +dialog.addtimeoffset.notimestamps= +dialog.connect.title= +dialog.connectphoto.clonepoint= dialog.saveexif.title=Zapisz Exif dialog.saveexif.intro=Select the zdj\u0119cia to save using the checkboxes -dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save -dialog.saveexif.noexiftool=No exiftool program could be found. Continue? +dialog.saveexif.nothingtosave= +dialog.saveexif.noexiftool= dialog.saveexif.table.photoname=Nazwa zdj\u0119cie dialog.saveexif.table.status=Status dialog.saveexif.table.save=Zapisz -dialog.saveexif.photostatus.connected=Connected -dialog.saveexif.photostatus.disconnected=Disconnected +dialog.saveexif.photostatus.connected= +dialog.saveexif.photostatus.disconnected= dialog.saveexif.photostatus.modified=Zmodyfikowany dialog.saveexif.overwrite=Overwrite pliki dialog.correlate.title=Skoreluj zdj\u0119cie @@ -153,74 +177,85 @@ dialog.correlate.notimestamps=There are no timestamps in the data punkty, so the dialog.correlate.nouncorrelatedphotos=There are no uncorrelated zdj\u0119cia.\nAre you sure you want to continue? dialog.correlate.photoselect.intro=Select one of these correlated zdj\u0119cia to use as the time offset dialog.correlate.photoselect.photoname=Nazwa zdj\u0119cie -dialog.correlate.photoselect.timediff=Time difference +dialog.correlate.photoselect.timediff= dialog.correlate.photoselect.photolater=Zdj\u0119cie later dialog.correlate.options.tip=Tip: By manually correlating at least one zdj\u0119cie, the time offset can be calculated for you. -dialog.correlate.options.intro=Select the options for automatic correlation -dialog.correlate.options.offsetpanel=Time offset -dialog.correlate.options.offset=Offset +dialog.correlate.options.intro= +dialog.correlate.options.offsetpanel= +dialog.correlate.options.offset= dialog.correlate.options.offset.hours=hours, dialog.correlate.options.offset.minutes=minuty i dialog.correlate.options.offset.seconds=seconds dialog.correlate.options.photolater=Zdj\u0119cie po punkt dialog.correlate.options.pointlater=Punkt po zdj\u0119cie -dialog.correlate.options.limitspanel=Correlation limits -dialog.correlate.options.notimelimit=No time limit -dialog.correlate.options.timelimit=Time limit -dialog.correlate.options.nodistancelimit=No distance limit -dialog.correlate.options.distancelimit=Distance limit -dialog.correlate.options.correlate=Correlate +dialog.correlate.options.limitspanel= +dialog.correlate.options.notimelimit= +dialog.correlate.options.timelimit= +dialog.correlate.options.nodistancelimit= +dialog.correlate.options.distancelimit= +dialog.correlate.options.correlate= dialog.correlate.alloutsiderange=All zdj\u0119cia are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one zdj\u0119cie. -dialog.map.title=Prune map dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\npo wi\u0119cej informacji and user guides. dialog.about.title=Prune Informacje dialog.about.version=Wersja dialog.about.build=Build -dialog.about.summarytext1=Prune is a program for loading, displaying and editing data from GPS receivers. -dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.
Copying, redistribution and modification are permitted and encouraged
according to the conditions in the included license.txt file. -dialog.about.summarytext3=Please see http://activityworkshop.net/ for more informacji and user guides. +dialog.about.summarytext1= +dialog.about.summarytext2= +dialog.about.summarytext3= +dialog.about.languages= dialog.about.translatedby=Tekst po polsku by Piotr. -dialog.about.systeminfo=System info -dialog.about.systeminfo.os=Operating System -dialog.about.systeminfo.java=Java Runtime +dialog.about.systeminfo= +dialog.about.systeminfo.os= +dialog.about.systeminfo.java= dialog.about.systeminfo.java3d=Java3d zainstalowana dialog.about.systeminfo.povray=Povray zainstalowana dialog.about.systeminfo.exiftool=Exiftool zainstalowana dialog.about.systeminfo.gpsbabel=Gpsbabel zainstalowana dialog.about.yes=Tak dialog.about.no=Nie -dialog.about.credits=Credits -dialog.about.credits.code=Prune code written by -dialog.about.credits.exifcode=Exif code by -dialog.about.credits.icons=Some icons taken from -dialog.about.credits.translators=Translators +dialog.about.credits= +dialog.about.credits.code= +dialog.about.credits.exifcode= +dialog.about.credits.icons= +dialog.about.credits.translators= dialog.about.credits.translations=T\u0142umaczenie helped by -dialog.about.credits.devtools=Development tools -dialog.about.credits.othertools=Other tools +dialog.about.credits.devtools= +dialog.about.credits.othertools= dialog.about.credits.thanks=Dzi\u0119kuje to dialog.about.readme=Readme +dialog.checkversion.title= +dialog.checkversion.error= +dialog.checkversion.uptodate= +dialog.checkversion.newversion1= +dialog.checkversion.newversion2= +dialog.checkversion.releasedate1= +dialog.checkversion.releasedate2= +dialog.checkversion.download=To download the new version, go to http://activityworkshop.net/software/prune/download.html. # 3d window dialog.3d.title=Prune tr\u00f3jwymiarowa model -dialog.3d.altitudecap=Minimum altitude range -dialog.3dlines.title=Prune gridlines -dialog.3dlines.empty=No gridlines to display! -dialog.3dlines.intro=These are the gridlines for the three-d view +dialog.3d.altitudecap= +dialog.3dlines.title= +dialog.3dlines.empty= +dialog.3dlines.intro= # Confirm messages || These are displayed as confirmation in the status bar -confirm.loadfile=Data loaded from file -confirm.save.ok1=Successfully saved +confirm.loadfile= +confirm.save.ok1= confirm.save.ok2=punkty to plik -confirm.deleteduplicates.single=duplicate was deleted -confirm.deleteduplicates.multi=duplicates were deleted +confirm.deleteduplicates.single= +confirm.deleteduplicates.multi= confirm.deletepoint.single=data punkt was removed confirm.deletepoint.multi=data punkty were removed -confirm.undo.single=operation undone -confirm.undo.multi=operations undone +confirm.undo.single= +confirm.undo.multi= confirm.point.edit=punkt edited -confirm.mergetracksegments=track segments merged -confirm.reverserange=Range reversed -confirm.saveexif.ok1=Saved +confirm.mergetracksegments= +confirm.reverserange= +confirm.addtimeoffset= +confirm.rearrangewaypoints= +confirm.cutandmove= +confirm.saveexif.ok1= confirm.saveexif.ok2=zdj\u0119cia pliki confirm.jpegload.single=zdj\u0119cie was added confirm.jpegload.multi=zdj\u0119cia were added @@ -228,6 +263,7 @@ confirm.photo.connect=zdj\u0119cie connected confirm.photo.disconnect=zdj\u0119cie disconnected confirm.correlate.single=zdj\u0119cie was correlated confirm.correlate.multi=zdj\u0119cia were correlated +confirm.createpoint= # Buttons button.ok=OK @@ -250,7 +286,16 @@ button.notoall=Nie wszystko button.selectall=Zaznacz wszystko button.selectnone=Odznacz button.preview=Podgl\u0105d -button.guessfields=Guess fields +button.guessfields= +button.showwebpage= + +# File types +filetype.txt=Pliki TXT +filetype.jpeg=Pliki JPG +filetype.kmlkmz=Pliki KML, KMZ +filetype.kml=Pliki KML +filetype.gpx=Pliki GPX +filetype.pov=Pliki POV # Display components display.nodata=Brak za\u0142awadowanych danych @@ -264,41 +309,43 @@ details.pointdetails=Punkt szczeg\u00F3\u0142y details.index.selected=Indeks details.index.of=z details.nopointselection=Brak punkt zaznaczenia -details.speed=Speed +details.speed= details.photofile=Plik zdj\u0119cie details.norangeselection=Brak range zaznaczenia details.rangedetails=Range szczeg\u00F3\u0142y -details.range.selected=Selected -details.range.to=to -details.altitude.to=to -details.range.climb=Climb -details.range.descent=Descent +details.range.selected= +details.range.to= +details.altitude.to= +details.range.climb= +details.range.descent= details.coordformat=Format wsp\u00f3\u0142rz\u0119dne details.distanceunits=Jednostki dystansowy display.range.time.secs=s display.range.time.mins=m display.range.time.hours=h display.range.time.days=d -details.range.avespeed=Ave speed +details.range.avespeed= +details.range.avemovingspeed= details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Zdj\u0119cia details.photodetails=Zdj\u0119cie szczeg\u00F3\u0142y details.nophoto=Brak zdj\u0119cie zaznaczenia details.photo.loading=Wczytywanie -details.photo.connected=Connected +details.photo.connected= # Field names fieldname.latitude=Szeroko\u015B\u0107 fieldname.longitude=D\u0142ugo\u015B\u0107 fieldname.altitude=Wysoko\u015B\u0107 -fieldname.timestamp=Timestamp +fieldname.timestamp= fieldname.waypointname=Nazwa fieldname.waypointtype=Type -fieldname.newsegment=Segment +fieldname.newsegment= fieldname.custom=U\u017Cytkownika fieldname.prefix=Pole fieldname.distance=Dystansowy -fieldname.duration=Duration +fieldname.movingdistance= +fieldname.duration= # Measurement units units.original=Oryginalny @@ -334,19 +381,22 @@ undo.editpoint=edycja punkt undo.deletepoint=usu\u0144 punkt undo.deletephoto=remove zdj\u0119cie undo.deleterange=usu\u0144 range -undo.compress=compress track +undo.compress= undo.insert=insert punkty undo.deleteduplicates=usu\u0144 duplicates -undo.reverse=reverse range -undo.mergetracksegments=merge track segments -undo.rearrangewaypoints=rearrange waypoints +undo.reverse= +undo.mergetracksegments= +undo.addtimeoffset= +undo.rearrangewaypoints= +undo.cutandmove= undo.connectphoto=connect zdj\u0119cie undo.disconnectphoto=disconnect zdj\u0119cie undo.correlate=correlate zdj\u0119cia +undo.createpoint= # Error messages -error.save.dialogtitle=Error saving data -error.save.nodata=No data to save +error.save.dialogtitle= +error.save.nodata= error.save.failed=Failed to save the data to plik: error.saveexif.filenotfound=Failed to find zdj\u0119cie plik error.saveexif.cannotoverwrite1=Zdj\u0119cie plik @@ -365,11 +415,11 @@ error.undofailed.title=Cofnij failed error.undofailed.text=Nie mo\u017Cna cofn\u0105\u0107 error.function.noop.title=Funkcji had no effect error.rearrange.noop=Rearranging waypoints had no effect -error.function.notimplemented=Sorry, this funkcji has not yet been implemented. +error.function.notimplemented= error.function.notavailable.title=Funkcji nie jest dost\u0119pny error.function.nojava3d=This funkcji requires the Java3d library,\navailable from Sun.com. error.3d.title=B\u0142\u0105d in 3d display error.3d=A b\u0142\u0105d occurred with the 3d display -error.readme.notfound=Readme file not found -error.osmimage.dialogtitle=Error loading map images -error.osmimage.failed=Failed to load map images. Please check internet connection. +error.readme.notfound= +error.osmimage.dialogtitle= +error.osmimage.failed= diff --git a/tim/prune/load/FileLoader.java b/tim/prune/load/FileLoader.java index c1bf3df..4d9d2be 100644 --- a/tim/prune/load/FileLoader.java +++ b/tim/prune/load/FileLoader.java @@ -7,6 +7,7 @@ import javax.swing.JFrame; import javax.swing.JOptionPane; import tim.prune.App; +import tim.prune.Config; import tim.prune.I18nManager; import tim.prune.load.xml.XmlFileLoader; @@ -42,14 +43,27 @@ public class FileLoader */ public void openFile() { + // Construct file chooser if necessary if (_fileChooser == null) + { _fileChooser = new JFileChooser(); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"})); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"})); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.kml", new String[] {"kml"})); + _fileChooser.setAcceptAllFileFilterUsed(true); + // start from directory in config if already set (by load jpegs) + File configDir = Config.getWorkingDirectory(); + if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);} + } + // Show the open dialog if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { File file = _fileChooser.getSelectedFile(); // Check file exists and is readable if (file != null && file.exists() && file.canRead()) { + // Store directory in config for later + Config.setWorkingDirectory(file.getParentFile()); // Check file type to see if it's xml or just normal text String fileExtension = file.getName().toLowerCase(); if (fileExtension.length() > 4) diff --git a/tim/prune/load/GenericFileFilter.java b/tim/prune/load/GenericFileFilter.java new file mode 100644 index 0000000..3af4d9e --- /dev/null +++ b/tim/prune/load/GenericFileFilter.java @@ -0,0 +1,123 @@ +package tim.prune.load; + +import java.io.File; +import javax.swing.filechooser.FileFilter; + +import tim.prune.I18nManager; + +/** + * Class to act as a generic file filter based on file extension + */ +public class GenericFileFilter extends FileFilter +{ + /** Filter description for display */ + private String _filterDesc = null; + /** Array of allowed three-character suffixes */ + private String[] _threeCharSuffixes = null; + /** Array of allowed four-character suffixes */ + private String[] _fourCharSuffixes = null; + + + /** + * Constructor + * @param inDescKey key for filter description + * @param inSuffixes array of allowed 3- and 4-character file suffixes + */ + public GenericFileFilter(String inDescKey, String[] inSuffixes) + { + _filterDesc = I18nManager.getText(inDescKey); + if (inSuffixes != null && inSuffixes.length > 0) + { + _threeCharSuffixes = new String[inSuffixes.length]; + _fourCharSuffixes = new String[inSuffixes.length]; + int threeIndex = 0, fourIndex = 0; + for (int i=0; i 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 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); + } +} diff --git a/tim/prune/load/JpegLoader.java b/tim/prune/load/JpegLoader.java index cab6c56..c506236 100644 --- a/tim/prune/load/JpegLoader.java +++ b/tim/prune/load/JpegLoader.java @@ -18,9 +18,11 @@ import javax.swing.JPanel; import javax.swing.JProgressBar; import tim.prune.App; +import tim.prune.Config; import tim.prune.I18nManager; import tim.prune.data.Altitude; import tim.prune.data.DataPoint; +import tim.prune.data.LatLonRectangle; import tim.prune.data.Latitude; import tim.prune.data.Longitude; import tim.prune.data.Photo; @@ -39,12 +41,15 @@ public class JpegLoader implements Runnable private App _app = null; private JFrame _parentFrame = null; private JFileChooser _fileChooser = null; + private GenericFileFilter _fileFilter = null; private JCheckBox _subdirCheckbox = null; private JCheckBox _noExifCheckbox = null; + private JCheckBox _outsideAreaCheckbox = null; private JDialog _progressDialog = null; private JProgressBar _progressBar = null; private int[] _fileCounts = null; private boolean _cancelled = false; + private LatLonRectangle _trackRectangle = null; private TreeSet _photos = null; @@ -57,31 +62,45 @@ public class JpegLoader implements Runnable { _app = inApp; _parentFrame = inParentFrame; + String[] fileTypes = {"jpg", "jpe", "jpeg"}; + _fileFilter = new GenericFileFilter("filetype.jpeg", fileTypes); } /** * Open the GUI to select options and start the load + * @param inRectangle track rectangle */ - public void openDialog() + public void openDialog(LatLonRectangle inRectangle) { - // TODO: Allow restriction of load area, either to current track or manually-entered range + // Create file chooser if necessary if (_fileChooser == null) { _fileChooser = new JFileChooser(); _fileChooser.setMultiSelectionEnabled(true); _fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + _fileChooser.setFileFilter(_fileFilter); _fileChooser.setDialogTitle(I18nManager.getText("menu.file.addphotos")); _subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories")); _subdirCheckbox.setSelected(true); _noExifCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegswithoutcoords")); _noExifCheckbox.setSelected(true); + _outsideAreaCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegsoutsidearea")); + _outsideAreaCheckbox.setSelected(true); JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(_subdirCheckbox); panel.add(_noExifCheckbox); + panel.add(_outsideAreaCheckbox); _fileChooser.setAccessory(panel); + // start from directory in config if already set by other operations + File configDir = Config.getWorkingDirectory(); + if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);} } + // enable/disable track checkbox + _trackRectangle = inRectangle; + _outsideAreaCheckbox.setEnabled(_trackRectangle != null && !_trackRectangle.isEmpty()); + // Show file dialog to choose file / directory(ies) if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { // Bring up dialog before starting @@ -170,9 +189,7 @@ public class JpegLoader implements Runnable } else { - // Found some photos to load - // TODO: Load jpeg information into dialog for confirmation? - // Pass information back to app + // Found some photos to load - pass information back to app _app.informPhotosLoaded(_photos); } } @@ -231,7 +248,8 @@ public class JpegLoader implements Runnable _progressBar.repaint(); // Check whether filename corresponds with accepted filenames - if (!acceptPhotoFilename(inFile.getName())) {return;} + if (!_fileFilter.acceptFilename(inFile.getName())) {return;} + // If it's a Jpeg, we can use ExifReader to get coords, otherwise we could try exiftool (if it's installed) // Create Photo object Photo photo = new Photo(inFile); @@ -251,6 +269,7 @@ public class JpegLoader implements Runnable // Make DataPoint and attach to Photo DataPoint point = createDataPoint(jpegData); point.setPhoto(photo); + point.setSegmentStart(true); photo.setDataPoint(point); photo.setOriginalStatus(PhotoStatus.TAGGED); _fileCounts[3]++; @@ -265,17 +284,13 @@ public class JpegLoader implements Runnable catch (JpegException jpe) { // don't list errors, just count them } // Use file timestamp if exif timestamp isn't available - if (photo.getTimestamp() == null) - { + if (photo.getTimestamp() == null) { photo.setTimestamp(new Timestamp(inFile.lastModified())); - //System.out.println("No exif, using timestamp from file: " + inFile.lastModified() + " -> " + photo.getTimestamp().getText()); } - else - { - //System.out.println("timestamp from file = " + photo.getTimestamp().getText()); - } - // Add the photo if it's got a point or if pointless photos should be added - if (photo.getDataPoint() != null || _noExifCheckbox.isSelected()) + // Check the criteria for adding the photo - check whether the photo has coordinates and if so if they're within the rectangle + if ( (photo.getDataPoint() != null || _noExifCheckbox.isSelected()) + && (photo.getDataPoint() == null || !_outsideAreaCheckbox.isEnabled() + || _outsideAreaCheckbox.isSelected() || _trackRectangle.containsPoint(photo.getDataPoint()))) { _photos.add(photo); } @@ -300,6 +315,10 @@ public class JpegLoader implements Runnable File file = inFiles[i]; if (file.exists() && file.canRead()) { + // Store first directory in config for later + if (i == 0 && inFirstDir) { + Config.setWorkingDirectory(file.isDirectory()?file:file.getParentFile()); + } // Check whether it's a file or a directory if (file.isFile()) { @@ -393,34 +412,4 @@ public class JpegLoader implements Runnable catch (NumberFormatException nfe) {} return stamp; } - - - /** - * Check whether to accept the given filename - * @param inName name of file - * @return true if accepted, false otherwise - */ - private static boolean acceptPhotoFilename(String inName) - { - if (inName != null && inName.length() > 4) - { - // Check for three-character file extensions jpg and jpe - String lastFour = inName.substring(inName.length() - 4).toLowerCase(); - if (lastFour.equals(".jpg") || lastFour.equals(".jpe")) - { - return true; - } - // If not found, check for file extension jpeg - if (inName.length() > 5) - { - String lastFive = inName.substring(inName.length() - 5).toLowerCase(); - if (lastFive.equals(".jpeg")) - { - return true; - } - } - } - // Not matched so don't accept - return false; - } } diff --git a/tim/prune/load/TextFileLoader.java b/tim/prune/load/TextFileLoader.java index f0408da..0d7ef48 100644 --- a/tim/prune/load/TextFileLoader.java +++ b/tim/prune/load/TextFileLoader.java @@ -267,6 +267,7 @@ public class TextFileLoader _cardPanel.setLayout(_layout); JPanel firstCard = new JPanel(); firstCard.setLayout(new BorderLayout()); + firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15)); JPanel delimsPanel = new JPanel(); delimsPanel.setLayout(new GridLayout(0, 2)); @@ -310,6 +311,7 @@ public class TextFileLoader // Second screen, for field order selection JPanel secondCard = new JPanel(); secondCard.setLayout(new BorderLayout()); + secondCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15)); // table for file contents _fileExtractTableModel = new FileExtractTableModel(); JTable extractTable = new JTable(_fileExtractTableModel); @@ -319,6 +321,8 @@ public class TextFileLoader secondCard.add(makeLabelledPanel("dialog.openoptions.tabledesc", tableScrollPane), BorderLayout.NORTH); JPanel innerPanel2 = new JPanel(); innerPanel2.setLayout(new BorderLayout()); + innerPanel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + _fieldTable = new JTable(new FieldSelectionTableModel()); _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // add listener for selected table row diff --git a/tim/prune/load/xml/XmlFileLoader.java b/tim/prune/load/xml/XmlFileLoader.java index 987841e..8801d00 100644 --- a/tim/prune/load/xml/XmlFileLoader.java +++ b/tim/prune/load/xml/XmlFileLoader.java @@ -145,4 +145,12 @@ public class XmlFileLoader extends DefaultHandler implements Runnable } super.endElement(uri, localName, qName); } + + /** + * @return The Xml handler used for the parsing + */ + public XmlHandler getHandler() + { + return _handler; + } } diff --git a/tim/prune/readme.txt b/tim/prune/readme.txt index 44adc9f..bdc6cae 100644 --- a/tim/prune/readme.txt +++ b/tim/prune/readme.txt @@ -1,4 +1,4 @@ -Prune version 5 +Prune version 6 =============== Prune is an application for viewing, editing and managing coordinate data from GPS systems, @@ -16,7 +16,7 @@ Running ======= To run Prune from the jar file, simply call it from a command prompt or shell: - java -jar prune_05.jar + java -jar prune_06.jar If the jar file is saved in a different directory, you will need to include the path. Depending on your system settings, you may be able to click or double-click on the jar file @@ -24,9 +24,22 @@ in a file manager window to execute it. A shortcut, menu item, alias, desktop i or other link can of course be made should you wish. To specify a language other than the default, use an additional parameter, eg: - java -jar prune_05.jar --lang=DE + java -jar prune_06.jar --lang=DE +New with version 6 +================== + +The following features were added since version 5: + - Map view using OpenStreetMap images is now integrated in the main window, with control for map transparency + - Pov export has new option to use sphere sweeps for better appearance + - New function to check online for a newer version of Prune + - New function to take a section of track and cut/paste it to another position + - New function to add or subtract a time offset from point timestamps + - New function to call gpsbabel to load data directly from GPS receiver + - Additional file filter options on load and save + - Italian language thanks to generous user input + New with version 5 ================== diff --git a/tim/prune/save/FileSaver.java b/tim/prune/save/FileSaver.java index c42a644..5991b86 100644 --- a/tim/prune/save/FileSaver.java +++ b/tim/prune/save/FileSaver.java @@ -32,6 +32,7 @@ import javax.swing.ListSelectionModel; import javax.swing.table.TableModel; import tim.prune.App; +import tim.prune.Config; import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; import tim.prune.data.Altitude; @@ -41,6 +42,7 @@ import tim.prune.data.Field; import tim.prune.data.FieldList; import tim.prune.data.Timestamp; import tim.prune.data.Track; +import tim.prune.load.GenericFileFilter; import tim.prune.load.OneCharDocument; /** @@ -220,7 +222,7 @@ public class FileSaver // header checkbox firstCard.add(Box.createRigidArea(new Dimension(0,10))); - _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow")); + _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"), true); firstCard.add(_headerRowCheckbox); _cards.add(firstCard, "card1"); @@ -382,8 +384,17 @@ public class FileSaver boolean saveOK = true; FileWriter writer = null; if (_fileChooser == null) + { _fileChooser = new JFileChooser(); - _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"})); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"})); + _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.kml", new String[] {"kml"})); + _fileChooser.setAcceptAllFileFilterUsed(true); + // start from directory in config which should be set + File configDir = Config.getWorkingDirectory(); + if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);} + } if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { File saveFile = _fileChooser.getSelectedFile(); @@ -485,7 +496,7 @@ public class FileSaver { try { - buffer.append(point.getAltitude().getValue(altitudeFormat)); + buffer.append(point.getAltitude().getStringValue(altitudeFormat)); } catch (NullPointerException npe) {} } @@ -518,6 +529,8 @@ public class FileSaver writer.write(buffer.toString()); writer.write(lineSeparator); } + // Store directory in config for later + Config.setWorkingDirectory(saveFile.getParentFile()); // Save successful UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1") + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2") diff --git a/tim/prune/save/GpxExporter.java b/tim/prune/save/GpxExporter.java index 0e028d1..5afdda3 100644 --- a/tim/prune/save/GpxExporter.java +++ b/tim/prune/save/GpxExporter.java @@ -12,8 +12,10 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -21,8 +23,8 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; -import javax.swing.filechooser.FileFilter; +import tim.prune.Config; import tim.prune.GpsPruner; import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; @@ -32,6 +34,7 @@ import tim.prune.data.DataPoint; import tim.prune.data.Timestamp; import tim.prune.data.Track; import tim.prune.data.TrackInfo; +import tim.prune.load.GenericFileFilter; /** * Class to export track information @@ -44,6 +47,7 @@ public class GpxExporter implements Runnable private JDialog _dialog = null; private JTextField _nameField = null; private JTextField _descriptionField = null; + private JCheckBox _timestampsCheckbox = null; private JFileChooser _fileChooser = null; private File _exportFile = null; @@ -103,6 +107,10 @@ public class GpxExporter implements Runnable _descriptionField = new JTextField(10); descPanel.add(_descriptionField); mainPanel.add(descPanel); + // checkbox for timestamps + _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps")); + _timestampsCheckbox.setSelected(true); + mainPanel.add(_timestampsCheckbox); dialogPanel.add(mainPanel, BorderLayout.CENTER); // button panel at bottom @@ -127,6 +135,7 @@ public class GpxExporter implements Runnable }); buttonPanel.add(cancelButton); dialogPanel.add(buttonPanel, BorderLayout.SOUTH); + dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15)); return dialogPanel; } @@ -138,20 +147,15 @@ public class GpxExporter implements Runnable { // OK pressed, so choose output file if (_fileChooser == null) - {_fileChooser = new JFileChooser();} - _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); - _fileChooser.setFileFilter(new FileFilter() { - public boolean accept(File f) - { - return (f != null && (f.isDirectory() - || f.getName().toLowerCase().endsWith(".gpx"))); - } - public String getDescription() - { - return I18nManager.getText("dialog.exportgpx.filetype"); - } - }); - _fileChooser.setAcceptAllFileFilterUsed(false); + { + _fileChooser = new JFileChooser(); + _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"})); + _fileChooser.setAcceptAllFileFilterUsed(false); + // start from directory in config which should be set + File configDir = Config.getWorkingDirectory(); + if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);} + } // Allow choose again if an existing file is selected boolean chooseAgain = false; do @@ -202,6 +206,8 @@ public class GpxExporter implements Runnable // close file writer.close(); + // Store directory in config for later + Config.setWorkingDirectory(_exportFile.getParentFile()); // Show confirmation UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1") + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2") @@ -237,7 +243,9 @@ public class GpxExporter implements Runnable inWriter.write(GPX_VERSION_NUMBER); inWriter.write("\" creator=\""); inWriter.write(GPX_CREATOR); - inWriter.write("\">\n"); + inWriter.write("\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + + " xmlns=\"http://www.topografix.com/GPX/1/0\"" + + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n"); // Name field if (_nameField != null && _nameField.getText() != null && !_nameField.getText().equals("")) { @@ -284,11 +292,11 @@ public class GpxExporter implements Runnable for (i=0; i\n\t\n"); } if (!point.isWaypoint()) { - // restart track segment if necessary // export the track point exportTrackpoint(point, inWriter); firstPoint = false; @@ -321,11 +329,11 @@ public class GpxExporter implements Runnable if (inPoint.hasAltitude()) { inWriter.write("\t\t"); - inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES)); inWriter.write("\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