import tim.prune.data.DataPoint;
import tim.prune.data.Field;
+import tim.prune.data.Photo;
+import tim.prune.data.PhotoList;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
import tim.prune.edit.FieldEditList;
import tim.prune.gui.UndoManager;
import tim.prune.load.FileLoader;
import tim.prune.load.JpegLoader;
+import tim.prune.load.PhotoMeasurer;
+import tim.prune.save.ExifSaver;
import tim.prune.save.FileSaver;
import tim.prune.save.KmlExporter;
import tim.prune.save.PovExporter;
import tim.prune.threedee.ThreeDWindow;
import tim.prune.threedee.WindowFactory;
import tim.prune.undo.UndoCompress;
+import tim.prune.undo.UndoConnectPhoto;
import tim.prune.undo.UndoDeleteDuplicates;
+import tim.prune.undo.UndoDeletePhoto;
import tim.prune.undo.UndoDeletePoint;
import tim.prune.undo.UndoDeleteRange;
import tim.prune.undo.UndoEditPoint;
private MenuManager _menuManager = null;
private FileLoader _fileLoader = null;
private JpegLoader _jpegLoader = null;
+ private KmlExporter _exporter = null;
private PovExporter _povExporter = null;
private Stack _undoStack = null;
private UpdateMessageBroker _broker = null;
*/
public boolean hasDataUnsaved()
{
- return _undoStack.size() > _lastSavePosition;
+ return (_undoStack.size() > _lastSavePosition
+ && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
}
/**
}
else
{
- KmlExporter exporter = new KmlExporter(this, _frame, _track);
- exporter.showDialog();
+ // Invoke the export
+ if (_exporter == null)
+ {
+ _exporter = new KmlExporter(_frame, _trackInfo);
+ }
+ _exporter.showDialog();
}
}
// Make new exporter if necessary
if (_povExporter == null)
{
- _povExporter = new PovExporter(this, _frame, _track);
+ _povExporter = new PovExporter(_frame, _track);
}
// Specify angles if necessary
if (inDefineSettings)
*/
public void exit()
{
+ // grab focus
+ _frame.toFront();
+ _frame.requestFocus();
// check if ok to exit
Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
if (!hasDataUnsaved()
{
// Open point dialog to display details
PointNameEditor editor = new PointNameEditor(this, _frame);
- editor.showDialog(_track, currentPoint);
+ editor.showDialog(currentPoint);
}
}
}
DataPoint currentPoint = _trackInfo.getCurrentPoint();
if (currentPoint != null)
{
+ boolean deletePhoto = false;
+ Photo currentPhoto = currentPoint.getPhoto();
+ if (currentPhoto != null)
+ {
+ // Confirm deletion of photo or decoupling
+ int response = JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getFile().getName(),
+ I18nManager.getText("dialog.deletepoint.title"),
+ JOptionPane.YES_NO_CANCEL_OPTION);
+ if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
+ {
+ // cancel pressed- abort delete
+ return;
+ }
+ if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
+ }
// add information to undo stack
int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
- UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint);
+ int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
+ // Undo object needs to know index of photo in list (if any) to restore
+ UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex);
// call track to delete point
if (_trackInfo.deletePoint())
{
_undoStack.push(undo);
+ if (currentPhoto != null)
+ {
+ // delete photo if necessary
+ if (deletePhoto)
+ {
+ _trackInfo.getPhotoList().deletePhoto(photoIndex);
+ }
+ else
+ {
+ // decouple photo from point
+ currentPhoto.setDataPoint(null);
+ }
+ }
}
}
}
{
if (_track != null)
{
- // add information to undo stack
- UndoOperation undo = new UndoDeleteRange(_trackInfo);
- // call track to delete point
- if (_trackInfo.deleteRange())
+ // Find out if photos should be deleted or not
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ if (selStart >= 0 && selEnd >= selStart)
{
- _undoStack.push(undo);
+ int numToDelete = selEnd - selStart + 1;
+ boolean[] deletePhotos = new boolean[numToDelete];
+ Photo[] photosToDelete = new Photo[numToDelete];
+ boolean deleteAll = false;
+ boolean deleteNone = false;
+ String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
+ I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"),
+ I18nManager.getText("button.cancel")};
+ DataPoint point = null;
+ for (int i=0; i<numToDelete; i++)
+ {
+ point = _trackInfo.getTrack().getPoint(i + selStart);
+ if (point != null && point.getPhoto() != null)
+ {
+ if (deleteAll)
+ {
+ deletePhotos[i] = true;
+ photosToDelete[i] = point.getPhoto();
+ }
+ else if (deleteNone) {deletePhotos[i] = false;}
+ else
+ {
+ int response = JOptionPane.showOptionDialog(_frame,
+ I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getFile().getName(),
+ I18nManager.getText("dialog.deletepoint.title"),
+ JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ questionOptions, questionOptions[1]);
+ // check for cancel or close
+ if (response == 4 || response == -1) {return;}
+ // check for yes or yes to all
+ if (response == 0 || response == 2)
+ {
+ deletePhotos[i] = true;
+ photosToDelete[i] = point.getPhoto();
+ if (response == 2) {deleteAll = true;}
+ }
+ // check for no to all
+ if (response == 3) {deleteNone = true;}
+ }
+ }
+ }
+ // add information to undo stack
+ UndoOperation undo = new UndoDeleteRange(_trackInfo);
+ // delete requested photos
+ for (int i=0; i<numToDelete; i++)
+ {
+ point = _trackInfo.getTrack().getPoint(i + selStart);
+ if (point != null && point.getPhoto() != null)
+ {
+ if (deletePhotos[i])
+ {
+ // delete photo from list
+ _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
+ }
+ else
+ {
+ // decouple from point
+ point.getPhoto().setDataPoint(null);
+ }
+ }
+ }
+ // call track to delete range
+ if (_trackInfo.deleteRange())
+ {
+ _undoStack.push(undo);
+ }
}
}
}
*/
public void selectNone()
{
+ // deselect point, range and photo
_trackInfo.getSelection().clearAll();
}
+
/**
* Receive loaded data and optionally merge with current Track
* @param inFieldArray array of fields
*/
public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
{
+ // Check whether loaded array can be properly parsed into a Track
+ Track loadedTrack = new Track(_broker);
+ loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
+ if (loadedTrack.getNumPoints() <= 0)
+ {
+ JOptionPane.showMessageDialog(_frame,
+ I18nManager.getText("error.load.nopoints"),
+ I18nManager.getText("error.load.dialogtitle"),
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
// Decide whether to load or append
if (_track != null && _track.getNumPoints() > 0)
{
if (answer == JOptionPane.YES_OPTION)
{
// append data to current Track
- Track loadedTrack = new Track(_broker);
- loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
_undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints()));
_track.combine(loadedTrack);
- _trackInfo.getFileInfo().addFile();
+ // set filename if currently empty
+ if (_trackInfo.getFileInfo().getNumFiles() == 0)
+ {
+ _trackInfo.getFileInfo().setFile(inFilename);
+ }
+ else
+ {
+ _trackInfo.getFileInfo().addFile();
+ }
}
else if (answer == JOptionPane.NO_OPTION)
{
// Don't append, replace data
- _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length));
+ PhotoList photos = null;
+ if (_trackInfo.getPhotoList().hasCorrelatedPhotos())
+ {
+ photos = _trackInfo.getPhotoList().cloneList();
+ }
+ _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos));
_lastSavePosition = _undoStack.size();
+ // TODO: Should be possible to reuse the Track object already loaded?
_trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
_trackInfo.getFileInfo().setFile(inFilename);
+ if (photos != null)
+ {
+ _trackInfo.getPhotoList().removeCorrelatedPhotos();
+ }
}
}
else
{
// currently no data held, so use received data
- _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length));
+ _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null));
_lastSavePosition = _undoStack.size();
_trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
_trackInfo.getFileInfo().setFile(inFilename);
{
if (inPhotoList != null && !inPhotoList.isEmpty())
{
- // TODO: Attempt to restrict loaded photos to current area (if any) ?
- int numAdded = _trackInfo.addPhotos(inPhotoList);
- if (numAdded > 0)
+ int[] numsAdded = _trackInfo.addPhotos(inPhotoList);
+ int numPhotosAdded = numsAdded[0];
+ int numPointsAdded = numsAdded[1];
+ if (numPhotosAdded > 0)
{
- _undoStack.add(new UndoLoadPhotos(numAdded));
+ // Save numbers so load can be undone
+ _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
+ // Trigger preloading of photo sizes in separate thread
+ new PhotoMeasurer(_trackInfo.getPhotoList()).measurePhotos();
}
- if (numAdded == 1)
+ if (numPhotosAdded == 1)
{
JOptionPane.showMessageDialog(_frame,
- "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
+ "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(_frame,
- "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
+ "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
}
// TODO: Improve message when photo(s) fail to load (eg already added)
}
+ /**
+ * Connect the current photo to the current point
+ */
+ public void connectPhotoToPoint()
+ {
+ Photo photo = _trackInfo.getCurrentPhoto();
+ DataPoint point = _trackInfo.getCurrentPoint();
+ if (photo != null && point != null && point.getPhoto() == null)
+ {
+ // connect
+ _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
+ photo.setDataPoint(point);
+ point.setPhoto(photo);
+ //TODO: Confirm connect (maybe with status in photo panel?)
+ }
+ }
+
+
+ /**
+ * Remove the current photo, if any
+ */
+ public void deleteCurrentPhoto()
+ {
+ // Delete the current photo, and optionally its point too, keeping undo information
+ Photo currentPhoto = _trackInfo.getCurrentPhoto();
+ if (currentPhoto != null)
+ {
+ // Photo is selected, see if it has a point or not
+ boolean photoDeleted = false;
+ UndoDeletePhoto undoAction = null;
+ if (currentPhoto.getDataPoint() == null)
+ {
+ // no point attached, so just delete photo
+ undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
+ null, -1);
+ photoDeleted = _trackInfo.deleteCurrentPhoto(false);
+ }
+ else
+ {
+ // point is attached, so need to confirm point deletion
+ undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
+ currentPhoto.getDataPoint(), _trackInfo.getTrack().getPointIndex(currentPhoto.getDataPoint()));
+ int response = JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.deletephoto.deletepoint"),
+ I18nManager.getText("dialog.deletephoto.title"),
+ JOptionPane.YES_NO_CANCEL_OPTION);
+ boolean deletePointToo = (response == JOptionPane.YES_OPTION);
+ // Cancel delete if cancel pressed or dialog closed
+ if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION)
+ {
+ photoDeleted = _trackInfo.deleteCurrentPhoto(deletePointToo);
+ }
+ }
+ // Add undo information to stack if necessary
+ if (photoDeleted)
+ {
+ _undoStack.add(undoAction);
+ }
+ }
+ }
+
+
+ /**
+ * Save the coordinates of photos in their exif data
+ */
+ public void saveExif()
+ {
+ ExifSaver saver = new ExifSaver(_frame);
+ saver.saveExifInformation(_trackInfo.getPhotoList());
+ }
+
+
/**
* Inform the app that the data has been saved
*/
--- /dev/null
+package tim.prune;
+
+import java.io.IOException;
+
+
+/**
+ * Class to manage interfaces to external tools, like exiftool
+ */
+public abstract class ExternalTools
+{
+
+ /**
+ * Attempt to call Povray to see if it's installed / available in path
+ * @return true if found, false otherwise
+ */
+ public static boolean isPovrayInstalled()
+ {
+ try
+ {
+ Runtime.getRuntime().exec("povray");
+ return true;
+ }
+ catch (IOException ioe)
+ {
+ // exception thrown, povray not found
+ return false;
+ }
+ }
+
+
+ /**
+ * Attempt to call Exiftool to see if it's installed / available in path
+ * @return true if found, false otherwise
+ */
+ public static boolean isExiftoolInstalled()
+ {
+ try
+ {
+ Runtime.getRuntime().exec("exiftool -v");
+ return true;
+ }
+ catch (IOException ioe)
+ {
+ // exception thrown, exiftool not found
+ return false;
+ }
+ }
+}
package tim.prune;
+import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Locale;
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.MenuManager;
import tim.prune.gui.ProfileChart;
+import tim.prune.gui.SelectorDisplay;
/**
* Tool to visualize, edit and prune GPS data
*/
public class GpsPruner
{
- // Version 2, released 29 March 2007, 1 April 2007
- public static final String VERSION_NUMBER = "2";
- public static final String BUILD_NUMBER = "056";
+ // Final release of version 3
+ public static final String VERSION_NUMBER = "3";
+ public static final String BUILD_NUMBER = "074";
private static App APP = null;
{
if (args[0].startsWith("--locale="))
{
- if (args[0].length() == 11)
- locale = new Locale(args[0].substring(9));
- else if (args[0].length() == 14)
- locale = new Locale(args[0].substring(9, 11), args[0].substring(12));
- else
- System.out.println("Unrecognised locale '" + args[0].substring(9)
- + "' - locale should be eg 'DE' or 'DE_ch'");
+ locale = getLanguage(args[0].substring(9));
+ }
+ else if (args[0].startsWith("--lang="))
+ {
+ locale = getLanguage(args[0].substring(7));
}
else
+ {
System.out.println("Unknown parameter '" + args[0] +
- "'. Possible parameters:\n --locale= used for overriding locale settings\n");
+ "'. Possible parameters:\n --locale= or --lang= used for overriding language settings\n");
+ }
}
I18nManager.init(locale);
launch();
}
+ /**
+ * Choose a locale based on the given code
+ * @param inString code for locale
+ * @return Locale object if available, otherwise null
+ */
+ private static Locale getLanguage(String inString)
+ {
+ if (inString.length() == 2)
+ {
+ return new Locale(inString);
+ }
+ else if (inString.length() == 5)
+ {
+ return new Locale(inString.substring(0, 2), inString.substring(3));
+ }
+ System.out.println("Unrecognised locale '" + inString
+ + "' - value should be eg 'DE' or 'DE_ch'");
+ return null;
+ }
+
+
/**
* Launch the main application
*/
frame.setJMenuBar(menuManager.createMenuBar());
APP.setMenuManager(menuManager);
broker.addSubscriber(menuManager);
+ // Make toolbar for buttons
+ JToolBar toolbar = menuManager.createToolBar();
// Make three GUI components and add as listeners
- DetailsDisplay leftPanel = new DetailsDisplay(APP, APP.getTrackInfo());
+ SelectorDisplay leftPanel = new SelectorDisplay(APP.getTrackInfo());
broker.addSubscriber(leftPanel);
+ DetailsDisplay rightPanel = new DetailsDisplay(APP.getTrackInfo());
+ broker.addSubscriber(rightPanel);
MapChart mapDisp = new MapChart(APP, APP.getTrackInfo());
broker.addSubscriber(mapDisp);
ProfileChart profileDisp = new ProfileChart(APP.getTrackInfo());
broker.addSubscriber(profileDisp);
- JSplitPane rightPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, mapDisp, profileDisp);
- rightPane.setResizeWeight(1.0); // allocate as much space as poss to map
+ JSplitPane midPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, mapDisp, profileDisp);
+ midPane.setResizeWeight(1.0); // allocate as much space as poss to map
+ JSplitPane triplePane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, midPane, rightPanel);
+ triplePane.setResizeWeight(1.0); // allocate as much space as poss to map
+
+ frame.getContentPane().setLayout(new BorderLayout());
+ frame.getContentPane().add(toolbar, BorderLayout.NORTH);
frame.getContentPane().add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel,
- rightPane));
+ triplePane), BorderLayout.CENTER);
// add closing listener
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// finish off and display frame
frame.pack();
- frame.setSize(600, 450);
+ frame.setSize(650, 450);
frame.show();
}
}
{
private DataSubscriber[] _subscribers;
private int _subscriberNum = 0;
- private static final int MAXIMUM_NUMBER_SUBSCRIBERS = 4;
+ private static final int MAXIMUM_NUMBER_SUBSCRIBERS = 5;
/**
{
try
{
- _value = Integer.parseInt(inString.trim());
+ _value = (int) Double.parseDouble(inString.trim());
_format = inFormat;
_valid = true;
}
public static final int FORMAT_DEG_MIN = 11;
public static final int FORMAT_DEG = 12;
public static final int FORMAT_DEG_WITHOUT_CARDINAL = 13;
+ public static final int FORMAT_DEG_WHOLE_MIN = 14;
+ public static final int FORMAT_DEG_MIN_SEC_WITH_SPACES = 15;
+ public static final int FORMAT_CARDINAL = 16;
public static final int FORMAT_NONE = 19;
// Instance variables
{
_asDouble = inValue;
// Calculate degrees, minutes, seconds
- _degrees = (int) inValue;
- double numMins = (Math.abs(_asDouble)-Math.abs(_degrees)) * 60.0;
+ _degrees = (int) Math.abs(inValue);
+ double numMins = (Math.abs(_asDouble)-_degrees) * 60.0;
_minutes = (int) numMins;
double numSecs = (numMins - _minutes) * 60.0;
_seconds = (int) numSecs;
.append(twoDigitString(_minutes)).append('\'')
.append(twoDigitString(_seconds)).append('.')
.append(_fracs);
- answer = buffer.toString(); break;
+ answer = buffer.toString();
+ break;
}
case FORMAT_DEG_MIN:
{
answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
- + (_minutes + _seconds / 60.0 + _fracs / 600.0); break;
+ + (_minutes + _seconds / 60.0 + _fracs / 600.0) + "'";
+ break;
+ }
+ case FORMAT_DEG_WHOLE_MIN:
+ {
+ answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
+ + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 600.0 + 0.5) + "'";
+ break;
}
case FORMAT_DEG:
case FORMAT_DEG_WITHOUT_CARDINAL:
{
answer = (_asDouble<0.0?"-":"")
- + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 36000.0); break;
+ + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 36000.0);
+ break;
+ }
+ case FORMAT_DEG_MIN_SEC_WITH_SPACES:
+ {
+ answer = "" + _degrees + " " + _minutes + " " + _seconds + "." + _fracs;
+ break;
+ }
+ case FORMAT_CARDINAL:
+ {
+ answer = "" + PRINTABLE_CARDINALS[_cardinal];
+ break;
}
}
}
private Timestamp _timestamp = null;
private Photo _photo = null;
private String _waypointName = null;
- private boolean _pointValid = false;
+ // private boolean _startOfSegment = false;
/**
_altitude = new Altitude(getFieldValue(Field.ALTITUDE), inAltFormat);
_timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
_waypointName = getFieldValue(Field.WAYPT_NAME);
+ // TODO: Parse segment start field (format?)
}
public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude)
{
// Only these three fields are available
- _fieldValues = new String[0];
- _fieldList = new FieldList();
+ _fieldValues = new String[3];
+ Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
+ _fieldList = new FieldList(fields);
_latitude = inLatitude;
+ _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);}
_timestamp = new Timestamp(null);
}
private static final double EARTH_RADIUS_KM = 6372.795;
private static final double EARTH_RADIUS_MILES = 3959.8712255;
// Conversion constants
- private static final double CONVERT_KM_TO_MILES = 1.609344;
- private static final double CONVERT_MILES_TO_KM = 0.621371192;
+ //private static final double CONVERT_KM_TO_MILES = 1.609344;
+ //private static final double CONVERT_MILES_TO_KM = 0.621371192;
/**
package tim.prune.data;
+import java.awt.Dimension;
import java.io.File;
+import javax.swing.ImageIcon;
+
/**
* Class to represent a photo and link to DataPoint
*/
{
/** File where photo is stored */
private File _file = null;
+ /** Timestamp, if any */
+ private Timestamp _timestamp = null;
/** Associated DataPoint if correlated */
private DataPoint _dataPoint = null;
+ /** Size of original image */
+ private Dimension _size = null;
+ /** Status of photo when loaded */
+ private byte _originalStatus = PhotoStatus.NOT_CONNECTED;
+ /** Current photo status */
+ private byte _currentStatus = PhotoStatus.NOT_CONNECTED;
+ // TODO: Need to store caption for image?
+ // TODO: Need to store thumbnail for image?
/**
public Photo(File inFile)
{
_file = inFile;
- // TODO: Cache photo file contents to allow thumbnail preview
}
public void setDataPoint(DataPoint inPoint)
{
_dataPoint = inPoint;
+ // set status according to point
+ if (inPoint == null)
+ {
+ setCurrentStatus(PhotoStatus.NOT_CONNECTED);
+ }
+ else
+ {
+ setCurrentStatus(PhotoStatus.CONNECTED);
+ }
}
/**
return _dataPoint;
}
+ /**
+ * @param inTimestamp Timestamp of photo
+ */
+ public void setTimestamp(Timestamp inTimestamp)
+ {
+ _timestamp = inTimestamp;
+ }
+
+ /**
+ * @return timestamp of photo
+ */
+ public Timestamp getTimestamp()
+ {
+ return _timestamp;
+ }
+
+ /**
+ * Calculate the size of the image (slow)
+ */
+ private void calculateSize()
+ {
+ ImageIcon icon = new ImageIcon(_file.getAbsolutePath());
+ int width = icon.getIconWidth();
+ int height = icon.getIconHeight();
+ if (width > 0 && height > 0)
+ {
+ _size = new Dimension(width, height);
+ }
+ }
+
+ /**
+ * @return size of image as Dimension object
+ */
+ public Dimension getSize()
+ {
+ if (_size == null)
+ {
+ calculateSize();
+ }
+ return _size;
+ }
+
+ /**
+ * @return width of the image, if known
+ */
+ public int getWidth()
+ {
+ if (_size == null)
+ {
+ calculateSize();
+ if (_size == null) {return -1;}
+ }
+ return _size.width;
+ }
+
+ /**
+ * @return height of the image, if known
+ */
+ public int getHeight()
+ {
+ if (_size == null)
+ {
+ calculateSize();
+ if (_size == null) {return -1;}
+ }
+ return _size.height;
+ }
+
+ /**
+ * @param inStatus status of photo when loaded
+ */
+ public void setOriginalStatus(byte inStatus)
+ {
+ _originalStatus = inStatus;
+ _currentStatus = inStatus;
+ }
+
+ /**
+ * @return status of photo when it was loaded
+ */
+ public byte getOriginalStatus()
+ {
+ return _originalStatus;
+ }
+
+ /**
+ * @return current status of photo
+ */
+ public byte getCurrentStatus()
+ {
+ return _currentStatus;
+ }
+ /**
+ * @param inStatus current status of photo
+ */
+ public void setCurrentStatus(byte inStatus)
+ {
+ _currentStatus = inStatus;
+ }
+
+
+ /**
+ * Delete the cached data when the Photo is no longer needed
+ */
+ public void resetCachedData()
+ {
+ _size = null;
+ // remove thumbnail too
+ }
+
/**
* Check if a Photo object refers to the same File as another
* @param inOther other Photo object
{
private ArrayList _photos = null;
+ /**
+ * Empty constructor
+ */
+ public PhotoList()
+ {
+ this(null);
+ }
+
+ /**
+ * Constructor
+ * @param inList ArrayList containing Photo objects
+ */
+ private PhotoList(ArrayList inList)
+ {
+ _photos = inList;
+ }
+
+
/**
* @return the number of photos in the list
*/
/**
- * Add a List of Photos
- * @param inList List containing Photo objects
+ * Add a Photo to the list
+ * @param inPhoto Photo object to add
*/
public void addPhoto(Photo inPhoto)
{
- // Make sure array is initialised
- if (_photos == null)
+ if (inPhoto != null)
{
- _photos = new ArrayList();
+ // Make sure array is initialised
+ if (_photos == null)
+ {
+ _photos = new ArrayList();
+ }
+ // Add the photo
+ _photos.add(inPhoto);
}
- // Add the photo
+ }
+
+
+ /**
+ * Add a Photo to the list
+ * @param inPhoto Photo object to add
+ * @param inIndex index at which to add photo
+ */
+ public void addPhoto(Photo inPhoto, int inIndex)
+ {
if (inPhoto != null)
{
- _photos.add(inPhoto);
+ // Make sure array is initialised
+ if (_photos == null)
+ {
+ _photos = new ArrayList();
+ }
+ // Add the photo
+ _photos.add(inIndex, inPhoto);
+ }
+ }
+
+
+ /**
+ * Remove the selected photo from the list
+ * @param inIndex index number to remove
+ */
+ public void deletePhoto(int inIndex)
+ {
+ // Maybe throw exception if this fails?
+ if (_photos != null)
+ {
+ _photos.remove(inIndex);
}
}
* @return true if it's already in the list
*/
public boolean contains(Photo inPhoto)
+ {
+ return (getPhotoIndex(inPhoto) > -1);
+ }
+
+
+ /**
+ * Get the index of the given Photo
+ * @param inPhoto Photo object to check
+ * @return index of this Photo in the list, or -1 if not found
+ */
+ public int getPhotoIndex(Photo inPhoto)
{
// Check if we need to check
- if (getNumPhotos() <= 0 || inPhoto == null || inPhoto.getFile() == null)
- return false;
+ int numPhotos = getNumPhotos();
+ if (numPhotos <= 0 || inPhoto == null || inPhoto.getFile() == null)
+ return -1;
// Loop around photos in list
- for (int i=0; i<getNumPhotos(); i++)
+ Photo foundPhoto = null;
+ for (int i=0; i<numPhotos; i++)
{
- if (getPhoto(i) != null && getPhoto(i).equals(inPhoto))
+ foundPhoto = getPhoto(i);
+ if (foundPhoto != null && foundPhoto.equals(inPhoto))
{
- return true;
+ return i;
}
}
// not found
- return false;
+ return -1;
}
if (inIndex <= 0)
{
// delete whole list
- _photos.clear();
+ if (_photos != null) {_photos.clear();}
}
else
{
}
}
+
/**
* @return array of file names
*/
}
return names;
}
+
+
+ /**
+ * @return true if photo list contains correlated photos
+ */
+ public boolean hasCorrelatedPhotos()
+ {
+ int numPhotos = getNumPhotos();
+ boolean hasCorrelated = false;
+ // Loop over photos in list
+ for (int i=0; i<numPhotos && !hasCorrelated; i++)
+ {
+ if (getPhoto(i).getDataPoint() != null)
+ hasCorrelated = true;
+ }
+ return hasCorrelated;
+ }
+
+
+ /**
+ * Remove all correlated photos from the list
+ */
+ public void removeCorrelatedPhotos()
+ {
+ int numPhotos = getNumPhotos();
+ if (numPhotos > 0)
+ {
+ // Construct new list to copy into
+ ArrayList listCopy = new ArrayList();
+ // Loop over photos in list
+ for (int i=0; i<numPhotos; i++)
+ {
+ // Copy photo if it has no point
+ Photo photo = getPhoto(i);
+ if (photo != null)
+ {
+ if (photo.getDataPoint() == null)
+ listCopy.add(photo);
+ else
+ photo.resetCachedData();
+ }
+ }
+ // Switch reference to new list
+ _photos = listCopy;
+ }
+ }
+
+
+ /**
+ * @return clone of photo list contents
+ */
+ public PhotoList cloneList()
+ {
+ if (_photos == null) return this;
+ return new PhotoList((ArrayList) _photos.clone());
+ }
+
+
+ /**
+ * Restore contents from other PhotoList
+ * @param inOther PhotoList with cloned contents
+ */
+ public void restore(PhotoList inOther)
+ {
+ if (inOther.getNumPhotos() == 0)
+ {
+ // List is empty
+ _photos = null;
+ }
+ else
+ {
+ // Clear array and copy over from other one
+ _photos.clear();
+ _photos.addAll(inOther._photos);
+ }
+ }
}
--- /dev/null
+package tim.prune.data;
+
+/**
+ * Interface to hold constants for photo status
+ */
+public interface PhotoStatus
+{
+ public static final byte NOT_CONNECTED = 0;
+ public static final byte TAGGED = 1;
+ public static final byte CONNECTED = 2;
+}
private int _currentPoint = -1;
private boolean _valid = false;
private int _startIndex = -1, _endIndex = -1;
+ private int _currentPhotoIndex = -1;
private IntegerRange _altitudeRange = null;
private int _climb = -1, _descent = -1;
private int _altitudeFormat = Altitude.FORMAT_NONE;
{
_currentPoint = -1;
deselectRange();
+ deselectPhoto();
}
}
+ /**
+ * Deselect photo
+ */
+ public void deselectPhoto()
+ {
+ _currentPhotoIndex = -1;
+ check();
+ }
+
+
+ /**
+ * Select the specified photo and point
+ * @param inPhotoIndex index of selected photo in PhotoList
+ * @param inPointIndex index of selected point
+ */
+ public void selectPhotoAndPoint(int inPhotoIndex, int inPointIndex)
+ {
+ _currentPhotoIndex = inPhotoIndex;
+ if (inPointIndex > -1)
+ {
+ // select associated point, if any
+ selectPoint(inPointIndex);
+ }
+ else
+ {
+ // Check if not already done
+ check();
+ }
+ }
+
+
+ /**
+ * @return currently selected photo index
+ */
+ public int getCurrentPhotoIndex()
+ {
+ // System.out.println("Current photo index = " + _currentPhotoIndex);
+ return _currentPhotoIndex;
+ }
+
+
/**
* Check that the selection still makes sense
* and fire update message to listeners
*/
public Timestamp(String inString)
{
+ // TODO: Does it really help to store timestamps in seconds rather than ms?
if (inString != null && !inString.equals(""))
{
// Try to parse into a long
// Lastly, check garmin offset
if (diff4 < smallestDiff)
{
- // milliseconds since garmin offset
+ // seconds since garmin offset
_seconds = rawValue + GARTRIP_OFFSET;
}
_valid = true;
}
+ /**
+ * Constructor giving each field value individually
+ * @param inYear year
+ * @param inMonth month, beginning with 1
+ * @param inDay day of month, beginning with 1
+ * @param inHour hour of day, 0-24
+ * @param inMinute minute
+ * @param inSecond seconds
+ */
+ public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.YEAR, inYear);
+ cal.set(Calendar.MONTH, inMonth - 1);
+ cal.set(Calendar.DAY_OF_MONTH, inDay);
+ cal.set(Calendar.HOUR_OF_DAY, inHour);
+ cal.set(Calendar.MINUTE, inMinute);
+ cal.set(Calendar.SECOND, inSecond);
+ cal.set(Calendar.MILLISECOND, 0);
+ _seconds = cal.getTimeInMillis() / 1000;
+ _valid = true;
+ }
+
+
+ /**
+ * Constructor giving millis since 1970
+ * @param inMillis
+ */
+ public Timestamp(long inMillis)
+ {
+ _seconds = inMillis / 1000;
+ _valid = true;
+ }
+
+
/**
* @return true if timestamp is valid
*/
*/
public void load(Field[] inFieldArray, Object[][] inPointArray, int inAltFormat)
{
+ if (inFieldArray == null || inPointArray == null)
+ {
+ _numPoints = 0;
+ return;
+ }
// copy field list
_masterFieldList = new FieldList(inFieldArray);
// make DataPoint object from each point in inPointList
for (int i=0; i<_numPoints; i++)
{
boolean keepPoint = true;
- if (!_dataPoints[i].isWaypoint())
+ // Don't delete waypoints or photo points
+ if (!_dataPoints[i].isWaypoint() && _dataPoints[i].getPhoto() == null)
{
// go through newPointArray to check for range
for (int j=0; j<numCopied && keepPoint; j++)
*/
public boolean deleteRange(int inStart, int inEnd)
{
- // TODO: Check for deleting photos?
if (inStart < 0 || inEnd < 0 || inEnd < inStart)
{
// no valid range selected so can't delete
System.arraycopy(_dataPoints, inEnd + 1, newPointArray, inStart,
_numPoints - inEnd - 1);
}
- // Copy points over original array (careful!)
+ // Copy points over original array
_dataPoints = newPointArray;
_numPoints -= numToDelete;
// needs to be scaled again
return true;
}
- // TODO: Need to rearrange photo points too?
/**
* Interpolate extra points between two selected ones
// loop over points and copy all waypoints into list
for (int i=0; i<=_numPoints-1; i++)
{
- if (_dataPoints[i].isWaypoint())
+ if (_dataPoints[i] != null && _dataPoints[i].isWaypoint())
{
inList.add(_dataPoints[i]);
}
}
}
- // TODO: Make similar method to get list of photos
/**
{
if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
{
+ // remember if coordinates have changed
+ boolean coordsChanged = false;
// go through edits one by one
int numEdits = inEditList.getNumEdits();
for (int i=0; i<numEdits; i++)
{
FieldEdit edit = inEditList.getEdit(i);
inPoint.setFieldValue(edit.getField(), edit.getValue());
+ // check coordinates
+ coordsChanged |= (edit.getField().equals(Field.LATITUDE)
+ || edit.getField().equals(Field.LONGITUDE) || edit.getField().equals(Field.ALTITUDE));
+ }
+ // set photo status if coordinates have changed
+ if (inPoint.getPhoto() != null && coordsChanged)
+ {
+ inPoint.getPhoto().setCurrentStatus(PhotoStatus.CONNECTED);
}
- // possibly needs to be scaled again
+ // point possibly needs to be scaled again
_scaled = false;
// trigger listeners
_broker.informSubscribers();
private Track _track = null;
private Selection _selection = null;
private FileInfo _fileInfo = null;
- // TODO: How to store photos? In separate list to be maintained or dynamic? Only store pointless photos?
private PhotoList _photoList = null;
return _track.getPoint(_selection.getCurrentPointIndex());
}
+ /**
+ * Get the currently selected photo, if any
+ * @return Photo if selected, otherwise null
+ */
+ public Photo getCurrentPhoto()
+ {
+ return _photoList.getPhoto(_selection.getCurrentPhotoIndex());
+ }
+
/**
* Load the specified data into the Track
/**
* Add a List of Photos
* @param inList List containing Photo objects
- * @return number of photos added
+ * @return array containing number of photos and number of points added
*/
- public int addPhotos(List inList)
+ public int[] addPhotos(List inList)
{
- // Firstly count number to add to make array
+ // TODO: Should photos be sorted at load-time, either by filename or date?
+ // Firstly count number of points and photos to add
int numPhotosToAdd = 0;
+ int numPointsToAdd = 0;
if (inList != null && !inList.isEmpty())
{
for (int i=0; i<inList.size(); i++)
if (photo != null && !_photoList.contains(photo))
{
numPhotosToAdd++;
+ if (photo.getDataPoint() != null)
+ {
+ numPointsToAdd++;
+ }
}
}
catch (ClassCastException ce) {}
// If there are any photos to add, add them
if (numPhotosToAdd > 0)
{
- DataPoint[] dataPoints = new DataPoint[numPhotosToAdd];
+ DataPoint[] dataPoints = new DataPoint[numPointsToAdd];
int pointNum = 0;
+ boolean hasAltitude = false;
// Add each Photo in turn
for (int i=0; i<inList.size(); i++)
{
Photo photo = (Photo) inList.get(i);
if (photo != null && !_photoList.contains(photo))
{
+ // Add photo
_photoList.addPhoto(photo);
- dataPoints[pointNum] = photo.getDataPoint();
- pointNum++;
+ // Add point if there is one
+ if (photo.getDataPoint() != null)
+ {
+ dataPoints[pointNum] = photo.getDataPoint();
+ // Check if any points have altitudes
+ hasAltitude |= (photo.getDataPoint().getAltitude() != null);
+ pointNum++;
+ }
}
}
catch (ClassCastException ce) {}
}
- _track.appendPoints(dataPoints);
+ if (numPointsToAdd > 0)
+ {
+ // add points to track
+ _track.appendPoints(dataPoints);
+ // modify track field list
+ _track.getFieldList().extendList(Field.LATITUDE);
+ _track.getFieldList().extendList(Field.LONGITUDE);
+ if (hasAltitude) {_track.getFieldList().extendList(Field.ALTITUDE);}
+ }
}
- return numPhotosToAdd;
+ int[] result = {numPhotosToAdd, numPointsToAdd};
+ return result;
}
*/
public boolean deleteRange()
{
- // TODO: Check whether to delete photos associated with this range
- int currPoint = _selection.getCurrentPointIndex();
int startSel = _selection.getStart();
int endSel = _selection.getEnd();
boolean answer = _track.deleteRange(startSel, endSel);
{
if (_track.deletePoint(_selection.getCurrentPointIndex()))
{
- // TODO: Check whether to delete photo associated with this point
_selection.modifyPointDeleted();
_broker.informSubscribers();
return true;
}
+ /**
+ * Delete the currently selected photo and optionally its point too
+ * @param inPointToo true to also delete associated point
+ * @return true if delete successful
+ */
+ public boolean deleteCurrentPhoto(boolean inPointToo)
+ {
+ // delete currently selected photo
+ int photoIndex = _selection.getCurrentPhotoIndex();
+ if (photoIndex >= 0)
+ {
+ Photo photo = _photoList.getPhoto(photoIndex);
+ _photoList.deletePhoto(photoIndex);
+ // has it got a point?
+ if (photo.getDataPoint() != null)
+ {
+ if (inPointToo)
+ {
+ // delete point
+ int pointIndex = _track.getPointIndex(photo.getDataPoint());
+ _track.deletePoint(pointIndex);
+ }
+ else
+ {
+ // disconnect point from photo
+ photo.getDataPoint().setPhoto(null);
+ photo.setDataPoint(null);
+ }
+ }
+ // update subscribers
+ _selection.modifyPointDeleted();
+ _broker.informSubscribers();
+ }
+ return true;
+ }
+
+
/**
* Compress the track to the given resolution
* @param inResolution resolution
// give to selection
_selection.selectPoint(index);
}
+
+ /**
+ * Select the given Photo and its point if any
+ * @param inPhotoIndex index of photo to select
+ */
+ public void selectPhoto(int inPhotoIndex)
+ {
+ // Find Photo object
+ Photo photo = _photoList.getPhoto(inPhotoIndex);
+ if (photo != null)
+ {
+ // Find point object and its index
+ int pointIndex = _track.getPointIndex(photo.getDataPoint());
+ // give to selection object
+ _selection.selectPhotoAndPoint(inPhotoIndex, pointIndex);
+ }
+ else
+ {
+ // no photo, just reset selection
+ _selection.selectPhotoAndPoint(-1, -1);
+ }
+ }
+
+
+ /**
+ * Fire a trigger to all data subscribers
+ */
+ public void triggerUpdate()
+ {
+ _broker.informSubscribers();
+ }
}
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
-import tim.prune.data.Track;
/**
* Class to manage the display and editing of waypoint names
private App _app = null;
private JFrame _parentFrame = null;
private JDialog _dialog = null;
- private Track _track = null;
private DataPoint _point = null;
private JTextField _nameField = null;
private JButton _okButton = null;
/**
* Show the edit point name dialog
- * @param inTrack track object
* @param inPoint point to edit
*/
- public void showDialog(Track inTrack, DataPoint inPoint)
+ public void showDialog(DataPoint inPoint)
{
- _track = inTrack;
_point = inPoint;
_dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.pointnameedit.title"), true);
_dialog.setLocationRelativeTo(_parentFrame);
panel.setLayout(new BorderLayout());
// Create GUI layout for point name editor
JPanel centrePanel = new JPanel();
- // centrePanel.set
centrePanel.add(new JLabel(I18nManager.getText("dialog.pointnameedit.name") + ":"));
+ // Make listener to react to ok being pressed
+ ActionListener okActionListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ // Check for empty name
+ if (_nameField.getText().length() > 0)
+ {
+ // update App with edit
+ confirmEdit();
+ _dialog.dispose();
+ }
+ }
+ };
_nameField = new JTextField(inName, 12);
_nameField.addKeyListener(new KeyAdapter() {
- public void keyTyped(KeyEvent e)
+ public void keyReleased(KeyEvent e)
{
- _okButton.setEnabled(true);
+ // close dialog if escape pressed
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
+ {
+ _dialog.dispose();
+ }
+ // Enable ok button if name not empty
+ _okButton.setEnabled(_nameField.getText().length() > 0);
}
});
+ _nameField.addActionListener(okActionListener);
centrePanel.add(_nameField);
panel.add(centrePanel);
JPanel rightPanel = new JPanel();
{
_nameField.setText(_nameField.getText().toUpperCase());
_okButton.setEnabled(true);
+ _nameField.requestFocus();
}
});
rightPanel.add(upperButton);
{
_nameField.setText(_nameField.getText().toLowerCase());
_okButton.setEnabled(true);
+ _nameField.requestFocus();
}
});
rightPanel.add(lowerButton);
{
_nameField.setText(sentenceCase(_nameField.getText()));
_okButton.setEnabled(true);
+ _nameField.requestFocus();
}
});
rightPanel.add(sentenceButton);
lowerPanel.add(cancelButton);
_okButton = new JButton(I18nManager.getText("button.ok"));
_okButton.setEnabled(false);
- _okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- // update App with edit
- confirmEdit();
- _dialog.dispose();
- }
- });
+ _okButton.addActionListener(okActionListener);
lowerPanel.add(_okButton);
panel.add(lowerPanel, BorderLayout.SOUTH);
return panel;
package tim.prune.gui;
+import java.awt.BorderLayout;
import java.awt.Component;
+import java.awt.FlowLayout;
import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import tim.prune.ExternalTools;
import tim.prune.GpsPruner;
import tim.prune.I18nManager;
+import tim.prune.threedee.WindowFactory;
/**
* Class to represent the "About" popup window
*/
public class AboutScreen extends JDialog
{
+ JButton _okButton = null;
/**
* Constructor
private Component makeContents()
{
JPanel mainPanel = new JPanel();
- mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
- mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ mainPanel.setLayout(new BorderLayout());
+
+ JTabbedPane tabPane = new JTabbedPane();
+ mainPanel.add(tabPane, BorderLayout.CENTER);
+
+ JPanel aboutPanel = new JPanel();
+ aboutPanel.setLayout(new BoxLayout(aboutPanel, BoxLayout.Y_AXIS));
+ aboutPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JLabel titleLabel = new JLabel("Prune");
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
titleLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
- mainPanel.add(titleLabel);
+ aboutPanel.add(titleLabel);
JLabel versionLabel = new JLabel(I18nManager.getText("dialog.about.version") + ": " + GpsPruner.VERSION_NUMBER);
versionLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
- mainPanel.add(versionLabel);
+ aboutPanel.add(versionLabel);
JLabel buildLabel = new JLabel(I18nManager.getText("dialog.about.build") + ": " + GpsPruner.BUILD_NUMBER);
buildLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
- mainPanel.add(buildLabel);
- mainPanel.add(new JLabel(" "));
+ aboutPanel.add(buildLabel);
+ aboutPanel.add(new JLabel(" "));
StringBuffer descBuffer = new StringBuffer();
descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext1")).append("</p>");
descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext2")).append("</p>");
descPane.setOpaque(false);
descPane.setAlignmentX(JEditorPane.CENTER_ALIGNMENT);
- mainPanel.add(descPane);
- mainPanel.add(new JLabel(" "));
- JButton okButton = new JButton(I18nManager.getText("button.ok"));
- okButton.addActionListener(new ActionListener()
+ aboutPanel.add(descPane);
+ aboutPanel.add(new JLabel(" "));
+ tabPane.add(I18nManager.getText("dialog.about.title"), aboutPanel);
+
+ // Second pane for system info
+ JPanel sysInfoPanel = new JPanel();
+ GridBagLayout gridBag = new GridBagLayout();
+ sysInfoPanel.setLayout(gridBag);
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.weightx = 0.0; constraints.weighty = 0.0;
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.systeminfo.os") + " : "),
+ 0, 0);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(System.getProperty("os.name")),
+ 1, 0);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.systeminfo.java") + " : "),
+ 0, 1);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(System.getProperty("java.runtime.version")),
+ 1, 1);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.systeminfo.java3d") + " : "),
+ 0, 2);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText(WindowFactory.isJava3dEnabled()?"dialog.about.yes":"dialog.about.no")),
+ 1, 2);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.systeminfo.povray") + " : "),
+ 0, 3);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText(ExternalTools.isPovrayInstalled()?"dialog.about.yes":"dialog.about.no")),
+ 1, 3);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.systeminfo.exiftool") + " : "),
+ 0, 4);
+ addToGridBagPanel(sysInfoPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText(ExternalTools.isExiftoolInstalled()?"dialog.about.yes":"dialog.about.no")),
+ 1, 4);
+ tabPane.add(I18nManager.getText("dialog.about.systeminfo"), sysInfoPanel);
+
+ // Third pane for credits
+ JPanel creditsPanel = new JPanel();
+ gridBag = new GridBagLayout();
+ creditsPanel.setLayout(gridBag);
+ constraints = new GridBagConstraints();
+ constraints.weightx = 0.0; constraints.weighty = 0.0;
+
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.code") + " : "),
+ 0, 0);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("activityworkshop.net"),
+ 1, 0);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.exifcode") + " : "),
+ 0, 1);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Drew Noakes"),
+ 1, 1);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.icons") + " : "),
+ 0, 2);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Eclipse"),
+ 1, 2);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "),
+ 0, 3);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Open Office, Gpsdrive, Babelfish, Leo"),
+ 1, 3);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.devtools") + " : "),
+ 0, 4);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Mandriva Linux, Sun Java, Eclipse, Svn, Gimp"),
+ 1, 4);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.othertools") + " : "),
+ 0, 5);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Garble, Kate, Povray, Inkscape, Google Earth"),
+ 1, 5);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel(I18nManager.getText("dialog.about.credits.thanks") + " : "),
+ 0, 6);
+ addToGridBagPanel(creditsPanel, gridBag, constraints,
+ new JLabel("Friends and loved ones, for encouragement and support"),
+ 1, 6);
+ tabPane.add(I18nManager.getText("dialog.about.credits"), creditsPanel);
+
+ // OK button at the bottom
+ JPanel okPanel = new JPanel();
+ okPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ _okButton = new JButton(I18nManager.getText("button.ok"));
+ _okButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
dispose();
}
});
- okButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
- mainPanel.add(okButton);
+ _okButton.addKeyListener(new KeyListener() {
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {dispose();}
+ }
+ public void keyTyped(KeyEvent e) {}
+ public void keyReleased(KeyEvent e) {}
+ });
+ okPanel.add(_okButton);
+ mainPanel.add(okPanel, BorderLayout.SOUTH);
return mainPanel;
}
+ /**
+ * Helper function to reduce complexity of gui making code
+ * when adding labels to a GridBagLayout
+ * @param inPanel panel to add to
+ * @param inLayout GridBagLayout object
+ * @param inConstraints GridBagConstraints object
+ * @param inLabel label to add
+ * @param inX grid x
+ * @param inY grid y
+ */
+ private static void addToGridBagPanel(JPanel inPanel, GridBagLayout inLayout, GridBagConstraints inConstraints,
+ JLabel inLabel, int inX, int inY)
+ {
+ // set x and y in constraints
+ inConstraints.gridx = inX;
+ inConstraints.gridy = inY;
+ // set anchor
+ inConstraints.anchor = (inX == 0?GridBagConstraints.EAST:GridBagConstraints.WEST);
+ // set constraints to label
+ inLayout.setConstraints(inLabel, inConstraints);
+ // add label to panel
+ inPanel.add(inLabel);
+ }
+
/**
* Show window
pack();
// setSize(300,200);
super.show();
+ _okButton.requestFocus();
}
}
import java.awt.BorderLayout;
import java.awt.Component;
-import java.awt.FlowLayout;
+import java.awt.Dimension;
import java.awt.Font;
-import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.awt.event.AdjustmentEvent;
-import java.awt.event.AdjustmentListener;
import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
-import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
-import javax.swing.JList;
import javax.swing.JPanel;
-import javax.swing.JScrollBar;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
import javax.swing.border.EtchedBorder;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-
-import tim.prune.App;
import tim.prune.DataSubscriber;
import tim.prune.I18nManager;
import tim.prune.data.Altitude;
import tim.prune.data.DataPoint;
import tim.prune.data.Distance;
import tim.prune.data.IntegerRange;
+import tim.prune.data.Photo;
import tim.prune.data.Selection;
import tim.prune.data.TrackInfo;
*/
public class DetailsDisplay extends GenericDisplay
{
- // App object to be notified of editing commands
- private App _app = null;
-
- // Track details
- private JLabel _trackpointsLabel = null;
- private JLabel _filenameLabel = null;
// Point details
private JLabel _indexLabel = null;
private JLabel _latLabel = null, _longLabel = null;
private JLabel _altLabel = null, _nameLabel = null;
- private JLabel _timeLabel = null, _photoFileLabel = null;
- // Scroll bar
- private JScrollBar _scroller = null;
- private boolean _ignoreScrollEvents = false;
- // Button panel
- private JButton _startRangeButton = null, _endRangeButton = null;
- private JButton _deletePointButton = null, _deleteRangeButton = null;
+ private JLabel _timeLabel = null;
// Range details
private JLabel _rangeLabel = null;
private JLabel _distanceLabel = null, _durationLabel = null;
private JLabel _altRangeLabel = null, _updownLabel = null;
- // Photos
- private JList _photoList = null;
- private PhotoListModel _photoListModel = null;
- // Waypoints
- private JList _waypointList = null;
- private WaypointListModel _waypointListModel = null;
+
+ // Photo details
+ private JLabel _photoLabel = null;
+ private PhotoThumbnail _photoThumbnail = null;
+
// Units
private JComboBox _unitsDropdown = null;
// Formatter
private static final String LABEL_RANGE_DESCENT = ", " + I18nManager.getText("details.range.descent") + ": ";
private static String LABEL_POINT_ALTITUDE_UNITS = null;
private static int LABEL_POINT_ALTITUDE_FORMAT = Altitude.FORMAT_NONE;
- // scrollbar interval
- private static final int SCROLLBAR_INTERVAL = 50;
/**
* Constructor
- * @param inApp App object for callbacks
* @param inTrackInfo Track info object
*/
- public DetailsDisplay(App inApp, TrackInfo inTrackInfo)
+ public DetailsDisplay(TrackInfo inTrackInfo)
{
super(inTrackInfo);
- _app = inApp;
setLayout(new BorderLayout());
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
- // Track details panel
- JPanel trackDetailsPanel = new JPanel();
- trackDetailsPanel.setLayout(new BoxLayout(trackDetailsPanel, BoxLayout.Y_AXIS));
- trackDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
- BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
- );
- JLabel trackDetailsLabel = new JLabel(I18nManager.getText("details.trackdetails"));
- Font biggerFont = trackDetailsLabel.getFont();
- biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
- trackDetailsLabel.setFont(biggerFont);
- trackDetailsPanel.add(trackDetailsLabel);
- _trackpointsLabel = new JLabel(I18nManager.getText("details.notrack"));
- trackDetailsPanel.add(_trackpointsLabel);
- _filenameLabel = new JLabel("");
- trackDetailsPanel.add(_filenameLabel);
// Point details panel
JPanel pointDetailsPanel = new JPanel();
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
);
JLabel pointDetailsLabel = new JLabel(I18nManager.getText("details.pointdetails"));
+ Font biggerFont = pointDetailsLabel.getFont();
+ biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
pointDetailsLabel.setFont(biggerFont);
pointDetailsPanel.add(pointDetailsLabel);
_indexLabel = new JLabel(I18nManager.getText("details.nopointselection"));
pointDetailsPanel.add(_altLabel);
_timeLabel = new JLabel("");
pointDetailsPanel.add(_timeLabel);
- _photoFileLabel = new JLabel("");
- pointDetailsPanel.add(_photoFileLabel);
_nameLabel = new JLabel("");
pointDetailsPanel.add(_nameLabel);
pointDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
- // Scroll bar
- _scroller = new JScrollBar(JScrollBar.HORIZONTAL, 0, SCROLLBAR_INTERVAL, 0, 100);
- _scroller.addAdjustmentListener(new AdjustmentListener() {
- public void adjustmentValueChanged(AdjustmentEvent e)
- {
- selectPoint(e.getValue());
- }
- });
- _scroller.setEnabled(false);
-
- // Button panel
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new GridLayout(2, 2, 3, 3));
- _startRangeButton = new JButton(I18nManager.getText("button.startrange"));
- _startRangeButton.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- _trackInfo.getSelection().selectRangeStart();
- }
- });
- _startRangeButton.setEnabled(false);
- buttonPanel.add(_startRangeButton);
- _endRangeButton = new JButton(I18nManager.getText("button.endrange"));
- _endRangeButton.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- _trackInfo.getSelection().selectRangeEnd();
- }
- });
- _endRangeButton.setEnabled(false);
- buttonPanel.add(_endRangeButton);
- _deletePointButton = new JButton(I18nManager.getText("button.deletepoint"));
- _deletePointButton.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- _app.deleteCurrentPoint();
- }
- });
- _deletePointButton.setEnabled(false);
- buttonPanel.add(_deletePointButton);
- _deleteRangeButton = new JButton(I18nManager.getText("button.deleterange"));
- _deleteRangeButton.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- _app.deleteSelectedRange();
- }
- });
- _deleteRangeButton.setEnabled(false);
- buttonPanel.add(_deleteRangeButton);
- buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-
// range details panel
- JPanel otherDetailsPanel = new JPanel();
- otherDetailsPanel.setLayout(new BoxLayout(otherDetailsPanel, BoxLayout.Y_AXIS));
- otherDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
+ JPanel rangeDetailsPanel = new JPanel();
+ rangeDetailsPanel.setLayout(new BoxLayout(rangeDetailsPanel, BoxLayout.Y_AXIS));
+ rangeDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
);
-
- JLabel otherDetailsLabel = new JLabel(I18nManager.getText("details.rangedetails"));
- otherDetailsLabel.setFont(biggerFont);
- otherDetailsPanel.add(otherDetailsLabel);
+ JLabel rangeDetailsLabel = new JLabel(I18nManager.getText("details.rangedetails"));
+ rangeDetailsLabel.setFont(biggerFont);
+ rangeDetailsPanel.add(rangeDetailsLabel);
_rangeLabel = new JLabel(I18nManager.getText("details.norangeselection"));
- otherDetailsPanel.add(_rangeLabel);
+ rangeDetailsPanel.add(_rangeLabel);
_distanceLabel = new JLabel("");
- otherDetailsPanel.add(_distanceLabel);
+ rangeDetailsPanel.add(_distanceLabel);
_durationLabel = new JLabel("");
- otherDetailsPanel.add(_durationLabel);
+ rangeDetailsPanel.add(_durationLabel);
_altRangeLabel = new JLabel("");
- otherDetailsPanel.add(_altRangeLabel);
+ rangeDetailsPanel.add(_altRangeLabel);
_updownLabel = new JLabel("");
- otherDetailsPanel.add(_updownLabel);
- otherDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ rangeDetailsPanel.add(_updownLabel);
+ rangeDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
- // Add tab panel for waypoints / photos
- JPanel waypointsPanel = new JPanel();
- waypointsPanel.setLayout(new BoxLayout(waypointsPanel, BoxLayout.Y_AXIS));
- waypointsPanel.setBorder(BorderFactory.createCompoundBorder(
+ // range details panel
+ JPanel photoDetailsPanel = new JPanel();
+ photoDetailsPanel.setLayout(new BoxLayout(photoDetailsPanel, BoxLayout.Y_AXIS));
+ photoDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
);
- JTabbedPane tabPane = new JTabbedPane();
- _waypointListModel = new WaypointListModel(_trackInfo.getTrack());
- _waypointList = new JList(_waypointListModel);
- _waypointList.setVisibleRowCount(5);
- _waypointList.addListSelectionListener(new ListSelectionListener() {
- public void valueChanged(ListSelectionEvent e)
- {
- if (!e.getValueIsAdjusting()) selectWaypoint(_waypointList.getSelectedIndex());
- }});
- tabPane.addTab(I18nManager.getText("details.waypointsphotos.waypoints"), new JScrollPane(_waypointList));
- _photoListModel = new PhotoListModel(_trackInfo.getPhotoList());
- _photoList = new JList(_photoListModel);
- _photoList.setVisibleRowCount(5);
- _photoList.addListSelectionListener(new ListSelectionListener() {
- public void valueChanged(ListSelectionEvent e)
- {
- if (!e.getValueIsAdjusting()) selectPhoto(_photoList.getSelectedIndex());
- }});
- // TODO: Re-add photos list after v2
- // tabPane.addTab(I18nManager.getText("details.waypointsphotos.photos"), new JScrollPane(_photoList));
- tabPane.setAlignmentX(Component.LEFT_ALIGNMENT);
- waypointsPanel.add(tabPane);
- waypointsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-
- // add the slider, point details, and the other details to the main panel
- mainPanel.add(buttonPanel);
- mainPanel.add(Box.createVerticalStrut(5));
- mainPanel.add(_scroller);
- mainPanel.add(Box.createVerticalStrut(5));
- mainPanel.add(trackDetailsPanel);
- mainPanel.add(Box.createVerticalStrut(5));
+ JLabel photoDetailsLabel = new JLabel(I18nManager.getText("details.photodetails"));
+ photoDetailsLabel.setFont(biggerFont);
+ photoDetailsPanel.add(photoDetailsLabel);
+ _photoLabel = new JLabel(I18nManager.getText("details.nophoto"));
+ photoDetailsPanel.add(_photoLabel);
+ _photoThumbnail = new PhotoThumbnail();
+ _photoThumbnail.setVisible(false);
+ _photoThumbnail.setPreferredSize(new Dimension(100, 100));
+ photoDetailsPanel.add(_photoThumbnail);
+
+ // add the details panels to the main panel
mainPanel.add(pointDetailsPanel);
mainPanel.add(Box.createVerticalStrut(5));
- mainPanel.add(otherDetailsPanel);
+ mainPanel.add(rangeDetailsPanel);
+ mainPanel.add(Box.createVerticalStrut(5));
+ mainPanel.add(photoDetailsPanel);
mainPanel.add(Box.createVerticalStrut(5));
- mainPanel.add(waypointsPanel);
// add the main panel at the top
add(mainPanel, BorderLayout.NORTH);
// Add units selection
JPanel lowerPanel = new JPanel();
- lowerPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
- lowerPanel.add(new JLabel(I18nManager.getText("details.distanceunits") + ": "));
+ lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.Y_AXIS));
+ JLabel unitsLabel = new JLabel(I18nManager.getText("details.distanceunits") + ": ");
+ unitsLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ lowerPanel.add(unitsLabel);
String[] distUnits = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")};
_unitsDropdown = new JComboBox(distUnits);
_unitsDropdown.addActionListener(new ActionListener() {
}
});
lowerPanel.add(_unitsDropdown);
+ _unitsDropdown.setAlignmentX(Component.LEFT_ALIGNMENT);
add(lowerPanel, BorderLayout.SOUTH);
}
- /**
- * Select the specified point
- * @param inValue value to select
- */
- private void selectPoint(int inValue)
- {
- if (_track != null && !_ignoreScrollEvents)
- {
- _trackInfo.getSelection().selectPoint(inValue);
- }
- }
-
-
- /**
- * Select the specified photo
- * @param inPhotoIndex index of selected photo
- */
- private void selectPhoto(int inPhotoIndex)
- {
- if (_photoListModel.getPhoto(inPhotoIndex) != null)
- {
- // TODO: Deselect the photo when another point is selected
- // TODO: show photo thumbnail
- // select associated point, if any
- DataPoint point = _photoListModel.getPhoto(inPhotoIndex).getDataPoint();
- if (point != null)
- {
- _trackInfo.selectPoint(point);
- }
- }
- }
-
-
- /**
- * Select the specified waypoint
- * @param inWaypointIndex index of selected waypoint
- */
- private void selectWaypoint(int inWaypointIndex)
- {
- if (inWaypointIndex >= 0)
- {
- _trackInfo.selectPoint(_waypointListModel.getWaypoint(inWaypointIndex));
- }
- }
-
-
/**
* Notification that Track has been updated
*/
public void dataUpdated(byte inUpdateType)
{
- // Update track data
- if (_track == null || _track.getNumPoints() <= 0)
- {
- _trackpointsLabel.setText(I18nManager.getText("details.notrack"));
- _filenameLabel.setText("");
- }
- else
- {
- _trackpointsLabel.setText(I18nManager.getText("details.track.points") + ": "
- + _track.getNumPoints());
- int numFiles = _trackInfo.getFileInfo().getNumFiles();
- if (numFiles == 1)
- {
- _filenameLabel.setText(I18nManager.getText("details.track.file") + ": "
- + _trackInfo.getFileInfo().getFilename());
- }
- else if (numFiles > 1)
- {
- _filenameLabel.setText(I18nManager.getText("details.track.numfiles") + ": "
- + numFiles);
- }
- else _filenameLabel.setText("");
- }
-
// Update current point data, if any
DataPoint currentPoint = _trackInfo.getCurrentPoint();
Selection selection = _trackInfo.getSelection();
_longLabel.setText("");
_altLabel.setText("");
_timeLabel.setText("");
- _photoFileLabel.setText("");
_nameLabel.setText("");
}
else
_timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
else
_timeLabel.setText("");
- if (currentPoint.getPhoto() != null && currentPoint.getPhoto().getFile() != null)
- {
- _photoFileLabel.setText(I18nManager.getText("details.photofile") + ": "
- + currentPoint.getPhoto().getFile().getName());
- }
- else
- _photoFileLabel.setText("");
String name = currentPoint.getWaypointName();
if (name != null && !name.equals(""))
{
else _nameLabel.setText("");
}
- // Update scroller settings
- _ignoreScrollEvents = true;
- if (_track == null || _track.getNumPoints() < 2)
- {
- // careful to avoid event loops here
- // _scroller.setValue(0);
- _scroller.setEnabled(false);
- }
- else
- {
- _scroller.setMaximum(_track.getNumPoints() + SCROLLBAR_INTERVAL);
- if (currentPointIndex >= 0)
- _scroller.setValue(currentPointIndex);
- _scroller.setEnabled(true);
- }
- _ignoreScrollEvents = false;
-
- // Update button panel
- boolean hasPoint = (_track != null && currentPointIndex >= 0);
- _startRangeButton.setEnabled(hasPoint);
- _endRangeButton.setEnabled(hasPoint);
- _deletePointButton.setEnabled(hasPoint);
- _deleteRangeButton.setEnabled(selection.hasRangeSelected());
-
// Update range details
if (_track == null || !selection.hasRangeSelected())
{
_updownLabel.setText("");
}
}
- // update waypoints and photos if necessary
- if ((inUpdateType |
- (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.WAYPOINTS_MODIFIED)) > 0)
- {
- _waypointListModel.fireChanged();
- }
- if ((inUpdateType |
- (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.PHOTOS_MODIFIED)) > 0)
- {
- _photoListModel.fireChanged();
- }
- // Deselect selected waypoint if selected point has since changed
- if (_waypointList.getSelectedIndex() >= 0)
+ // show photo details and thumbnail
+ Photo currentPhoto = _trackInfo.getPhotoList().getPhoto(_trackInfo.getSelection().getCurrentPhotoIndex());
+ if (_track == null || ( (currentPoint == null || currentPoint.getPhoto() == null) && currentPhoto == null))
{
- if (_trackInfo.getCurrentPoint() == null
- || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint()))
- {
- // point is selected in list but different from current point - deselect
- _waypointList.clearSelection();
- }
+ // no photo, hide details
+ _photoLabel.setText(I18nManager.getText("details.nophoto"));
+ _photoThumbnail.setVisible(false);
}
- // Do the same for the photos
- if (_photoList.getSelectedIndex() >= 0)
+ else
{
- if (_trackInfo.getCurrentPoint() == null
- || !_photoListModel.getPhoto(_photoList.getSelectedIndex()).getDataPoint().equals(_trackInfo.getCurrentPoint()))
- {
- // photo is selected in list but different from current point - deselect
- _photoList.clearSelection();
- }
+ if (currentPhoto == null) {currentPhoto = currentPoint.getPhoto();}
+ _photoLabel.setText(I18nManager.getText("details.photofile") + ": " + currentPhoto.getFile().getName());
+ _photoThumbnail.setVisible(true);
+ _photoThumbnail.setPhoto(currentPhoto);
}
+ _photoThumbnail.repaint();
}
--- /dev/null
+package tim.prune.gui;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.image.Kernel;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Class for providing generic image processing functions
+ */
+public abstract class ImageUtils
+{
+ private static final float SMOOTH_FACTOR = 0.008f;
+ private static ConvolveOp CONVOLVER = null;
+
+ /** Static block for initialization */
+ static
+ {
+ float[] smoothMatrix = {
+ 0, SMOOTH_FACTOR, 0,
+ SMOOTH_FACTOR, 1-(SMOOTH_FACTOR*4), SMOOTH_FACTOR,
+ 0, SMOOTH_FACTOR, 0
+ };
+ CONVOLVER = new ConvolveOp(new Kernel(3, 3, smoothMatrix));
+ }
+
+
+ /**
+ * Create a scaled and smoothed image according to the specified size
+ * @param inImage image to scale
+ * @param inWidth width to scale to
+ * @param inHeight height to scale to
+ * @return BufferedImage containing scaled result
+ */
+ public static BufferedImage createScaledImage(Image inImage, int inWidth, int inHeight)
+ {
+ // create smaller image and force its loading
+ Image smallerImage = inImage.getScaledInstance(inWidth, inHeight, Image.SCALE_SMOOTH);
+ Image tempImage = new ImageIcon(smallerImage).getImage();
+ tempImage.getWidth(null);
+
+ // create buffered image to do transform
+ BufferedImage buffer = new BufferedImage(inWidth, inHeight, BufferedImage.TYPE_INT_RGB);
+ // copy scaled picture into buffer
+ Graphics buffG = buffer.getGraphics();
+ buffG.drawImage(smallerImage, 0, 0, inWidth, inHeight, null);
+ buffG.dispose();
+
+ // clear variables
+ smallerImage = null; tempImage = null;
+ // smooth scaled image using a normalized 3x3 matrix - taking next neighbour
+ buffer = CONVOLVER.filter(buffer, null);
+
+ return buffer;
+ }
+
+
+ /**
+ * Work out the max size of a thumbnail
+ * @param inOrigWidth width of original picture
+ * @param inOrigHeight height of original picture
+ * @param inMaxWidth max width of thumbnail
+ * @param inMaxHeight max height of thumbnail
+ * @return size of thumbnail as Dimension
+ */
+ public static Dimension getThumbnailSize(int inOrigWidth, int inOrigHeight, int inMaxWidth, int inMaxHeight)
+ {
+ if (inMaxWidth <= 0 || inMaxHeight <= 0)
+ {
+ //System.out.println("Can't do it - maxwidth=" + inMaxWidth + ", maxheight=" + inMaxHeight);
+ return new Dimension(0,0);
+ }
+ // work out maximum zoom ratio available so that thumbnail isn't too big
+ double xZoom = inMaxWidth * 1.0 / inOrigWidth;
+ double yZoom = inMaxHeight * 1.0 / inOrigHeight;
+ double zoom = (xZoom > yZoom?yZoom:xZoom);
+ // Don't make thumbnail bigger than picture
+ if (zoom > 1.0) {return new Dimension(inOrigWidth, inOrigHeight);}
+ // calculate new width and height
+ return new Dimension ((int) (zoom * inOrigWidth), (int) (zoom * inOrigHeight));
+ }
+}
private BufferedImage _image = null;
private JPopupMenu _popup = null;
private JCheckBoxMenuItem _autoPanMenuItem = null;
- private String _trackString = null;
private int _numPoints = -1;
private double _scale;
private double _offsetX, _offsetY, _zoomScale;
}
_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
g.drawImage(_image, 0, 0, width, height, COLOR_BG, null);
}
// Attempt to grab keyboard focus if possible
- this.requestFocus();
+ //this.requestFocus();
}
{
DataPoint point = _track.getPoint(i);
String waypointName = point.getWaypointName();
- if (waypointName != null && !waypointName.equals("") && numWaypointNamesShown < LIMIT_WAYPOINT_NAMES)
+ if (waypointName != null && !waypointName.equals(""))
{
+ // escape if nothing more to do
+ if (numWaypointNamesShown >= LIMIT_WAYPOINT_NAMES || _image == null) {break;}
+ // calculate coordinates of point
x = halfWidth + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale);
y = halfHeight - (int) ((_track.getY(i) - _offsetY) / _scale * _zoomScale);
if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH)
*/
private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
{
- // if (true) return true;
- for (int x=0; x<inWidth; x++)
+ try
{
- for (int y=0; y<inHeight; y++)
+ // loop over x coordinate of rectangle
+ for (int x=0; x<inWidth; x++)
{
- int pixelColor = _image.getRGB(inX + x, inY - y);
- if (pixelColor != -1) return true;
+ // loop over y coordinate of rectangle
+ for (int y=0; y<inHeight; y++)
+ {
+ int pixelColor = _image.getRGB(inX + x, inY - y);
+ if (pixelColor != -1) return true;
+ }
}
}
+ catch (NullPointerException e) {
+ // ignore null pointers, just return false
+ }
return false;
}
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;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
+import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import tim.prune.App;
import tim.prune.DataSubscriber;
import tim.prune.I18nManager;
+import tim.prune.data.PhotoList;
import tim.prune.data.Selection;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
/**
- * Class to manage the menu bar,
- * including enabling and disabling the menu items
+ * Class to manage the menu bar and tool bar,
+ * including enabling and disabling the items
*/
public class MenuManager implements DataSubscriber
{
private App _app = null;
private Track _track = null;
private Selection _selection = null;
+ private PhotoList _photos = null;
// Menu items which need enabling/disabling
- JMenuItem _saveItem = null;
- JMenuItem _exportKmlItem = null;
- JMenuItem _exportPovItem = null;
- JMenuItem _undoItem = null;
- JMenuItem _clearUndoItem = null;
- JMenuItem _editPointItem = null;
- JMenuItem _editWaypointNameItem = null;
- JMenuItem _deletePointItem = null;
- JMenuItem _deleteRangeItem = null;
- JMenuItem _deleteDuplicatesItem = null;
- JMenuItem _compressItem = null;
- JMenuItem _interpolateItem = null;
- JMenuItem _selectAllItem = null;
- JMenuItem _selectNoneItem = null;
- JMenuItem _show3dItem = null;
- JMenuItem _reverseItem = null;
- JMenu _rearrangeMenu = null;
- JMenuItem _rearrangeStartItem = null;
- JMenuItem _rearrangeEndItem = null;
- JMenuItem _rearrangeNearestItem = null;
+ private JMenuItem _saveItem = null;
+ private JMenuItem _exportKmlItem = null;
+ private JMenuItem _exportPovItem = null;
+ private JMenuItem _undoItem = null;
+ private JMenuItem _clearUndoItem = null;
+ private JMenuItem _editPointItem = null;
+ private JMenuItem _editWaypointNameItem = null;
+ private JMenuItem _deletePointItem = null;
+ private JMenuItem _deleteRangeItem = null;
+ private JMenuItem _deleteDuplicatesItem = null;
+ private JMenuItem _compressItem = null;
+ private JMenuItem _interpolateItem = null;
+ private JMenuItem _selectAllItem = null;
+ private JMenuItem _selectNoneItem = null;
+ private JMenuItem _selectStartItem = null;
+ private JMenuItem _selectEndItem = null;
+ private JMenuItem _reverseItem = null;
+ private JMenu _rearrangeMenu = null;
+ private JMenuItem _rearrangeStartItem = null;
+ private JMenuItem _rearrangeEndItem = null;
+ private JMenuItem _rearrangeNearestItem = null;
+ private JMenuItem _show3dItem = null;
+ private JMenuItem _saveExifItem = null;
+ private JMenuItem _connectPhotoItem = null;
+ private JMenuItem _deletePhotoItem = null;
+ // TODO: Does Photo menu require disconnect option?
+
+ // ActionListeners for reuse by menu and toolbar
+ private ActionListener _openFileAction = null;
+ private ActionListener _addPhotoAction = null;
+ private ActionListener _saveAction = null;
+ private ActionListener _undoAction = null;
+ private ActionListener _editPointAction = null;
+ private ActionListener _selectStartAction = null;
+ private ActionListener _selectEndAction = null;
+ private ActionListener _connectPhotoAction = null;
+
+ // Toolbar buttons which need enabling/disabling
+ private JButton _saveButton = null;
+ private JButton _undoButton = null;
+ private JButton _editPointButton = null;
+ private JButton _selectStartButton = null;
+ private JButton _selectEndButton = null;
+ private JButton _connectPhotoButton = null;
/**
_app = inApp;
_track = inTrackInfo.getTrack();
_selection = inTrackInfo.getSelection();
+ _photos = inTrackInfo.getPhotoList();
}
// Open file
JMenuItem openMenuItem = new JMenuItem(I18nManager.getText("menu.file.open"));
openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
- openMenuItem.addActionListener(new ActionListener() {
+ _openFileAction = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
_app.openFile();
}
- });
+ };
+ openMenuItem.addActionListener(_openFileAction);
fileMenu.add(openMenuItem);
// Add photos
JMenuItem addPhotosMenuItem = new JMenuItem(I18nManager.getText("menu.file.addphotos"));
- addPhotosMenuItem.addActionListener(new ActionListener() {
+ _addPhotoAction = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
_app.addPhotos();
}
- });
- // TODO: Re-add add photos menu item after v2
- // fileMenu.add(addPhotosMenuItem);
+ };
+ addPhotosMenuItem.addActionListener(_addPhotoAction);
+ fileMenu.add(addPhotosMenuItem);
// Save
_saveItem = new JMenuItem(I18nManager.getText("menu.file.save"), KeyEvent.VK_S);
- _saveItem.addActionListener(new ActionListener() {
+ _saveAction = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
_app.saveFile();
}
- });
+ };
+ _saveItem.addActionListener(_saveAction);
_saveItem.setEnabled(false);
fileMenu.add(_saveItem);
// Export
JMenu editMenu = new JMenu(I18nManager.getText("menu.edit"));
editMenu.setMnemonic(KeyEvent.VK_E);
_undoItem = new JMenuItem(I18nManager.getText("menu.edit.undo"));
- _undoItem.addActionListener(new ActionListener() {
+ _undoAction = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
_app.beginUndo();
}
- });
+ };
+ _undoItem.addActionListener(_undoAction);
_undoItem.setEnabled(false);
editMenu.add(_undoItem);
_clearUndoItem = new JMenuItem(I18nManager.getText("menu.edit.clearundo"));
editMenu.add(_clearUndoItem);
editMenu.addSeparator();
_editPointItem = new JMenuItem(I18nManager.getText("menu.edit.editpoint"));
- _editPointItem.addActionListener(new ActionListener() {
+ _editPointAction = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
_app.editCurrentPoint();
}
- });
+ };
+ _editPointItem.addActionListener(_editPointAction);
_editPointItem.setEnabled(false);
editMenu.add(_editPointItem);
_editWaypointNameItem = new JMenuItem(I18nManager.getText("menu.edit.editwaypointname"));
}
});
selectMenu.add(_selectNoneItem);
+ selectMenu.addSeparator();
+ _selectStartItem = new JMenuItem(I18nManager.getText("menu.select.start"));
+ _selectStartItem.setEnabled(false);
+ _selectStartAction = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _selection.selectRangeStart();
+ }
+ };
+ _selectStartItem.addActionListener(_selectStartAction);
+ selectMenu.add(_selectStartItem);
+ _selectEndItem = new JMenuItem(I18nManager.getText("menu.select.end"));
+ _selectEndItem.setEnabled(false);
+ _selectEndAction = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _selection.selectRangeEnd();
+ }
+ };
+ _selectEndItem.addActionListener(_selectEndAction);
+ selectMenu.add(_selectEndItem);
menubar.add(selectMenu);
+ // Add photo menu
+ JMenu photoMenu = new JMenu(I18nManager.getText("menu.photo"));
+ addPhotosMenuItem = new JMenuItem(I18nManager.getText("menu.file.addphotos"));
+ addPhotosMenuItem.addActionListener(_addPhotoAction);
+ photoMenu.add(addPhotosMenuItem);
+ _saveExifItem = new JMenuItem(I18nManager.getText("menu.photo.saveexif"));
+ _saveExifItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _app.saveExif();
+ }
+ });
+ _saveExifItem.setEnabled(false);
+ photoMenu.add(_saveExifItem);
+ _connectPhotoItem = new JMenuItem(I18nManager.getText("menu.photo.connect"));
+ _connectPhotoAction = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _app.connectPhotoToPoint();
+ }
+ };
+ _connectPhotoItem.addActionListener(_connectPhotoAction);
+ _connectPhotoItem.setEnabled(false);
+ photoMenu.add(_connectPhotoItem);
+ _deletePhotoItem = new JMenuItem(I18nManager.getText("menu.photo.delete"));
+ _deletePhotoItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _app.deleteCurrentPhoto();
+ }
+ });
+ _deletePhotoItem.setEnabled(false);
+ photoMenu.add(_deletePhotoItem);
+ menubar.add(photoMenu);
+
// Add 3d menu (whether java3d available or not)
JMenu threeDMenu = new JMenu(I18nManager.getText("menu.3d"));
_show3dItem = new JMenuItem(I18nManager.getText("menu.3d.show3d"));
}
+ /**
+ * Create a JToolBar containing all toolbar buttons
+ * @return toolbar containing buttons
+ */
+ public JToolBar createToolBar()
+ {
+ JToolBar toolbar = new JToolBar();
+ // Add text file
+ JButton openFileButton = new JButton(new ImageIcon(getClass().getResource("images/add_textfile_icon.png")));
+ 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")));
+ 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.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.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.setToolTipText(I18nManager.getText("menu.edit.editpoint"));
+ _editPointButton.addActionListener(_editPointAction);
+ _editPointButton.setEnabled(false);
+ toolbar.add(_editPointButton);
+ // Select start, end
+ _selectStartButton = new JButton(new ImageIcon(getClass().getResource("images/set_start_icon.png")));
+ _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.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.setToolTipText(I18nManager.getText("menu.photo.connect"));
+ _connectPhotoButton.addActionListener(_connectPhotoAction);
+ _connectPhotoButton.setEnabled(false);
+ toolbar.add(_connectPhotoButton);
+ // finish off
+ toolbar.setFloatable(false);
+ return toolbar;
+ }
+
+
/**
* Method to update menu when file loaded
*/
boolean hasData = (_track != null && _track.getNumPoints() > 0);
// set functions which require data
_saveItem.setEnabled(hasData);
+ _saveButton.setEnabled(hasData);
_exportKmlItem.setEnabled(hasData);
_exportPovItem.setEnabled(hasData);
_deleteDuplicatesItem.setEnabled(hasData);
// is undo available?
boolean hasUndo = !_app.getUndoStack().isEmpty();
_undoItem.setEnabled(hasUndo);
+ _undoButton.setEnabled(hasUndo);
_clearUndoItem.setEnabled(hasUndo);
// is there a current point?
boolean hasPoint = (hasData && _selection.getCurrentPointIndex() >= 0);
_editPointItem.setEnabled(hasPoint);
+ _editPointButton.setEnabled(hasPoint);
_editWaypointNameItem.setEnabled(hasPoint);
_deletePointItem.setEnabled(hasPoint);
+ _selectStartItem.setEnabled(hasPoint);
+ _selectStartButton.setEnabled(hasPoint);
+ _selectEndItem.setEnabled(hasPoint);
+ _selectEndButton.setEnabled(hasPoint);
+ // are there any photos?
+ _saveExifItem.setEnabled(_photos != null && _photos.getNumPhotos() > 0);
+ // is there a current photo?
+ boolean hasPhoto = _photos != null && _photos.getNumPhotos() > 0
+ && _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;
+ _connectPhotoItem.setEnabled(connectAvailable);
+ _connectPhotoButton.setEnabled(connectAvailable);
+ _deletePhotoItem.setEnabled(hasPhoto);
// is there a current range?
boolean hasRange = (hasData && _selection.hasRangeSelected());
_deleteRangeItem.setEnabled(hasRange);
--- /dev/null
+package tim.prune.gui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.data.Photo;
+
+/**
+ * GUI component for showing photo thumbnail
+ */
+public class PhotoThumbnail extends JPanel implements Runnable
+{
+ private Photo _photo = null;
+ private BufferedImage _thumbnail = null;
+ private int _lastWidth = -1;
+ private int _lastHeight = -1;
+ private boolean _loadingImage = false;
+ private static String _loadingString = null;
+
+
+ /**
+ * Constructor
+ */
+ public PhotoThumbnail()
+ {
+ // TODO: Make size of thumbnail dynamic, as big as it can be
+ setOpaque(true);
+ _loadingString = I18nManager.getText("details.photo.loading") + " ...";
+ }
+
+
+ /**
+ * Set the Photo
+ * @param inPhoto Photo object to show thumbnail for
+ */
+ public void setPhoto(Photo inPhoto)
+ {
+ // Check whether the photo has changed
+ if (_photo == inPhoto) {return;}
+ _photo = inPhoto;
+ _thumbnail = null;
+ }
+
+
+ /**
+ * Override paint method
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ public void paint(Graphics inG)
+ {
+ super.paint(inG);
+ if (_photo != null)
+ {
+ // recalculate thumbnail if photo has changed
+ if (_thumbnail == null || getWidth() != _lastWidth || getHeight() != _lastHeight)
+ {
+ // initiate load if not already started
+ if (!_loadingImage)
+ {
+ _loadingImage = true;
+ new Thread(this).start();
+ }
+ }
+ // Set width and height
+ _lastWidth = getWidth();
+ _lastHeight = getHeight();
+ // if loading, display image
+ if (_loadingImage)
+ {
+ inG.setColor(Color.BLACK);
+ inG.drawString(_loadingString, 10, 30);
+ }
+ else
+ {
+ // Copy scaled, smoothed image onto the screen
+ inG.drawImage(_thumbnail, 0, 0, _thumbnail.getWidth(), _thumbnail.getHeight(), null);
+ }
+ }
+ }
+
+
+ /**
+ * Run method, for loading image in separate thread
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ int picWidth = _photo.getWidth();
+ int picHeight = _photo.getHeight();
+ if (picWidth > -1 && picHeight > -1)
+ {
+ int displayWidth = Math.min(getWidth(), getParent().getWidth());
+ // System.out.println("width = " + getWidth() + ", " + getParent().getWidth() + " = " + displayWidth);
+ int displayHeight = Math.min(getHeight(), getParent().getHeight());
+ // System.out.println("height = " + getHeight() + ", " + getParent().getHeight() + " = " + displayHeight);
+
+ // calculate maximum thumbnail size
+ Dimension thumbSize = ImageUtils.getThumbnailSize(picWidth, picHeight, displayWidth, displayHeight);
+ // Work out if need to remake image
+ boolean needToRemake = (_thumbnail == null)
+ || _thumbnail.getWidth() != thumbSize.width || _thumbnail.getHeight() != thumbSize.height;
+ if (thumbSize.width > 0 && thumbSize.height > 0 && needToRemake)
+ {
+ // Make icon to load image into
+ Image image = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage();
+ // save scaled, smoothed thumbnail for reuse
+ _thumbnail = ImageUtils.createScaledImage(image, thumbSize.width, thumbSize.height);
+ image = null;
+ // TODO: Calculate and set size of thumbnail here
+ // setPreferredSize(new Dimension(200, 200));
+ }
+ }
+ _loadingImage = false;
+ repaint();
+ }
+}
/**
* Override paint method to draw map
+ * @param g Graphics object
*/
public void paint(Graphics g)
{
int maxAltitude = altitudeRange.getMaximum();
// message if no altitudes in track
- if (minAltitude < 0 || maxAltitude < 0)
+ if (minAltitude < 0 || maxAltitude < 0
+ || minAltitude == maxAltitude)
{
g.setColor(COLOR_LINES);
g.drawString(I18nManager.getText("display.noaltitudes"), 50, height/2);
--- /dev/null
+package tim.prune.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Photo;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Class to allow selection of points and photos
+ * as a visual component
+ */
+public class SelectorDisplay extends GenericDisplay
+{
+ // Track details
+ private JLabel _trackpointsLabel = null;
+ private JLabel _filenameLabel = null;
+ // Scroll bar
+ private JScrollBar _scroller = null;
+ private boolean _ignoreScrollEvents = false;
+
+ // Photos
+ private JList _photoList = null;
+ private PhotoListModel _photoListModel = null;
+ // Waypoints
+ private JList _waypointList = null;
+ private WaypointListModel _waypointListModel = null;
+
+ // scrollbar interval
+ private static final int SCROLLBAR_INTERVAL = 50;
+ // number of rows in lists
+ private static final int NUM_LIST_ENTRIES = 7;
+
+
+ /**
+ * Constructor
+ * @param inTrackInfo Track info object
+ */
+ public SelectorDisplay(TrackInfo inTrackInfo)
+ {
+ super(inTrackInfo);
+ setLayout(new BorderLayout());
+
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ // Track details panel
+ JPanel trackDetailsPanel = new JPanel();
+ trackDetailsPanel.setLayout(new BoxLayout(trackDetailsPanel, BoxLayout.Y_AXIS));
+ trackDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
+ );
+ JLabel trackDetailsLabel = new JLabel(I18nManager.getText("details.trackdetails"));
+ Font biggerFont = trackDetailsLabel.getFont();
+ biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
+ trackDetailsLabel.setFont(biggerFont);
+ trackDetailsPanel.add(trackDetailsLabel);
+ _trackpointsLabel = new JLabel(I18nManager.getText("details.notrack"));
+ trackDetailsPanel.add(_trackpointsLabel);
+ _filenameLabel = new JLabel("");
+ trackDetailsPanel.add(_filenameLabel);
+
+ // Scroll bar
+ _scroller = new JScrollBar(JScrollBar.HORIZONTAL, 0, SCROLLBAR_INTERVAL, 0, 100);
+ _scroller.addAdjustmentListener(new AdjustmentListener() {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ selectPoint(e.getValue());
+ }
+ });
+ _scroller.setEnabled(false);
+
+ // Add panel for waypoints / photos
+ JPanel listsPanel = new JPanel();
+ listsPanel.setLayout(new BoxLayout(listsPanel, BoxLayout.Y_AXIS));
+ listsPanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
+ );
+ _waypointListModel = new WaypointListModel(_trackInfo.getTrack());
+ _waypointList = new JList(_waypointListModel);
+ _waypointList.setVisibleRowCount(NUM_LIST_ENTRIES);
+ _waypointList.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e)
+ {
+ if (!e.getValueIsAdjusting()) selectWaypoint(_waypointList.getSelectedIndex());
+ }});
+ listsPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.waypoints")));
+ listsPanel.add(new JScrollPane(_waypointList));
+ _photoListModel = new PhotoListModel(_trackInfo.getPhotoList());
+ _photoList = new JList(_photoListModel);
+ _photoList.setVisibleRowCount(NUM_LIST_ENTRIES);
+ _photoList.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e)
+ {
+ if (!e.getValueIsAdjusting()) selectPhoto(_photoList.getSelectedIndex());
+ }});
+ listsPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.photos")));
+ listsPanel.add(new JScrollPane(_photoList));
+ listsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ // add the controls to the main panel
+ mainPanel.add(trackDetailsPanel);
+ mainPanel.add(Box.createVerticalStrut(5));
+ mainPanel.add(_scroller);
+ mainPanel.add(Box.createVerticalStrut(5));
+ mainPanel.add(listsPanel);
+
+ // add the main panel at the top
+ add(mainPanel, BorderLayout.NORTH);
+ // set preferred width to be small
+ setPreferredSize(new Dimension(100, 100));
+ }
+
+
+ /**
+ * Select the specified point
+ * @param inValue value to select
+ */
+ private void selectPoint(int inValue)
+ {
+ if (_track != null && !_ignoreScrollEvents)
+ {
+ _trackInfo.getSelection().selectPoint(inValue);
+ }
+ }
+
+
+ /**
+ * Select the specified photo
+ * @param inPhotoIndex index of selected photo
+ */
+ private void selectPhoto(int inPhotoIndex)
+ {
+ _trackInfo.selectPhoto(inPhotoIndex);
+ }
+
+
+ /**
+ * Select the specified waypoint
+ * @param inWaypointIndex index of selected waypoint
+ */
+ private void selectWaypoint(int inWaypointIndex)
+ {
+ if (inWaypointIndex >= 0)
+ {
+ _trackInfo.selectPoint(_waypointListModel.getWaypoint(inWaypointIndex));
+ }
+ }
+
+
+ /**
+ * Notification that Track has been updated
+ */
+ public void dataUpdated(byte inUpdateType)
+ {
+ // Update track data
+ if (_track == null || _track.getNumPoints() <= 0)
+ {
+ _trackpointsLabel.setText(I18nManager.getText("details.notrack"));
+ _filenameLabel.setText("");
+ }
+ else
+ {
+ _trackpointsLabel.setText(I18nManager.getText("details.track.points") + ": "
+ + _track.getNumPoints());
+ int numFiles = _trackInfo.getFileInfo().getNumFiles();
+ if (numFiles == 1)
+ {
+ _filenameLabel.setText(I18nManager.getText("details.track.file") + ": "
+ + _trackInfo.getFileInfo().getFilename());
+ }
+ else if (numFiles > 1)
+ {
+ _filenameLabel.setText(I18nManager.getText("details.track.numfiles") + ": "
+ + numFiles);
+ }
+ else _filenameLabel.setText("");
+ }
+
+ // Update scroller settings
+ int currentPointIndex = _trackInfo.getSelection().getCurrentPointIndex();
+ _ignoreScrollEvents = true;
+ if (_track == null || _track.getNumPoints() < 2)
+ {
+ // careful to avoid event loops here
+ // _scroller.setValue(0);
+ _scroller.setEnabled(false);
+ }
+ else
+ {
+ _scroller.setMaximum(_track.getNumPoints() + SCROLLBAR_INTERVAL);
+ if (currentPointIndex >= 0)
+ _scroller.setValue(currentPointIndex);
+ _scroller.setEnabled(true);
+ }
+ _ignoreScrollEvents = false;
+
+ // update waypoints and photos if necessary
+ if ((inUpdateType |
+ (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.WAYPOINTS_MODIFIED)) > 0)
+ {
+ _waypointListModel.fireChanged();
+ }
+ if ((inUpdateType |
+ (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.PHOTOS_MODIFIED)) > 0)
+ {
+ _photoListModel.fireChanged();
+ }
+ // Deselect selected waypoint if selected point has since changed
+ if (_waypointList.getSelectedIndex() >= 0)
+ {
+ if (_trackInfo.getCurrentPoint() == null
+ || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint()))
+ {
+ // point is selected in list but different from current point - deselect
+ _waypointList.clearSelection();
+ }
+ }
+ // Do the same for the photos
+ if (_photoList.getSelectedIndex() >= 0)
+ {
+ DataPoint trackPoint = _trackInfo.getCurrentPoint();
+ Photo selectedPhoto = _photoListModel.getPhoto(_photoList.getSelectedIndex());
+ // Get selected Photo, if it's still there
+ DataPoint photoPoint = null;
+ if (selectedPhoto != null) {
+ photoPoint = _photoListModel.getPhoto(_photoList.getSelectedIndex()).getDataPoint();
+ }
+ // Compare selected photo with selected point
+ if ( (photoPoint != null && (trackPoint == null || !photoPoint.equals(trackPoint)))
+ || (_trackInfo.getSelection().getCurrentPhotoIndex() < 0) )
+ {
+ // photo is selected in list but different from current point - deselect
+ _photoList.clearSelection();
+ _trackInfo.getSelection().deselectPhoto();
+ }
+ }
+ }
+}
menu.select=Select
menu.select.all=Select all
menu.select.none=Select none
+menu.select.start=Set range start
+menu.select.end=Set range end
+menu.photo=Photo
+menu.photo.saveexif=Save to Exif
+menu.photo.connect=Connect to point
+menu.photo.delete=Remove photo
menu.3d=Three-D
menu.3d.show3d=Show in Three-D
menu.help=Help
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.deletepoint.title=Delete Point
+dialog.deletepoint.deletephoto=Delete photo attached to this point?
+dialog.deletephoto.title=Delete Photo
+dialog.deletephoto.deletepoint=Delete point attached to this photo?
dialog.deleteduplicates.title=Delete Duplicates
dialog.deleteduplicates.single.text=duplicate was deleted
dialog.deleteduplicates.multi.text=duplicates were deleted
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.progress.title=Loading photos
dialog.jpegload.progress=Please wait while the photos are searched
dialog.jpegload.title=Loaded photos
dialog.save.overwrite.title=File already exists
dialog.save.overwrite.text=This file already exists. Are you sure you want to overwrite the file?
dialog.exportkml.title=Export KML
-dialog.exportkml.text=Please enter a short description for the data
-dialog.exportkml.filetype=KML files
+dialog.exportkml.text=Title for the data
+dialog.exportkml.kmz=Compress to make kmz file
+dialog.exportkml.exportimages=Export image thumbnails to kmz
+dialog.exportkml.filetype=KML, KMZ files
dialog.exportpov.title=Export POV
dialog.exportpov.text=Please enter the parameters for the POV export
dialog.exportpov.font=Font
dialog.pointnameedit.uppercase=UPPER case
dialog.pointnameedit.lowercase=lower case
dialog.pointnameedit.sentencecase=Sentence case
+dialog.saveexif.title=Save Exif
+dialog.saveexif.intro=Select the photos to save using the checkboxes
+dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save
+dialog.saveexif.noexiftool=No exiftool program could be found. Continue?
+dialog.saveexif.table.photoname=Photo name
+dialog.saveexif.table.status=Status
+dialog.saveexif.table.save=Save
+dialog.saveexif.photostatus.connected=Connected
+dialog.saveexif.photostatus.disconnected=Disconnected
+dialog.saveexif.photostatus.modified=Modified
+dialog.saveexif.overwrite=Overwrite files
+dialog.saveexif.ok1=Saved
+dialog.saveexif.ok2=photo files
dialog.about.title=About Prune
dialog.about.version=Version
dialog.about.build=Build
dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.<br>Copying, redistribution and modification are permitted and encouraged<br>according to the conditions in the included <code>license.txt</code> file.
dialog.about.summarytext3=Please see <code style="font-weight:bold">http://activityworkshop.net/</code> for more information and user guides.
dialog.about.translatedby=English text by activityworkshop.
+dialog.about.systeminfo=System info
+dialog.about.systeminfo.os=Operating System
+dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo.java3d=Java3d installed
+dialog.about.systeminfo.povray=Povray installed
+dialog.about.systeminfo.exiftool=Exiftool installed
+dialog.about.yes=Yes
+dialog.about.no=No
+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.translations=Translations helped by
+dialog.about.credits.devtools=Development tools
+dialog.about.credits.othertools=Other tools
+dialog.about.credits.thanks=Thanks to
# 3d window
dialog.3d.title=Prune Three-d view
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
# Buttons
button.ok=OK
button.overwrite=Overwrite
button.moveup=Move up
button.movedown=Move down
-button.startrange=Set start
-button.endrange=Set end
button.deletepoint=Delete point
button.deleterange=Delete range
+button.showlines=Show lines
button.edit=Edit
button.exit=Exit
button.close=Close
button.continue=Continue
+button.yes=Yes
+button.no=No
+button.yestoall=Yes to all
+button.notoall=No to all
+button.selectall=Select all
+button.selectnone=Select none
# Display components
display.nodata=No data loaded
display.range.time.days=d
details.waypointsphotos.waypoints=Waypoints
details.waypointsphotos.photos=Photos
+details.photodetails=Photo details
+details.nophoto=No photo selected
+details.photo.loading=Loading
# Field names
fieldname.latitude=Latitude
undo.loadphotos=load photos
undo.editpoint=edit point
undo.deletepoint=delete point
+undo.deletephoto=remove photo
undo.deleterange=delete range
undo.compress=compress track
undo.insert=insert points
undo.deleteduplicates=delete duplicates
undo.reverse=reverse range
undo.rearrangewaypoints=rearrange waypoints
+undo.connectphoto=connect photo
# Error messages
error.save.dialogtitle=Error saving data
error.save.nodata=No data to save
error.save.failed=Failed to save the data to file:
+error.saveexif.filenotfound=Failed to find photo file
+error.saveexif.cannotoverwrite1=Photo file
+error.saveexif.cannotoverwrite2=is read-only and can't be overwritten. Write to copy?
error.load.dialogtitle=Error loading data
error.load.noread=Cannot read file
+error.load.nopoints=No coordinate information found in the file
+error.load.unknownxml=Unrecognised xml format:
+error.load.othererror=Error reading file:
error.jpegload.dialogtitle=Error loading photos
error.jpegload.nofilesfound=No files found
error.jpegload.nojpegsfound=No jpeg files found
menu.select=Selektieren
menu.select.all=Alles selektieren
menu.select.none=Nichts selektieren
+menu.select.start=Start setzen
+menu.select.end=Stopp setzen
+menu.photo=Foto
+menu.photo.saveexif=Exif Daten speichern
+menu.photo.connect=Mit Punkt verbinden
+menu.photo.delete=Foto entfernen
menu.3d=Drei-D
menu.3d.show3d=In drei-D zeigen
menu.help=Hilfe
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.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.deleteduplicates.title=Duplikate löschen
dialog.deleteduplicates.single.text=Duplikat wurde gelöscht
dialog.deleteduplicates.multi.text=Duplikate wurden gelöscht
dialog.openoptions.tabledesc=Extrakt von der Datei
dialog.openoptions.altitudeunits=Höhe Maßeinheiten
dialog.jpegload.subdirectories=Subordnern auch durchsuchen
+dialog.jpegload.loadjpegswithoutcoords=Auch Fotos ohne Koordinaten laden
dialog.jpegload.progress.title=Fotos werden geladen
dialog.jpegload.progress=Bitte warten während die Fotos durchgesucht werden
dialog.jpegload.title=Fotos geladen
dialog.save.overwrite.title=Datei existiert
dialog.save.overwrite.text=Diese Datei existiert schon. Sind Sie sicher, Sie wollen die Datei überschreiben?
dialog.exportkml.title=KML exportieren
-dialog.exportkml.text=Kurze Beschreibung von den Daten
-dialog.exportkml.filetype=KML Dateien
+dialog.exportkml.text=Titel für die Daten
+dialog.exportkml.kmz=Daten ins kmz Datei komprimieren
+dialog.exportkml.exportimages=Bilder ins kmz exportieren
+dialog.exportkml.filetype=KML, KMZ Dateien
dialog.exportpov.title=POV exportieren
dialog.exportpov.text=Geben Sie die Parameter ein für das POV Export
dialog.exportpov.font=Font
dialog.pointnameedit.uppercase=GROSS geschrieben
dialog.pointnameedit.lowercase=klein geschrieben
dialog.pointnameedit.sentencecase=Gemischt geschrieben
+dialog.saveexif.title=Exif speichern
+dialog.saveexif.intro=Selektieren Sie die Fotos zu speichern
+dialog.saveexif.nothingtosave=Koordinaten sind nicht modifiziert, nichts zu speichern
+dialog.saveexif.noexiftool=Kein exiftool Program gefunden. Trotzdem fortfahren?
+dialog.saveexif.table.photoname=Foto Name
+dialog.saveexif.table.status=Status
+dialog.saveexif.table.save=Speichern
+dialog.saveexif.photostatus.connected=Verbunden
+dialog.saveexif.photostatus.disconnected=Getrennt
+dialog.saveexif.photostatus.modified=Modifiziert
+dialog.saveexif.overwrite=Dateien überschreiben
+dialog.saveexif.ok1=Es wurden
+dialog.saveexif.ok2=Foto Dateien geschrieben
dialog.about.title=Ãœber Prune
dialog.about.version=Version
dialog.about.build=Build
dialog.about.summarytext2=Es ist unter den Gnu GPL zur Verfügung gestellt, für frei, gratis und offen Gebrauch und Weiterentwicklung.<br>Kopieren, Weiterverbreitung und Veränderungen sind erlaubt und willkommen<br>unter die Bedingungen in der enthaltenen <code>license.txt</code> Datei.
dialog.about.summarytext3=Bitte sehen Sie <code style="font-weight:bold">http://activityworkshop.net/</code> für weitere Information und Benutzeranleitungen.
dialog.about.translatedby=Deutsche Ãœbersetzung von activityworkshop.
+dialog.about.systeminfo=System Information
+dialog.about.systeminfo.os=Betriebsystem
+dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo.java3d=Java3d installiert
+dialog.about.systeminfo.povray=Povray installiert
+dialog.about.systeminfo.exiftool=Exiftool installiert
+dialog.about.yes=Ja
+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.translations=Ãœbersetzungen mit Hilfe von
+dialog.about.credits.devtools=Entwicklungsprogrammen
+dialog.about.credits.othertools=Andere Programmen
+dialog.about.credits.thanks=Danke an
# 3d window
dialog.3d.title=Prune Drei-D 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
# Buttons
button.ok=OK
button.overwrite=Ãœberschreiben
button.moveup=Aufwärts moven
button.movedown=Abwärts moven
-button.startrange=Start setzen
-button.endrange=Stopp setzen
button.deletepoint=Punkt löschen
button.deleterange=Spanne löschen
+button.showlines=Linien anzeigen
button.edit=Bearbeiten
button.exit=Beenden
button.close=Schließen
button.continue=Fortsetzen
+button.yes=Ja
+button.no=Nein
+button.yestoall=Ja für alle
+button.notoall=Nein für alle
+button.selectall=Alle selektieren
+button.selectnone=Nichts selektieren
# Display components
display.nodata=Keine Daten geladen
display.range.time.days=T
details.waypointsphotos.waypoints=Waypoints
details.waypointsphotos.photos=Fotos
+details.photodetails=Details vom Foto
+details.nophoto=Kein Foto selektiert
+details.photo.loading=Laden
# Field names
fieldname.latitude=Breitengrad
undo.loadphotos=Fotos laden
undo.editpoint=Punkt bearbeiten
undo.deletepoint=Punkt löschen
+undo.deletephoto=Photo entfernen
undo.deleterange=Spanne löschen
undo.compress=Track komprimieren
undo.insert=Punkte hinzufügen
undo.deleteduplicates=Duplikaten löschen
undo.reverse=Spanne umdrehen
undo.rearrangewaypoints=Waypoints reorganisieren
+undo.connectphoto=Foto verbinden
# Error messages
error.save.dialogtitle=Fehler beim Speichern
error.save.nodata=Keine Daten wurden geladen
-error.save.failed=Speichern von der Datei fehlgeschlagen :
+error.save.failed=Speichern von der Datei fehlgeschlagen:
+error.saveexif.filenotfound=Foto Datei nicht gefunden
+error.saveexif.cannotoverwrite1=Foto Datei
+error.saveexif.cannotoverwrite2=ist schreib-geschützt. Speichern zu einer Kopie?
error.load.dialogtitle=Fehler beim Laden
error.load.noread=Datei konnte nicht gelesen werden
+error.load.nopoints=Keine gültigen Daten in Datei gefunden
+error.load.unknownxml=Unbekanntes xml Format:
+error.load.othererror=Fehler beim Lesen von der Datei:
error.jpegload.dialogtitle=Fehler beim Laden von Fotos
error.jpegload.nofilesfound=Keine Dateien gefunden
error.jpegload.nojpegsfound=Keine Jpeg Dateien gefunden
menu.select=Selektiere
menu.select.all=Alles selektiere
menu.select.none=Nüüt selektiere
+menu.select.start=Start setzä
+menu.select.end=Stopp setzä
+menu.photo=Föteli
+menu.photo.saveexif=Exif Date speicherä
+menu.photo.connect=Mitem Punkt verbindä
+menu.photo.delete=Föteli entfernä
menu.3d=Drüü-D
menu.3d.show3d=In drüü-D zeigä
menu.help=Hilfe
dialog.exit.confirm.text=Ihri Date sind nonig gspeicheret worde. Wend Sie trotzdem s Programm beände?
dialog.openappend.title=Date aahänge oder ersätze
dialog.openappend.text=Häng diese Date zur aktuelli Daten aa?
+dialog.deletepoint.title=Punkt löschä
+dialog.deletepoint.deletephoto=s Föteli vonem Punkt au löschä?
+dialog.deletephoto.title=Föteli entfernä
+dialog.deletephoto.deletepoint=Punkt vonem Föteli au löschä?
dialog.deleteduplicates.title=Duplikaten lösche
dialog.deleteduplicates.single.text=Duplikat isch glöscht worde
dialog.deleteduplicates.multi.text=Duplikaten sin glöscht worde
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.progress.title=Fötelis lade
-dialog.jpegload.progress=Bitte warte während die Fötolis durägsucht werde
+dialog.jpegload.progress=Bitte warte während die Fötelis durägsucht werde
dialog.jpegload.title=Fötelis glade worde
dialog.jpegload.photoadded=Föteli isch glade worde
dialog.jpegload.photosadded=Fötelis sin glade worde
-dialog.saveoptions.title=File speichere
-dialog.save.fieldstosave=Fälder zu speichere
+dialog.saveoptions.title=File speicherä
+dialog.save.fieldstosave=Fälder zu speicherä
dialog.save.table.field=Fäld
dialog.save.table.hasdata=Het Date
-dialog.save.table.save=Speichere
-dialog.save.headerrow=Titel Ziile speichere
+dialog.save.table.save=Speicherä
+dialog.save.headerrow=Titel Ziile speicherä
dialog.save.coordinateunits=Koordinate Massiiheite
dialog.save.units.original=Original
dialog.save.altitudeunits=Höchi Massiiheite
dialog.save.oktitle=File gespeichert worde
-dialog.save.ok1=Es isch
-dialog.save.ok2=Punkte gespeichert worde na
+dialog.save.ok1=Es sin
+dialog.save.ok2=Punkte gspeicheret worde na
dialog.save.overwrite.title=s'File existiert scho
dialog.save.overwrite.text=s'File existiert scho. Sind Sie sicher, Sie wend s'File überschriibe?
-dialog.exportkml.title=KML exportiere
-dialog.exportkml.text=Kurze Beschriibig von den Date
-dialog.exportkml.filetype=KML Dateie
+dialog.exportkml.title=KML exportierä
+dialog.exportkml.text=Titel für die Date
+dialog.exportkml.kmz=Date ins kmz File komprimierä
+dialog.exportkml.exportimages=Bildli ins Kmz exportierä
+dialog.exportkml.filetype=KML, KMZ Dateie
dialog.exportpov.title=POV exportiere
dialog.exportpov.text=Gäbet Sie die Parameter ii fürs POV Export
dialog.exportpov.font=Font
dialog.pointnameedit.uppercase=GROSS gschriebe
dialog.pointnameedit.lowercase=chli gschriebe
dialog.pointnameedit.sentencecase=Gmischt gschriebe
+dialog.saveexif.title=Exif go speicherä
+dialog.saveexif.intro=Wählet Sie die Fötelis uus zum speicherä
+dialog.saveexif.nothingtosave=Koordinaten sin nöd geänderet, nüüt zum speicherä
+dialog.saveexif.noexiftool=Kei exiftool Programm gfunde. Wiiter?
+dialog.saveexif.table.photoname=Föteli Name
+dialog.saveexif.table.status=Status
+dialog.saveexif.table.save=Speicherä
+dialog.saveexif.photostatus.connected=Verbundä
+dialog.saveexif.photostatus.disconnected=Gtrännt
+dialog.saveexif.photostatus.modified=Gänderet
+dialog.saveexif.overwrite=Files überschriebä
+dialog.saveexif.ok1=Es sin
+dialog.saveexif.ok2=Fötelis gschriebe worde
dialog.about.title=Ãœber Prune
dialog.about.version=Version
dialog.about.build=Build
dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verfüegig gstellt,für frei, gratis und offen Gebruuch und Wiiterentwicklig.<br>Kopiere, Wiiterverbreitig und Veränderige sin erlaubt und willkommen<br>unter die Bedingunge im enthaltene <code>license.txt</code> File.
dialog.about.summarytext3=Bitte lueg na <code style="font-weight:bold">http://activityworkshop.net/</code> für wiitere Information und Benutzeraaleitige.
dialog.about.translatedby=Schwiizerdüütschi Übersetzig vo activityworkshop.
+dialog.about.systeminfo=Syschtem Info
+dialog.about.systeminfo.os=Betriebsyschtem
+dialog.about.systeminfo.java=Version vonem Java
+dialog.about.systeminfo.java3d=Java3d inschtalliert
+dialog.about.systeminfo.povray=Povray inschtalliert
+dialog.about.systeminfo.exiftool=Exiftool inschtalliert
+dialog.about.yes=Ja
+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.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
# 3d window
-dialog.3d.title=Prune Drüü-d aasicht
+dialog.3d.title=Prune Drüü-d Aasicht
dialog.3d.altitudecap=Minimum Höhenskala
+dialog.3dlines.title=Prune Gitterlinie
+dialog.3dlines.empty=Kei Linie zum aazeigä!
+dialog.3dlines.intro=Hier sin die Linie für die drüü-D Aasicht
# Buttons
button.ok=OK
button.back=Zrugg
-button.next=Nöchste
+button.next=Nöchstä
button.finish=Fertig
-button.cancel=Abbräche
-button.overwrite=Ãœberschriibe
-button.moveup=Uufwärts move
-button.movedown=Abwärts move
-button.startrange=Start setze
-button.endrange=Stopp setze
-button.deletepoint=Punkt lösche
-button.deleterange=Spanne lösche
-button.edit=Editiere
-button.exit=Beände
-button.close=Schliesse
-button.continue=Fortsetze
+button.cancel=Abbrächä
+button.overwrite=Überschriibä
+button.moveup=Uufä schiebä
+button.movedown=Aba schiebä
+button.deletepoint=Punkt löschä
+button.deleterange=Spanne löschä
+button.showlines=Linie aazeigä
+button.edit=Editierä
+button.exit=Beändä
+button.close=Schliessä
+button.continue=Fortsetzä
+button.yes=Ja
+button.no=Nei
+button.yestoall=Ja für alli
+button.notoall=Nei für alli
+button.selectall=Alli selektierä
+button.selectnone=Nüüt selektierä
# Display components
display.nodata=Kei Date glade worde
display.range.time.days=T
details.waypointsphotos.waypoints=Waypoints
details.waypointsphotos.photos=Fötelis
+details.photodetails=Details vom Föteli
+details.nophoto=Kei föteli selektiert
+details.photo.loading=Ladä
# Field names
fieldname.latitude=Breitegrad
cardinal.w=W
# Undo operations
-undo.load=Date lade
-undo.loadphotos=Fötelis lade
-undo.editpoint=Punkt editiere
-undo.deletepoint=Punkt lösche
-undo.deleterange=Spanne lösche
-undo.compress=Track komprimiere
-undo.insert=Punkte innätue
-undo.deleteduplicates=Duplikaten lösche
+undo.load=Date ladä
+undo.loadphotos=Fötelis ladä
+undo.editpoint=Punkt editierä
+undo.deletepoint=Punkt löschä
+undo.deletephoto=Föteli entfärnä
+undo.deleterange=Spanne löschä
+undo.compress=Track komprimierä
+undo.insert=Punkte innätuä
+undo.deleteduplicates=Duplikaten löschä
undo.reverse=Spanne umdrähie
-undo.rearrangewaypoints=Waypoints reorganisiere
+undo.rearrangewaypoints=Waypoints reorganisierä
+undo.connectphoto=Föteli verbindä
# Error messages
error.save.dialogtitle=Fehler bim Speichere
error.save.nodata=Kei Date zum speichere
error.save.failed=Speichere vom File fehlgschlage :
+error.saveexif.filenotfound=Föteli File nöd gfunde
+error.saveexif.cannotoverwrite1=Föteli File
+error.saveexif.cannotoverwrite2=isch nöd schriibbar. Speichere na einer Kopie?
error.load.dialogtitle=Fehler bim Lade
error.load.noread=File cha nöd glase werde
+error.load.nopoints=Kei gültigi Information im Datei gfunde
+error.load.unknownxml=Unbekanntes xml Format:
+error.load.othererror=Fehler bim Läse:
error.jpegload.dialogtitle=Fehler bim Lade von Fötelis
error.jpegload.nofilesfound=Kei Dateie gfunde
error.jpegload.nojpegsfound=Kei Jpegs gfunde
menu.select=Seleccionar
menu.select.all=Seleccionar todo
menu.select.none=Seleccionar nada
+menu.select.start=Fijar comienzo
+menu.select.end=Fijar final
+menu.photo=Foto
+menu.photo.saveexif=Guardar Exif
+menu.photo.connect=Connect con punto
+menu.photo.delete=Eliminar foto
menu.3d=3-D
menu.3d.show3d=Mostrar en 3-D
menu.help=Ayuda
dialog.exit.confirm.text=Los datos han sido modificados. Desea salir de Prune?
dialog.openappend.title=Agregar a datos existentes
dialog.openappend.text=Agregar estos datos a los datos ya guardados?
+dialog.deletepoint.title=Borrar punto
+dialog.deletepoint.deletephoto=Borrar foto tambien?
+dialog.deletephoto.title=Borrar foto
+dialog.deletephoto.deletepoint=Borrar punto tambien?
dialog.deleteduplicates.title=Borrar duplicados
dialog.deleteduplicates.single.text=duplicado eliminado
dialog.deleteduplicates.multi.text=duplicados eliminados
dialog.openoptions.tabledesc=Extraer archivo
dialog.openoptions.altitudeunits=Unidades altitud
dialog.jpegload.subdirectories=Incluir subdirectorios
+dialog.jpegload.loadjpegswithoutcoords=Fotos sin coordenadas tambien
dialog.jpegload.progress.title=Cargando fotos
dialog.jpegload.progress=Por favor espere mientras se buscan las fotos
dialog.jpegload.title=Fotos cargadas
dialog.save.overwrite.title=El archivo ya existe
dialog.save.overwrite.text=El archivo ya existe, desea sobreescribirlo?
dialog.exportkml.title=Exportar KML
-dialog.exportkml.text=Introduzca breve descripción para los datos
-dialog.exportkml.filetype=Archivos KML
+dialog.exportkml.text=Descripción para los datos
+dialog.exportkml.kmz=Comprimir al archivo kmz
+dialog.exportkml.exportimages=Exportar fotos al kmz
+dialog.exportkml.filetype=Archivos KML, KMZ
dialog.exportpov.title=Exportar POV
dialog.exportpov.text=Introdzca los Parametros para exportar
dialog.exportpov.font=Fuente
dialog.exportpov.cameray=Camera Y
dialog.exportpov.cameraz=Camera Z
dialog.exportpov.filetype=Archivos POV
-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.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.interpolate.title=Interpolar puntos
dialog.pointnameedit.uppercase=Maysculas
dialog.pointnameedit.lowercase=minsculas
dialog.pointnameedit.sentencecase=Mezcla
+dialog.saveexif.title=Guardar Exif
+dialog.saveexif.intro=Seleccione fotos a guardar
+dialog.saveexif.nothingtosave=Coordenadas no han modificados
+dialog.saveexif.noexiftool=exiftool program no encontrado. Desea continuar?
+dialog.saveexif.table.photoname=Nombre de foto
+dialog.saveexif.table.status=Status
+dialog.saveexif.table.save=Guardar
+dialog.saveexif.photostatus.connected=Connected
+dialog.saveexif.photostatus.disconnected=Disconnected
+dialog.saveexif.photostatus.modified=Modificado
+dialog.saveexif.overwrite=Sobreescribirlar archivos?
+dialog.saveexif.ok1=Guardando
+dialog.saveexif.ok2=fotos
dialog.about.title=Acerca de Prune
dialog.about.version=Versión
dialog.about.build=Construir
dialog.about.summarytext1=Prune es un programa para cargar, mostrar y editar datos de receptores GPS.
dialog.about.summarytext2=Distribuido bajo el GNU GPL para uso libre y gratuito.<br>Se permite (y se anima) la copia, redistribución y modificación de acuerdo<br>a las condiciones incluidas en el archivo <code>licence.txt</code>.
dialog.about.summarytext3=Por favor, ver <code style="font-weight:bold">http://activityworkshop.net/</code> para más información y guÃas del usuario.
-dialog.about.translatedby=Traducción en español por activityworkshop y un alma muy gentil!
+dialog.about.translatedby=Traducción al español realizada por activityworkshop y amigos muy amables!
+dialog.about.systeminfo=Informacion del System
+dialog.about.systeminfo.os=Operating System
+dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo.java3d=Java3d installed
+dialog.about.systeminfo.povray=Povray installed
+dialog.about.systeminfo.exiftool=Exiftool installed
+dialog.about.yes=Si
+dialog.about.no=No
+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.translations=Translations helped by
+dialog.about.credits.devtools=Development tools
+dialog.about.credits.othertools=Other tools
+dialog.about.credits.thanks=Thanks to
# 3d window
dialog.3d.title=Prune vista 3-D
dialog.3d.altitudecap=Escala de las altitudes
+dialog.3dlines.title=Prune gridlines
+dialog.3dlines.empty=Ninguna información coordenadas encontrada!
+dialog.3dlines.intro=Información de gridlines
# Buttons
button.ok=Aceptar
button.overwrite=Sobreescribir
button.moveup=Mover hacia arriba
button.movedown=Mover hacia abajo
-button.startrange=Fijar comienzo
-button.endrange=Fijar final
button.deletepoint=Eliminar punto
button.deleterange=Eliminar rango
+button.showlines=Mostrar gridlines
button.edit=Editar
button.exit=Salir
button.close=Cerrar
button.continue=Continúe
+button.yes=Si
+button.no=No
+button.yestoall=Si por todo
+button.notoall=No por todo
+button.selectall=Seleccionar todo
+button.selectnone=Seleccionar nada
# Display components
display.nodata=Ningún dato cargado
display.range.time.days=d
details.waypointsphotos.waypoints=Waypoints
details.waypointsphotos.photos=Fotos
+details.photodetails=Detalles del Foto
+details.nophoto=Ningún foto seleccionado
+details.photo.loading=Cargar
# Field names
fieldname.latitude=Latitud
undo.loadphotos=cargar fotos
undo.editpoint=editar punto
undo.deletepoint=eliminar punto
+undo.deletephoto=eliminar foto
undo.deleterange=eliminar rango
undo.compress=comprimir track
undo.insert=insertar puntos
undo.deleteduplicates=eliminar duplicados
undo.reverse=invertir rango
undo.rearrangewaypoints=reordenar waypoints
+undo.connectphoto=connectar foto
# Error messages
error.save.dialogtitle=Fallo al guardar datos
error.save.nodata=Ningún dato salvado
error.save.failed=Fallo al guardar datos al archivo:
+error.saveexif.filenotfound=Archivo no encontrado
+error.saveexif.cannotoverwrite1=No se puede guardar
+error.saveexif.cannotoverwrite2=. Guardar a una copia?
error.load.dialogtitle=Fallo al cargar datos
error.load.noread=No se puede leer el fichero
+error.load.nopoints=Ninguna información coordenadas encontrada
+error.load.unknownxml=Unrecognised xml format:
+error.load.othererror=Fallo al cargar datos:
error.jpegload.dialogtitle=Error cargando fotos
error.jpegload.nofilesfound=Ningún archivo encontrado
error.jpegload.nojpegsfound=Ningún archivo jpeg encontrado
--- /dev/null
+# Text entries for the Prune application
+# French entries as extra
+
+# Menu entries
+menu.file=Fichier
+menu.file.open=Ouvrir
+menu.file.addphotos=Ouvrir photos
+menu.file.save=Enregistrer
+menu.file.exportkml=Exporter au KML
+menu.file.exportpov=Exporter au POV
+menu.file.exit=Quitter
+menu.edit=Édition
+menu.edit.undo=Undo
+menu.edit.clearundo=Purger undo liste
+menu.edit.editpoint=Editer point
+menu.edit.editwaypointname=Editer nom du waypoint
+menu.edit.deletepoint=Supprimer du point
+menu.edit.deleterange=Supprimer de range
+menu.edit.deleteduplicates=Supprimer des duplicates
+menu.edit.compress=Compacter track
+menu.edit.interpolate=Interpolate
+menu.edit.reverse=Reverse range
+menu.edit.rearrange=Rearrange waypoints
+menu.edit.rearrange.start=Tous à tête de fichier
+menu.edit.rearrange.end=Tous à pied de fichier
+menu.edit.rearrange.nearest=Chaque à prochain point
+menu.select=Sélectionner
+menu.select.all=Tous sélectionner
+menu.select.none=Rien sélectionner
+menu.select.start=Set range start
+menu.select.end=Set range end
+menu.photo=Photo
+menu.photo.saveexif=Enregistrer à Exif
+menu.photo.connect=Connect to point
+menu.photo.delete=Remove photo
+menu.3d=Trois-D
+menu.3d.show3d=Montrer en Trois-D
+menu.help=Aide
+menu.help.about=À propos de Prune
+# Popup menu for map
+menu.map.zoomin=Zoom avant
+menu.map.zoomout=Zoom arrière
+menu.map.zoomfull=Zoom to full scale
+menu.map.autopan=Pan automatique
+
+# Dialogs
+dialog.exit.confirm.title=Terminer Prune
+dialog.exit.confirm.text=Le data a été modifié. Souhaitez-vous terminer Prune sans enregistrement?
+dialog.openappend.title=Append to existing data
+dialog.openappend.text=Append this data to the data already loaded?
+dialog.deletepoint.title=Delete Point
+dialog.deletepoint.deletephoto=Delete photo attached to this point?
+dialog.deletephoto.title=Delete Photo
+dialog.deletephoto.deletepoint=Delete point attached to this photo?
+dialog.deleteduplicates.title=Delete Duplicates
+dialog.deleteduplicates.single.text=duplicate was deleted
+dialog.deleteduplicates.multi.text=duplicates were deleted
+dialog.deleteduplicates.nonefound=No duplicates found
+dialog.compresstrack.title=Compress Track
+dialog.compresstrack.parameter.text=Parameter for compression (lower number = more compression)
+dialog.compresstrack.text=Track compressed
+dialog.compresstrack.single.text=data point was removed
+dialog.compresstrack.multi.text=data points were removed
+dialog.compresstrack.nonefound=No data points could be removed
+dialog.openoptions.title=Open options
+dialog.openoptions.filesnippet=Extract of fichier
+dialog.load.table.field=Field
+dialog.load.table.datatype=Data Type
+dialog.load.table.description=Description
+dialog.delimiter.label=Séparateur de texte
+dialog.delimiter.comma=Virgule ,
+dialog.delimiter.tab=Tabulation
+dialog.delimiter.space=Espace
+dialog.delimiter.semicolon=Point-virgule ;
+dialog.delimiter.other=Autres
+dialog.openoptions.deliminfo.records=records, avec
+dialog.openoptions.deliminfo.fields=fields
+dialog.openoptions.deliminfo.norecords=Pas de records
+dialog.openoptions.tabledesc=Extract of fichier
+dialog.openoptions.altitudeunits=Unités de altitude
+dialog.jpegload.subdirectories=Subdirectories aussi
+dialog.jpegload.loadjpegswithoutcoords=Photos sans coordonnées aussi
+dialog.jpegload.progress.title=Loading photos
+dialog.jpegload.progress=Please wait while the photos are searched
+dialog.jpegload.title=Loaded photos
+dialog.jpegload.photoadded=photo was added
+dialog.jpegload.photosadded=photos were added
+dialog.saveoptions.title=Save fichier
+dialog.save.fieldstosave=Fields to save
+dialog.save.table.field=Field
+dialog.save.table.hasdata=Has data
+dialog.save.table.save=Enregistrer
+dialog.save.headerrow=Output header row
+dialog.save.coordinateunits=Unités des coordonnées
+dialog.save.units.original=Original
+dialog.save.altitudeunits=Unités de altitude
+dialog.save.oktitle=File saved
+dialog.save.ok1=Successfully saved
+dialog.save.ok2=points to fichier
+dialog.save.overwrite.title=File already exists
+dialog.save.overwrite.text=This fichier already exists. Are you sure you want to overwrite the fichier?
+dialog.exportkml.title=Exporter au KML
+dialog.exportkml.text=Title for the data
+dialog.exportkml.kmz=Compress to make kmz fichier
+dialog.exportkml.exportimages=Export image thumbnails to kmz
+dialog.exportkml.filetype=Classeur KML, KMZ
+dialog.exportpov.title=Exporter au POV
+dialog.exportpov.text=Please enter the parameters for the POV export
+dialog.exportpov.font=Police
+dialog.exportpov.camerax=Camera X
+dialog.exportpov.cameray=Camera Y
+dialog.exportpov.cameraz=Camera Z
+dialog.exportpov.filetype=Classeur POV
+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.interpolate.title=Interpolate points
+dialog.interpolate.parameter.text=Number of points to insert between selected points
+dialog.undo.title=Undo action(s)
+dialog.undo.pretext=Please select the action(s) to undo
+dialog.confirmundo.title=Operation(s) undone
+dialog.confirmundo.single.text=operation undone.
+dialog.confirmundo.multiple.text=operations undone.
+dialog.undo.none.title=Cannot undo
+dialog.undo.none.text=No operations to undo!
+dialog.clearundo.title=Clear undo list
+dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost!
+dialog.pointedit.title=Edit point
+dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value
+dialog.pointedit.table.field=Field
+dialog.pointedit.table.value=Value
+dialog.pointedit.table.changed=Changed
+dialog.pointedit.changevalue.text=Enter the new value for this field
+dialog.pointedit.changevalue.title=Edit field
+dialog.pointnameedit.title=Edit waypoint name
+dialog.pointnameedit.name=Waypoint name
+dialog.pointnameedit.uppercase=CASSE MAJUSCULES
+dialog.pointnameedit.lowercase=casse minuscules
+dialog.pointnameedit.sentencecase=Casse Sentence
+dialog.saveexif.title=Save Exif
+dialog.saveexif.intro=Select the photos to save using the checkboxes
+dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save
+dialog.saveexif.noexiftool=No exiftool program could be found. Continue?
+dialog.saveexif.table.photoname=Photo name
+dialog.saveexif.table.status=Status
+dialog.saveexif.table.save=Save
+dialog.saveexif.photostatus.connected=Connected
+dialog.saveexif.photostatus.disconnected=Disconnected
+dialog.saveexif.photostatus.modified=Modified
+dialog.saveexif.overwrite=Overwrite fichiers
+dialog.saveexif.ok1=Saved
+dialog.saveexif.ok2=photo fichiers
+dialog.about.title=À propos de Prune
+dialog.about.version=Version
+dialog.about.build=Build
+dialog.about.summarytext1=Prune est une programme for loading, displaying and editing data from GPS receivers.
+dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.<br>Copying, redistribution and modification are permitted and encouraged<br>according to the conditions in the included <code>license.txt</code> file.
+dialog.about.summarytext3=Consultez la page <code style="font-weight:bold">http://activityworkshop.net/</code> pour de plus détails et user guides.
+dialog.about.translatedby=Texte en français de activityworkshop.
+dialog.about.systeminfo=Info de Systeme
+dialog.about.systeminfo.os=Operating Systeme
+dialog.about.systeminfo.java=Java Runtime
+dialog.about.systeminfo.java3d=Java3d installed
+dialog.about.systeminfo.povray=Povray installed
+dialog.about.systeminfo.exiftool=Exiftool installed
+dialog.about.yes=Oui
+dialog.about.no=Non
+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.translations=Translations helped by
+dialog.about.credits.devtools=Development tools
+dialog.about.credits.othertools=Other tools
+dialog.about.credits.thanks=Thanks to
+
+# 3d window
+dialog.3d.title=Vue Trois-d de Prune
+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
+
+# Buttons
+button.ok=OK
+button.back=Retour
+button.next=Prochain
+button.finish=Fini
+button.cancel=Annuler
+button.overwrite=Overwrite
+button.moveup=Move up
+button.movedown=Move down
+button.deletepoint=Delete point
+button.deleterange=Delete range
+button.showlines=Show lines
+button.edit=Éditer
+button.exit=Terminer
+button.close=Fermer
+button.continue=Continuer
+button.yes=Oui
+button.no=Non
+button.yestoall=Oui pour tous
+button.notoall=Non pour tous
+button.selectall=Sélecter tous
+button.selectnone=Sélecter rien
+
+# Display components
+display.nodata=No data loaded
+display.noaltitudes=Track data does not include altitudes
+details.trackdetails=Track details
+details.notrack=No track loaded
+details.track.points=Points
+details.track.file=File
+details.track.numfiles=Number of fichiers
+details.pointdetails=Point details
+details.index.selected=Index
+details.index.of=de
+details.nopointselection=No point selected
+details.photofile=Photo fichier
+details.norangeselection=No range selected
+details.rangedetails=Range details
+details.range.selected=Selected
+details.range.to=Ã
+details.altitude.to=Ã
+details.range.climb=Montée
+details.range.descent=Descente
+details.distanceunits=Unités de distance
+display.range.time.secs=s
+display.range.time.mins=m
+display.range.time.hours=h
+display.range.time.days=j
+details.waypointsphotos.waypoints=Waypoints
+details.waypointsphotos.photos=Photos
+details.photodetails=Photo details
+details.nophoto=Pas de Photo
+details.photo.loading=Loading
+
+# Field names
+fieldname.latitude=Latitude
+fieldname.longitude=Longitude
+fieldname.altitude=Altitude
+fieldname.timestamp=Timestamp
+fieldname.waypointname=Nom
+fieldname.waypointtype=Type
+fieldname.newsegment=Segment
+fieldname.custom=Custom
+fieldname.prefix=Champ
+fieldname.distance=Distance
+fieldname.duration=Durée
+
+# Measurement units
+units.metres=mètres
+units.metres.short=m
+units.feet=pieds
+units.feet.short=p
+units.kilometres=Kilomètres
+units.kilometres.short=km
+units.miles=lieues
+units.miles.short=li
+units.degminsec=Deg-min-sec
+units.degmin=Deg-min
+units.deg=Degrés
+
+# Cardinals for 3d plots
+cardinal.n=N
+cardinal.s=S
+cardinal.e=E
+cardinal.w=O
+
+# Undo operations
+undo.load=load data
+undo.loadphotos=load photos
+undo.editpoint=editer point
+undo.deletepoint=delete point
+undo.deletephoto=remove photo
+undo.deleterange=delete range
+undo.compress=compress track
+undo.insert=insert points
+undo.deleteduplicates=delete duplicates
+undo.reverse=reverse range
+undo.rearrangewaypoints=rearrange waypoints
+undo.connectphoto=connect photo
+
+# Error messages
+error.save.dialogtitle=Error saving data
+error.save.nodata=No data to save
+error.save.failed=Failed to save the data to fichier:
+error.saveexif.filenotfound=Failed to find photo fichier
+error.saveexif.cannotoverwrite1=Photo fichier
+error.saveexif.cannotoverwrite2=is read-only and can't be overwritten. Write to copy?
+error.load.dialogtitle=Error loading data
+error.load.noread=Cannot read fichier
+error.load.nopoints=No coordinate information found in the fichier
+error.load.unknownxml=Unrecognised xml format:
+error.load.othererror=Error reading fichier:
+error.jpegload.dialogtitle=Error loading photos
+error.jpegload.nofilesfound=No fichiers found
+error.jpegload.nojpegsfound=No jpeg fichiers found
+error.jpegload.noexiffound=No EXIF information found
+error.jpegload.nogpsfound=No GPS information found
+error.undofailed.title=Undo failed
+error.undofailed.text=Failed to undo operation
+error.function.noop.title=Function had no effect
+error.rearrange.noop=Rearranging waypoints had no effect
+error.function.notimplemented=Desoler, this function has not yet been implemented.
+error.function.notavailable.title=Function not available
+error.function.nojava3d=This function requires the Java3d library,\navailable from Sun.com or Blackdown.org.
+error.3d.title=Error in 3d display
+error.3d=An error occurred with the 3d display
{
for (int i=0; i<size; i++)
{
- if (result[i].length() > inMaxWidth)
- result[i] = result[i].trim();
- if (result[i].length() > inMaxWidth)
- result[i] = result[i].substring(0, inMaxWidth);
+ if (result[i] == null)
+ result[i] = "";
+ else
+ {
+ if (result[i].length() > inMaxWidth)
+ result[i] = result[i].trim();
+ if (result[i].length() > inMaxWidth)
+ result[i] = result[i].substring(0, inMaxWidth);
+ }
}
}
return result;
package tim.prune.load;
-import java.awt.BorderLayout;
-import java.awt.CardLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import javax.swing.*;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.table.TableCellEditor;
-
import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
import tim.prune.App;
import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
-import tim.prune.data.Field;
+import tim.prune.load.xml.XmlFileLoader;
/**
- * Special class to handle file loading including GUI options,
- * and conversion to a Track object
+ * Generic FileLoader class to select a file
+ * and pass handling on to appropriate loader
*/
public class FileLoader
{
- private File _file = null;
- private App _app = null;
- private JFrame _parentFrame = null;
- private JDialog _dialog = null;
- private JPanel _cardPanel = null;
- private CardLayout _layout = null;
- private JButton _backButton = null, _nextButton = null;
- private JButton _finishButton = null;
- private JButton _moveUpButton = null, _moveDownButton = null;
- private JRadioButton[] _delimiterRadios = null;
- private JTextField _otherDelimiterText = null;
- private JLabel _statusLabel = null;
- private DelimiterInfo[] _delimiterInfos = null;
private JFileChooser _fileChooser = null;
- private FileCacher _fileCacher = null;
- private JList _snippetBox = null;
- private FileExtractTableModel _fileExtractTableModel = null;
- private JTable _fieldTable;
- private FieldSelectionTableModel _fieldTableModel = null;
- private JComboBox _unitsDropDown = null;
- private int _selectedField = -1;
- private char _currentDelimiter = ',';
-
- // previously selected values
- private char _lastUsedDelimiter = ',';
- private int _lastNumFields = -1;
- private Field[] _lastSelectedFields = null;
- private int _lastAltitudeFormat = Altitude.FORMAT_NONE;
-
- // constants
- private static final int SNIPPET_SIZE = 6;
- private static final int MAX_SNIPPET_WIDTH = 80;
- private static final char[] DELIMITERS = {',', '\t', ';', ' '};
-
-
- /**
- * Inner class to listen for delimiter change operations
- */
- private class DelimListener implements ActionListener, DocumentListener
- {
- public void actionPerformed(ActionEvent e)
- {
- informDelimiterSelected();
- }
- public void changedUpdate(DocumentEvent e)
- {
- informDelimiterSelected();
- }
- public void insertUpdate(DocumentEvent e)
- {
- informDelimiterSelected();
- }
- public void removeUpdate(DocumentEvent e)
- {
- informDelimiterSelected();
- }
- }
+ private JFrame _parentFrame;
+ private TextFileLoader _textFileLoader = null;
+ private XmlFileLoader _xmlFileLoader = null;
/**
*/
public FileLoader(App inApp, JFrame inParentFrame)
{
- _app = inApp;
_parentFrame = inParentFrame;
+ _textFileLoader = new TextFileLoader(inApp, inParentFrame);
+ _xmlFileLoader = new XmlFileLoader(inApp, inParentFrame);
}
_fileChooser = new JFileChooser();
if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
{
- _file = _fileChooser.getSelectedFile();
- if (preCheckFile(_file))
+ File file = _fileChooser.getSelectedFile();
+ // Check file exists and is readable
+ if (file != null && file.exists() && file.canRead())
{
- _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.openoptions.title"), true);
- _dialog.setLocationRelativeTo(_parentFrame);
- _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- _dialog.getContentPane().add(makeDialogComponents());
-
- // select best separator according to row counts (more is better)
- int bestDelim = getBestOption(_delimiterInfos[0].getNumWinningRecords(),
- _delimiterInfos[1].getNumWinningRecords(), _delimiterInfos[2].getNumWinningRecords(),
- _delimiterInfos[3].getNumWinningRecords());
- if (bestDelim >= 0)
- _delimiterRadios[bestDelim].setSelected(true);
+ // Check file type to see if it's xml or just normal text
+ String fileExtension = file.getName().toLowerCase();
+ if (fileExtension.length() > 4)
+ {fileExtension = fileExtension.substring(fileExtension.length() - 4);}
+ if (fileExtension.equals(".kml") || fileExtension.equals(".gpx")
+ || fileExtension.equals(".xml"))
+ {
+ // Use xml loader for kml, gpx and xml filenames
+ _xmlFileLoader.openFile(file);
+ }
else
- _delimiterRadios[_delimiterRadios.length-1].setSelected(true);
- informDelimiterSelected();
- _dialog.pack();
- _dialog.show();
+ {
+ // Use text loader for everything else
+ _textFileLoader.openFile(file);
+ }
}
else
{
+ // couldn't read file - show error message
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.load.noread"),
I18nManager.getText("error.load.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
}
}
-
- /**
- * Check the given file for readability and funny characters,
- * and count the fields for the various separators
- * @param inFile file to check
- */
- private boolean preCheckFile(File inFile)
- {
- // Check file exists and is readable
- if (inFile == null || !inFile.exists() || !inFile.canRead())
- {
- return false;
- }
- // Use a FileCacher to read the file into an array
- _fileCacher = new FileCacher(inFile);
-
- // Check each line of the file
- String[] fileContents = _fileCacher.getContents();
- boolean fileOK = true;
- _delimiterInfos = new DelimiterInfo[5];
- for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
-
- String currLine = null;
- String[] splitFields = null;
- int commaFields = 0, semicolonFields = 0, tabFields = 0, spaceFields = 0;
- for (int lineNum=0; lineNum<fileContents.length && fileOK; lineNum++)
- {
- currLine = fileContents[lineNum];
- // check for invalid characters
- if (currLine.indexOf('\0') >= 0) {fileOK = false;}
- // check for commas
- splitFields = currLine.split(",");
- commaFields = splitFields.length;
- if (commaFields > 1) _delimiterInfos[0].incrementNumRecords();
- _delimiterInfos[0].updateMaxFields(commaFields);
- // check for tabs
- splitFields = currLine.split("\t");
- tabFields = splitFields.length;
- if (tabFields > 1) _delimiterInfos[1].incrementNumRecords();
- _delimiterInfos[1].updateMaxFields(tabFields);
- // check for semicolons
- splitFields = currLine.split(";");
- semicolonFields = splitFields.length;
- if (semicolonFields > 1) _delimiterInfos[2].incrementNumRecords();
- _delimiterInfos[2].updateMaxFields(semicolonFields);
- // check for spaces
- splitFields = currLine.split(" ");
- spaceFields = splitFields.length;
- if (spaceFields > 1) _delimiterInfos[3].incrementNumRecords();
- _delimiterInfos[3].updateMaxFields(spaceFields);
- // increment counters
- int bestScorer = getBestOption(commaFields, tabFields, semicolonFields, spaceFields);
- if (bestScorer >= 0)
- _delimiterInfos[bestScorer].incrementNumWinningRecords();
- }
- return fileOK;
- }
-
-
- /**
- * Get the index of the best one in the list
- * @return the index of the maximum of the four given values
- */
- private static int getBestOption(int inOpt0, int inOpt1, int inOpt2, int inOpt3)
- {
- int bestIndex = -1;
- int maxScore = 1;
- if (inOpt0 > maxScore) {bestIndex = 0; maxScore = inOpt0;}
- if (inOpt1 > maxScore) {bestIndex = 1; maxScore = inOpt1;}
- if (inOpt2 > maxScore) {bestIndex = 2; maxScore = inOpt2;}
- if (inOpt3 > maxScore) {bestIndex = 3; maxScore = inOpt3;}
- return bestIndex;
- }
-
-
- /**
- * Make the components for the open options dialog
- * @return Component for all options
- */
- private Component makeDialogComponents()
- {
- JPanel wholePanel = new JPanel();
- wholePanel.setLayout(new BorderLayout());
-
- // add buttons to south
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
- _backButton = new JButton(I18nManager.getText("button.back"));
- _backButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- _layout.previous(_cardPanel);
- _backButton.setEnabled(false);
- _nextButton.setEnabled(true);
- _finishButton.setEnabled(false);
- }
- });
- _backButton.setEnabled(false);
- buttonPanel.add(_backButton);
- _nextButton = new JButton(I18nManager.getText("button.next"));
- _nextButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- prepareSecondPanel();
- _layout.next(_cardPanel);
- _nextButton.setEnabled(false);
- _backButton.setEnabled(true);
- _finishButton.setEnabled(true);
- }
- });
- buttonPanel.add(_nextButton);
- _finishButton = new JButton(I18nManager.getText("button.finish"));
- _finishButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- finished();
- }
- });
- _finishButton.setEnabled(false);
- buttonPanel.add(_finishButton);
- JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- _dialog.dispose();
- }
- });
- buttonPanel.add(cancelButton);
- wholePanel.add(buttonPanel, BorderLayout.SOUTH);
-
- // Make the two cards, for delimiter and fields
- _cardPanel = new JPanel();
- _layout = new CardLayout();
- _cardPanel.setLayout(_layout);
- JPanel firstCard = new JPanel();
- firstCard.setLayout(new BorderLayout());
-
- JPanel delimsPanel = new JPanel();
- delimsPanel.setLayout(new GridLayout(0, 2));
- delimsPanel.add(new JLabel(I18nManager.getText("dialog.delimiter.label")));
- delimsPanel.add(new JLabel("")); // blank label to go to next grid row
- // radio buttons
- _delimiterRadios = new JRadioButton[5];
- _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
- delimsPanel.add(_delimiterRadios[0]);
- _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
- delimsPanel.add(_delimiterRadios[1]);
- _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
- delimsPanel.add(_delimiterRadios[2]);
- _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
- delimsPanel.add(_delimiterRadios[3]);
- JPanel otherPanel = new JPanel();
- otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
- _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
- otherPanel.add(_delimiterRadios[4]);
- _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
- otherPanel.add(_otherDelimiterText);
- // Group radio buttons
- ButtonGroup delimGroup = new ButtonGroup();
- DelimListener delimListener = new DelimListener();
- for (int i=0; i<_delimiterRadios.length; i++)
- {
- delimGroup.add(_delimiterRadios[i]);
- _delimiterRadios[i].addActionListener(delimListener);
- }
- _otherDelimiterText.getDocument().addDocumentListener(delimListener);
- delimsPanel.add(new JLabel(""));
- delimsPanel.add(otherPanel);
- _statusLabel = new JLabel("");
- delimsPanel.add(_statusLabel);
- firstCard.add(delimsPanel, BorderLayout.SOUTH);
- // load snippet to show first few lines
- _snippetBox = new JList(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
- _snippetBox.setEnabled(false);
- firstCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", _snippetBox), BorderLayout.CENTER);
-
- // Second screen, for field order selection
- JPanel secondCard = new JPanel();
- secondCard.setLayout(new BorderLayout());
- // table for file contents
- _fileExtractTableModel = new FileExtractTableModel();
- JTable extractTable = new JTable(_fileExtractTableModel);
- JScrollPane tableScrollPane = new JScrollPane(extractTable);
- extractTable.setPreferredScrollableViewportSize(new Dimension(350, 80));
- extractTable.getTableHeader().setReorderingAllowed(false);
- secondCard.add(makeLabelledPanel("dialog.openoptions.tabledesc", tableScrollPane), BorderLayout.NORTH);
- JPanel innerPanel2 = new JPanel();
- innerPanel2.setLayout(new BorderLayout());
- _fieldTable = new JTable(new FieldSelectionTableModel());
- _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
- // add listener for selected table row
- _fieldTable.getSelectionModel().addListSelectionListener(
- new ListSelectionListener() {
- public void valueChanged(ListSelectionEvent e) {
- ListSelectionModel lsm = (ListSelectionModel) e.getSource();
- if (lsm.isSelectionEmpty()) {
- //no rows are selected
- selectField(-1);
- } else {
- selectField(lsm.getMinSelectionIndex());
- }
- }
- });
- JPanel tablePanel = new JPanel();
- tablePanel.setLayout(new BorderLayout());
- tablePanel.add(_fieldTable.getTableHeader(), BorderLayout.NORTH);
- tablePanel.add(_fieldTable, BorderLayout.CENTER);
- innerPanel2.add(tablePanel, BorderLayout.CENTER);
-
- JPanel innerPanel3 = new JPanel();
- innerPanel3.setLayout(new BoxLayout(innerPanel3, BoxLayout.Y_AXIS));
- _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
- _moveUpButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- int currRow = _fieldTable.getSelectedRow();
- closeTableComboBox(currRow);
- _fieldTableModel.moveUp(currRow);
- _fieldTable.setRowSelectionInterval(currRow-1, currRow-1);
- }
- });
- innerPanel3.add(_moveUpButton);
- _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
- _moveDownButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- int currRow = _fieldTable.getSelectedRow();
- closeTableComboBox(currRow);
- _fieldTableModel.moveDown(currRow);
- _fieldTable.setRowSelectionInterval(currRow+1, currRow+1);
- }
- });
- innerPanel3.add(_moveDownButton);
- innerPanel3.add(Box.createVerticalStrut(70));
-
- innerPanel2.add(innerPanel3, BorderLayout.EAST);
- secondCard.add(innerPanel2, BorderLayout.CENTER);
- JPanel altUnitsPanel = new JPanel();
- altUnitsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
- altUnitsPanel.add(new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits")));
- String[] units = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
- _unitsDropDown = new JComboBox(units);
- altUnitsPanel.add(_unitsDropDown);
- secondCard.add(altUnitsPanel, BorderLayout.SOUTH);
- _cardPanel.add(firstCard, "card1");
- _cardPanel.add(secondCard, "card2");
-
- wholePanel.add(_cardPanel, BorderLayout.CENTER);
- return wholePanel;
- }
-
-
- /**
- * Close the combo box on the selected row of the field table
- * @param inRow currently selected row number
- */
- private void closeTableComboBox(int inRow)
- {
- TableCellEditor editor = _fieldTable.getCellEditor(inRow, 1);
- if (editor != null)
- {
- editor.stopCellEditing();
- }
- }
-
-
- /**
- * change the status based on selection of a delimiter
- */
- protected void informDelimiterSelected()
- {
- for (int i=0; i<(_delimiterRadios.length-1); i++)
- {
- if (_delimiterRadios[i].isSelected())
- {
- int numRecords = _delimiterInfos[i].getNumRecords();
- if (numRecords == 0)
- {
- _statusLabel.setText(I18nManager.getText("dialog.openoptions.deliminfo.norecords"));
- }
- else
- {
- _statusLabel.setText("" + numRecords + " " + I18nManager.getText("dialog.openoptions.deliminfo.records")
- + _delimiterInfos[i].getMaxFields() + " " + I18nManager.getText("dialog.openoptions.deliminfo.fields"));
- }
- }
- }
- if (_delimiterRadios[_delimiterRadios.length-1].isSelected())
- {
- _statusLabel.setText("");
- }
- // enable/disable next button
- _nextButton.setEnabled(_delimiterRadios[4].isSelected() == false
- || _otherDelimiterText.getText().length() == 1);
- }
-
-
- /**
- * Get the delimiter info from the first step
- * @return delimiter information object for the selected delimiter
- */
- public DelimiterInfo getSelectedDelimiterInfo()
- {
- for (int i=0; i<4; i++)
- if (_delimiterRadios[i].isSelected()) return _delimiterInfos[i];
- // must be "other" - build info if necessary
- if (_delimiterInfos[4] == null)
- _delimiterInfos[4] = new DelimiterInfo(_otherDelimiterText.getText().charAt(0));
- return _delimiterInfos[4];
- }
-
-
- /**
- * Use the delimiter selected to determine the fields in the file
- * and prepare the second panel accordingly
- */
- private void prepareSecondPanel()
- {
- DelimiterInfo info = getSelectedDelimiterInfo();
- FileSplitter splitter = new FileSplitter(_fileCacher);
- // Check info makes sense - num fields > 0, num records > 0
- // set "Finished" button to disabled if not ok
- // TODO: Work out if there are header rows or not, save?
- // Try to match header rows with fields
- // Try to match data with fields
- // Add data to GUI elements
- Object[][] tableData = splitter.splitFieldData(info.getDelimiter());
- // possible to ignore blank columns here
- _currentDelimiter = info.getDelimiter();
- _fileExtractTableModel.updateData(tableData);
- _fieldTableModel = new FieldSelectionTableModel();
-
- // Check number of fields and use last ones if count matches
- Field[] startFieldArray = null;
- if (_lastSelectedFields != null && splitter.getNumColumns() == _lastSelectedFields.length)
- startFieldArray = _lastSelectedFields;
- else
- startFieldArray = splitter.makeDefaultFields();
- _fieldTableModel.updateData(startFieldArray);
- _fieldTable.setModel(_fieldTableModel);
- // add dropdowns to second column
- JComboBox fieldTypesBox = new JComboBox();
- for (int i=0; i<Field.ALL_AVAILABLE_FIELDS.length; i++)
- {
- fieldTypesBox.addItem(Field.ALL_AVAILABLE_FIELDS[i].getName());
- }
- _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
-
- // Set altitude format to same as last time if available
- if (_lastAltitudeFormat == Altitude.FORMAT_METRES)
- _unitsDropDown.setSelectedIndex(0);
- else if (_lastAltitudeFormat == Altitude.FORMAT_FEET)
- _unitsDropDown.setSelectedIndex(1);
- // no selection on field list
- selectField(-1);
- }
-
-
- /**
- * All options have been selected, so load file
- */
- private void finished()
- {
- // Save delimiter, field array and altitude format for later use
- _lastUsedDelimiter = _currentDelimiter;
- _lastSelectedFields = _fieldTableModel.getFieldArray();
- int altitudeFormat = Altitude.FORMAT_METRES;
- if (_unitsDropDown.getSelectedIndex() == 1)
- {
- altitudeFormat = Altitude.FORMAT_FEET;
- }
- _lastAltitudeFormat = altitudeFormat;
- // give data to App
- _app.informDataLoaded(_fieldTableModel.getFieldArray(),
- _fileExtractTableModel.getData(), altitudeFormat,
- _file.getName());
- // clear up file cacher
- _fileCacher.clear();
- // dispose of dialog
- _dialog.dispose();
- }
-
-
- /**
- * Make a panel with a label and a component
- * @param inLabelKey label key to use
- * @param inComponent component for main area of panel
- * @return labelled Panel
- */
- private static JPanel makeLabelledPanel(String inLabelKey, JComponent inComponent)
- {
- JPanel panel = new JPanel();
- panel.setLayout(new BorderLayout());
- panel.add(new JLabel(I18nManager.getText(inLabelKey)), BorderLayout.NORTH);
- panel.add(inComponent, BorderLayout.CENTER);
- return panel;
- }
-
-
- /**
- * An entry in the field list has been selected
- * @param inFieldNum index of field, starting with 0
- */
- private void selectField(int inFieldNum)
- {
- if (inFieldNum == -1 || inFieldNum != _selectedField)
- {
- _selectedField = inFieldNum;
- _moveUpButton.setEnabled(inFieldNum > 0);
- _moveDownButton.setEnabled(inFieldNum >= 0
- && inFieldNum < (_fieldTableModel.getRowCount()-1));
- }
- }
-
-
/**
- * @return the last delimiter character used for a load
+ * @return the last delimiter character used for a text file load
*/
public char getLastUsedDelimiter()
{
- return _lastUsedDelimiter;
+ return _textFileLoader.getLastUsedDelimiter();
}
}
import tim.prune.data.Latitude;
import tim.prune.data.Longitude;
import tim.prune.data.Photo;
+import tim.prune.data.PhotoStatus;
+import tim.prune.data.Timestamp;
import tim.prune.drew.jpeg.ExifReader;
import tim.prune.drew.jpeg.JpegData;
import tim.prune.drew.jpeg.JpegException;
private JFrame _parentFrame = null;
private JFileChooser _fileChooser = null;
private JCheckBox _subdirCheckbox = null;
+ private JCheckBox _noExifCheckbox = null;
private JDialog _progressDialog = null;
private JProgressBar _progressBar = null;
private int[] _fileCounts = null;
_parentFrame = inParentFrame;
}
+
/**
* Select an input file and open the GUI frame
* to select load options
_fileChooser = new JFileChooser();
_fileChooser.setMultiSelectionEnabled(true);
_fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+ _fileChooser.setDialogTitle(I18nManager.getText("menu.file.addphotos"));
_subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories"));
_subdirCheckbox.setSelected(true);
- _fileChooser.setAccessory(_subdirCheckbox);
+ _noExifCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegswithoutcoords"));
+ _noExifCheckbox.setSelected(true);
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.add(_subdirCheckbox);
+ panel.add(_noExifCheckbox);
+ _fileChooser.setAccessory(panel);
}
if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
{
// Initialise arrays, errors, summaries
_fileCounts = new int[4]; // files, jpegs, exifs, gps
_photos = new ArrayList();
- // Loop over selected files/directories
File[] files = _fileChooser.getSelectedFiles();
+ // Loop recursively over selected files/directories to count files
int numFiles = countFileList(files, true, _subdirCheckbox.isSelected());
- // if (false) System.out.println("Found " + numFiles + " files");
+ // Set up the progress bar for this number of files
_progressBar.setMaximum(numFiles);
_progressBar.setValue(0);
_cancelled = false;
+
+ // Process the files recursively and build lists of photos
processFileList(files, true, _subdirCheckbox.isSelected());
_progressDialog.hide();
- if (_cancelled) return;
- // System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1] + ", " + _fileCounts[2] + ", " + _fileCounts[3]);
+ if (_cancelled) {return;}
+
+ //System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1]
+ // + ", " + _fileCounts[2] + ", " + _fileCounts[3]);
if (_fileCounts[0] == 0)
{
+ // No files found at all
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nofilesfound"),
I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
else if (_fileCounts[1] == 0)
{
+ // No jpegs found
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nojpegsfound"),
I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
- else if (_fileCounts[2] == 0)
+ else if (!_noExifCheckbox.isSelected() && _fileCounts[2] == 0)
{
+ // Need coordinates but no exif found
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.noexiffound"),
I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
- else if (_fileCounts[3] == 0)
+ else if (!_noExifCheckbox.isSelected() && _fileCounts[3] == 0)
{
+ // Need coordinates but no gps information found
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nogpsfound"),
I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
else
{
- // Load information into dialog for confirmation
+ // Found some photos to load
+ // TODO: Load jpeg information into dialog for confirmation?
+ // Pass information back to app
_app.informPhotosLoaded(_photos);
}
}
{
// Always process first directory,
// only process subdirectories if checkbox selected
- processDirectory(file, inDescend);
+ File[] files = file.listFiles();
+ processFileList(files, false, inDescend);
}
}
else
{
- // file doesn't exist or isn't readable - record error
+ // file doesn't exist or isn't readable - ignore error
}
- // check for cancel
+ // check for cancel button pressed
if (_cancelled) break;
}
}
*/
private void processFile(File inFile)
{
+ // Update progress bar
_fileCounts[0]++; // file found
_progressBar.setValue(_fileCounts[0]);
_progressBar.setString("" + _fileCounts[0] + " / " + _progressBar.getMaximum());
_progressBar.repaint();
+
+ // Check whether filename corresponds with accepted filenames
+ if (!acceptPhotoFilename(inFile.getName())) {return;}
+
+ // Create Photo object
+ Photo photo = new Photo(inFile);
+ // Try to get information out of exif
try
{
JpegData jpegData = new ExifReader(inFile).extract();
_fileCounts[1]++; // jpeg found (no exception thrown)
-// if (jpegData.getNumErrors() > 0)
-// System.out.println("Number of errors was: " + jpegData.getNumErrors() + ": " + jpegData.getErrors().get(0));
if (jpegData.getExifDataPresent())
- _fileCounts[2]++; // exif found
+ {_fileCounts[2]++;} // exif found
if (jpegData.isValid())
{
-// if (false && jpegData.getTimestamp() != null)
-// System.out.println("Timestamp is " + jpegData.getTimestamp()[0].toString() + ":" + jpegData.getTimestamp()[1].toString() + ":" + jpegData.getTimestamp()[2].toString());
-// if (false && jpegData.getDatestamp() != null)
-// System.out.println("Datestamp is " + jpegData.getDatestamp()[0].toString() + ":" + jpegData.getDatestamp()[1].toString() + ":" + jpegData.getDatestamp()[2].toString());
- // Make DataPoint and Photo
+ if (jpegData.getDatestamp() != null && jpegData.getTimestamp() != null)
+ {
+ photo.setTimestamp(createTimestamp(jpegData.getDatestamp(), jpegData.getTimestamp()));
+ }
+ // Make DataPoint and attach to Photo
DataPoint point = createDataPoint(jpegData);
- Photo photo = new Photo(inFile);
point.setPhoto(photo);
photo.setDataPoint(point);
- _photos.add(photo);
-// System.out.println("Made photo: " + photo.getFile().getAbsolutePath() + " with the datapoint: "
-// + point.getLatitude().output(Latitude.FORMAT_DEG_MIN_SEC) + ", "
-// + point.getLongitude().output(Longitude.FORMAT_DEG_MIN_SEC) + ", "
-// + point.getAltitude().getValue(Altitude.FORMAT_METRES));
+ photo.setOriginalStatus(PhotoStatus.TAGGED);
_fileCounts[3]++;
}
}
catch (JpegException jpe) { // don't list errors, just count them
}
- }
-
-
- /**
- * Process the given directory, by looping over its contents
- * and recursively through its subdirectories
- * @param inDirectory directory to read
- * @param inDescend true to descend subdirectories
- */
- private void processDirectory(File inDirectory, boolean inDescend)
- {
- File[] files = inDirectory.listFiles();
- processFileList(files, false, inDescend);
+ // Use file timestamp if exif timestamp isn't available
+ 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())
+ {
+ _photos.add(photo);
+ }
}
// Create model objects from jpeg data
double latval = getCoordinateDoubleValue(inData.getLatitude(),
inData.getLatitudeRef() == 'N' || inData.getLatitudeRef() == 'n');
- Latitude latitude = new Latitude(latval, Latitude.FORMAT_NONE);
+ Latitude latitude = new Latitude(latval, Latitude.FORMAT_DEG_MIN_SEC);
double lonval = getCoordinateDoubleValue(inData.getLongitude(),
inData.getLongitudeRef() == 'E' || inData.getLongitudeRef() == 'e');
- Longitude longitude = new Longitude(lonval, Longitude.FORMAT_NONE);
- Altitude altitude = new Altitude(inData.getAltitude().intValue(), Altitude.FORMAT_METRES);
+ Longitude longitude = new Longitude(lonval, Longitude.FORMAT_DEG_MIN_SEC);
+ Altitude altitude = null;
+ if (inData.getAltitude() != null)
+ {
+ altitude = new Altitude(inData.getAltitude().intValue(), Altitude.FORMAT_METRES);
+ }
return new DataPoint(latitude, longitude, altitude);
}
if (!isPositive) value = -value;
return value;
}
+
+
+ /**
+ * Use the given Rational values to create a timestamp
+ * @param inDate rationals describing date
+ * @param inTime rationals describing time
+ * @return Timestamp object corresponding to inputs
+ */
+ private static Timestamp createTimestamp(Rational[] inDate, Rational[] inTime)
+ {
+ //System.out.println("Making timestamp for date (" + inDate[0].toString() + "," + inDate[1].toString() + "," + inDate[2].toString() + ") and time ("
+ // + inTime[0].toString() + "," + inTime[1].toString() + "," + inTime[2].toString() + ")");
+ return new Timestamp(inDate[0].intValue(), inDate[1].intValue(), inDate[2].intValue(),
+ inTime[0].intValue(), inTime[1].intValue(), inTime[2].intValue());
+ }
+
+
+ /**
+ * 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;
+ }
}
--- /dev/null
+package tim.prune.load;
+
+import tim.prune.data.Photo;
+import tim.prune.data.PhotoList;
+
+/**
+ * This class starts a new thread to preload image sizes
+ * TODO: # Cache small image thumbnails too?
+ */
+public class PhotoMeasurer implements Runnable
+{
+ /** PhotoList to loop through */
+ private PhotoList _photoList = null;
+
+
+ /**
+ * Constructor
+ * @param inPhotoList photo list to loop through
+ */
+ public PhotoMeasurer(PhotoList inPhotoList)
+ {
+ _photoList = inPhotoList;
+ }
+
+
+ /**
+ * Start off the process to measure the photo sizes
+ */
+ public void measurePhotos()
+ {
+ // check if any photos in list
+ if (_photoList != null && _photoList.getNumPhotos() > 0)
+ {
+ // start new thread
+ new Thread(this).start();
+ }
+ }
+
+
+ /**
+ * Run method called in new thread
+ */
+ public void run()
+ {
+ try
+ {
+ // loop over all photos in list
+ for (int i=0; i<_photoList.getNumPhotos(); i++)
+ {
+ Photo photo = _photoList.getPhoto(i);
+ if (photo != null)
+ {
+ // call get size method which will calculate it if necessary
+ photo.getSize();
+ }
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException obe) {} // ignore, must have been changed by other thread
+ }
+}
--- /dev/null
+package tim.prune.load;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+
+import java.io.File;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.Altitude;
+import tim.prune.data.Field;
+
+
+/**
+ * Class to handle loading of text files including GUI options,
+ * and passing loaded data back to App object
+ */
+public class TextFileLoader
+{
+ private File _file = null;
+ private App _app = null;
+ private JFrame _parentFrame = null;
+ private JDialog _dialog = null;
+ private JPanel _cardPanel = null;
+ private CardLayout _layout = null;
+ private JButton _backButton = null, _nextButton = null;
+ private JButton _finishButton = null;
+ private JButton _moveUpButton = null, _moveDownButton = null;
+ private JRadioButton[] _delimiterRadios = null;
+ private JTextField _otherDelimiterText = null;
+ private JLabel _statusLabel = null;
+ private DelimiterInfo[] _delimiterInfos = null;
+ private FileCacher _fileCacher = null;
+ private JList _snippetBox = null;
+ private FileExtractTableModel _fileExtractTableModel = null;
+ private JTable _fieldTable;
+ private FieldSelectionTableModel _fieldTableModel = null;
+ private JComboBox _unitsDropDown = null;
+ private int _selectedField = -1;
+ private char _currentDelimiter = ',';
+
+ // previously selected values
+ private char _lastUsedDelimiter = ',';
+ private Field[] _lastSelectedFields = null;
+ private int _lastAltitudeFormat = Altitude.FORMAT_NONE;
+
+ // constants
+ private static final int SNIPPET_SIZE = 6;
+ private static final int MAX_SNIPPET_WIDTH = 80;
+ private static final char[] DELIMITERS = {',', '\t', ';', ' '};
+
+
+ /**
+ * Inner class to listen for delimiter change operations
+ */
+ private class DelimListener implements ActionListener, DocumentListener
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ informDelimiterSelected();
+ }
+ public void changedUpdate(DocumentEvent e)
+ {
+ informDelimiterSelected();
+ }
+ public void insertUpdate(DocumentEvent e)
+ {
+ informDelimiterSelected();
+ }
+ public void removeUpdate(DocumentEvent e)
+ {
+ informDelimiterSelected();
+ }
+ }
+
+
+ /**
+ * Constructor
+ * @param inApp Application object to inform of track load
+ * @param inParentFrame parent frame to reference for dialogs
+ */
+ public TextFileLoader(App inApp, JFrame inParentFrame)
+ {
+ _app = inApp;
+ _parentFrame = inParentFrame;
+ }
+
+
+ /**
+ * Open the selected file and show the GUI dialog
+ * to select load options
+ */
+ public void openFile(File inFile)
+ {
+ _file = inFile;
+ if (preCheckFile(_file))
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.openoptions.title"), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+
+ // select best separator according to row counts (more is better)
+ int bestDelim = getBestOption(_delimiterInfos[0].getNumWinningRecords(),
+ _delimiterInfos[1].getNumWinningRecords(), _delimiterInfos[2].getNumWinningRecords(),
+ _delimiterInfos[3].getNumWinningRecords());
+ if (bestDelim >= 0)
+ _delimiterRadios[bestDelim].setSelected(true);
+ else
+ _delimiterRadios[_delimiterRadios.length-1].setSelected(true);
+ informDelimiterSelected();
+ _dialog.pack();
+ _dialog.show();
+ }
+ else
+ {
+ JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.load.noread"),
+ I18nManager.getText("error.load.dialogtitle"), JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Check the given file for readability and funny characters,
+ * and count the fields for the various separators
+ * @param inFile file to check
+ */
+ private boolean preCheckFile(File inFile)
+ {
+ // Check file exists and is readable
+ if (inFile == null || !inFile.exists() || !inFile.canRead())
+ {
+ return false;
+ }
+ // Use a FileCacher to read the file into an array
+ _fileCacher = new FileCacher(inFile);
+
+ // Check each line of the file
+ String[] fileContents = _fileCacher.getContents();
+ boolean fileOK = true;
+ _delimiterInfos = new DelimiterInfo[5];
+ for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
+
+ String currLine = null;
+ String[] splitFields = null;
+ int commaFields = 0, semicolonFields = 0, tabFields = 0, spaceFields = 0;
+ for (int lineNum=0; lineNum<fileContents.length && fileOK; lineNum++)
+ {
+ currLine = fileContents[lineNum];
+ // check for invalid characters
+ if (currLine.indexOf('\0') >= 0) {fileOK = false;}
+ // check for commas
+ splitFields = currLine.split(",");
+ commaFields = splitFields.length;
+ if (commaFields > 1) _delimiterInfos[0].incrementNumRecords();
+ _delimiterInfos[0].updateMaxFields(commaFields);
+ // check for tabs
+ splitFields = currLine.split("\t");
+ tabFields = splitFields.length;
+ if (tabFields > 1) _delimiterInfos[1].incrementNumRecords();
+ _delimiterInfos[1].updateMaxFields(tabFields);
+ // check for semicolons
+ splitFields = currLine.split(";");
+ semicolonFields = splitFields.length;
+ if (semicolonFields > 1) _delimiterInfos[2].incrementNumRecords();
+ _delimiterInfos[2].updateMaxFields(semicolonFields);
+ // check for spaces
+ splitFields = currLine.split(" ");
+ spaceFields = splitFields.length;
+ if (spaceFields > 1) _delimiterInfos[3].incrementNumRecords();
+ _delimiterInfos[3].updateMaxFields(spaceFields);
+ // increment counters
+ int bestScorer = getBestOption(commaFields, tabFields, semicolonFields, spaceFields);
+ if (bestScorer >= 0)
+ _delimiterInfos[bestScorer].incrementNumWinningRecords();
+ }
+ return fileOK;
+ }
+
+
+ /**
+ * Get the index of the best one in the list
+ * @return the index of the maximum of the four given values
+ */
+ private static int getBestOption(int inOpt0, int inOpt1, int inOpt2, int inOpt3)
+ {
+ int bestIndex = -1;
+ int maxScore = 1;
+ if (inOpt0 > maxScore) {bestIndex = 0; maxScore = inOpt0;}
+ if (inOpt1 > maxScore) {bestIndex = 1; maxScore = inOpt1;}
+ if (inOpt2 > maxScore) {bestIndex = 2; maxScore = inOpt2;}
+ if (inOpt3 > maxScore) {bestIndex = 3; maxScore = inOpt3;}
+ return bestIndex;
+ }
+
+
+ /**
+ * Make the components for the open options dialog
+ * @return Component for all options
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel wholePanel = new JPanel();
+ wholePanel.setLayout(new BorderLayout());
+
+ // add buttons to south
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ _backButton = new JButton(I18nManager.getText("button.back"));
+ _backButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _layout.previous(_cardPanel);
+ _backButton.setEnabled(false);
+ _nextButton.setEnabled(true);
+ _finishButton.setEnabled(false);
+ }
+ });
+ _backButton.setEnabled(false);
+ buttonPanel.add(_backButton);
+ _nextButton = new JButton(I18nManager.getText("button.next"));
+ _nextButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ prepareSecondPanel();
+ _layout.next(_cardPanel);
+ _nextButton.setEnabled(false);
+ _backButton.setEnabled(true);
+ _finishButton.setEnabled(_fieldTableModel.getRowCount() > 1);
+ }
+ });
+ buttonPanel.add(_nextButton);
+ _finishButton = new JButton(I18nManager.getText("button.finish"));
+ _finishButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ finished();
+ }
+ });
+ _finishButton.setEnabled(false);
+ buttonPanel.add(_finishButton);
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ wholePanel.add(buttonPanel, BorderLayout.SOUTH);
+
+ // Make the two cards, for delimiter and fields
+ _cardPanel = new JPanel();
+ _layout = new CardLayout();
+ _cardPanel.setLayout(_layout);
+ JPanel firstCard = new JPanel();
+ firstCard.setLayout(new BorderLayout());
+
+ JPanel delimsPanel = new JPanel();
+ delimsPanel.setLayout(new GridLayout(0, 2));
+ delimsPanel.add(new JLabel(I18nManager.getText("dialog.delimiter.label")));
+ delimsPanel.add(new JLabel("")); // blank label to go to next grid row
+ // radio buttons
+ _delimiterRadios = new JRadioButton[5];
+ _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
+ delimsPanel.add(_delimiterRadios[0]);
+ _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
+ delimsPanel.add(_delimiterRadios[1]);
+ _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
+ delimsPanel.add(_delimiterRadios[2]);
+ _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
+ delimsPanel.add(_delimiterRadios[3]);
+ JPanel otherPanel = new JPanel();
+ otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
+ otherPanel.add(_delimiterRadios[4]);
+ _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
+ otherPanel.add(_otherDelimiterText);
+ // Group radio buttons
+ ButtonGroup delimGroup = new ButtonGroup();
+ DelimListener delimListener = new DelimListener();
+ for (int i=0; i<_delimiterRadios.length; i++)
+ {
+ delimGroup.add(_delimiterRadios[i]);
+ _delimiterRadios[i].addActionListener(delimListener);
+ }
+ _otherDelimiterText.getDocument().addDocumentListener(delimListener);
+ delimsPanel.add(new JLabel(""));
+ delimsPanel.add(otherPanel);
+ _statusLabel = new JLabel("");
+ delimsPanel.add(_statusLabel);
+ firstCard.add(delimsPanel, BorderLayout.SOUTH);
+ // load snippet to show first few lines
+ _snippetBox = new JList(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
+ _snippetBox.setEnabled(false);
+ firstCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", _snippetBox), BorderLayout.CENTER);
+
+ // Second screen, for field order selection
+ JPanel secondCard = new JPanel();
+ secondCard.setLayout(new BorderLayout());
+ // table for file contents
+ _fileExtractTableModel = new FileExtractTableModel();
+ JTable extractTable = new JTable(_fileExtractTableModel);
+ JScrollPane tableScrollPane = new JScrollPane(extractTable);
+ extractTable.setPreferredScrollableViewportSize(new Dimension(350, 80));
+ extractTable.getTableHeader().setReorderingAllowed(false);
+ secondCard.add(makeLabelledPanel("dialog.openoptions.tabledesc", tableScrollPane), BorderLayout.NORTH);
+ JPanel innerPanel2 = new JPanel();
+ innerPanel2.setLayout(new BorderLayout());
+ _fieldTable = new JTable(new FieldSelectionTableModel());
+ _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ // add listener for selected table row
+ _fieldTable.getSelectionModel().addListSelectionListener(
+ new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ ListSelectionModel lsm = (ListSelectionModel) e.getSource();
+ if (lsm.isSelectionEmpty()) {
+ //no rows are selected
+ selectField(-1);
+ } else {
+ selectField(lsm.getMinSelectionIndex());
+ }
+ }
+ });
+ JPanel tablePanel = new JPanel();
+ tablePanel.setLayout(new BorderLayout());
+ tablePanel.add(_fieldTable.getTableHeader(), BorderLayout.NORTH);
+ tablePanel.add(_fieldTable, BorderLayout.CENTER);
+ innerPanel2.add(tablePanel, BorderLayout.CENTER);
+
+ JPanel innerPanel3 = new JPanel();
+ innerPanel3.setLayout(new BoxLayout(innerPanel3, BoxLayout.Y_AXIS));
+ _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
+ _moveUpButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ int currRow = _fieldTable.getSelectedRow();
+ closeTableComboBox(currRow);
+ _fieldTableModel.moveUp(currRow);
+ _fieldTable.setRowSelectionInterval(currRow-1, currRow-1);
+ }
+ });
+ innerPanel3.add(_moveUpButton);
+ _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
+ _moveDownButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ int currRow = _fieldTable.getSelectedRow();
+ closeTableComboBox(currRow);
+ _fieldTableModel.moveDown(currRow);
+ _fieldTable.setRowSelectionInterval(currRow+1, currRow+1);
+ }
+ });
+ innerPanel3.add(_moveDownButton);
+ innerPanel3.add(Box.createVerticalStrut(70));
+
+ innerPanel2.add(innerPanel3, BorderLayout.EAST);
+ secondCard.add(innerPanel2, BorderLayout.CENTER);
+ JPanel altUnitsPanel = new JPanel();
+ altUnitsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ altUnitsPanel.add(new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits")));
+ String[] units = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
+ _unitsDropDown = new JComboBox(units);
+ altUnitsPanel.add(_unitsDropDown);
+ secondCard.add(altUnitsPanel, BorderLayout.SOUTH);
+ _cardPanel.add(firstCard, "card1");
+ _cardPanel.add(secondCard, "card2");
+
+ wholePanel.add(_cardPanel, BorderLayout.CENTER);
+ return wholePanel;
+ }
+
+
+ /**
+ * Close the combo box on the selected row of the field table
+ * @param inRow currently selected row number
+ */
+ private void closeTableComboBox(int inRow)
+ {
+ TableCellEditor editor = _fieldTable.getCellEditor(inRow, 1);
+ if (editor != null)
+ {
+ editor.stopCellEditing();
+ }
+ }
+
+
+ /**
+ * change the status based on selection of a delimiter
+ */
+ protected void informDelimiterSelected()
+ {
+ int fields = 0;
+ // Loop through radios to see which one is selected
+ for (int i=0; i<(_delimiterRadios.length-1); i++)
+ {
+ if (_delimiterRadios[i].isSelected())
+ {
+ // Set label text to describe records and fields
+ int numRecords = _delimiterInfos[i].getNumRecords();
+ if (numRecords == 0)
+ {
+ _statusLabel.setText(I18nManager.getText("dialog.openoptions.deliminfo.norecords"));
+ }
+ else
+ {
+ fields = _delimiterInfos[i].getMaxFields();
+ _statusLabel.setText("" + numRecords + " " + I18nManager.getText("dialog.openoptions.deliminfo.records")
+ + fields + " " + I18nManager.getText("dialog.openoptions.deliminfo.fields"));
+ }
+ }
+ }
+ // Don't show label if "other" delimiter is chosen (as records, fields are unknown)
+ if (_delimiterRadios[_delimiterRadios.length-1].isSelected())
+ {
+ _statusLabel.setText("");
+ }
+ // enable/disable next button
+ _nextButton.setEnabled((_delimiterRadios[4].isSelected() == false && fields > 1)
+ || _otherDelimiterText.getText().length() == 1);
+ }
+
+
+ /**
+ * Get the delimiter info from the first step
+ * @return delimiter information object for the selected delimiter
+ */
+ public DelimiterInfo getSelectedDelimiterInfo()
+ {
+ for (int i=0; i<4; i++)
+ if (_delimiterRadios[i].isSelected()) return _delimiterInfos[i];
+ // must be "other" - build info if necessary
+ if (_delimiterInfos[4] == null)
+ _delimiterInfos[4] = new DelimiterInfo(_otherDelimiterText.getText().charAt(0));
+ return _delimiterInfos[4];
+ }
+
+
+ /**
+ * Use the delimiter selected to determine the fields in the file
+ * and prepare the second panel accordingly
+ */
+ private void prepareSecondPanel()
+ {
+ DelimiterInfo info = getSelectedDelimiterInfo();
+ FileSplitter splitter = new FileSplitter(_fileCacher);
+ // Check info makes sense - num fields > 0, num records > 0
+ // set "Finished" button to disabled if not ok
+ // TODO: Work out if there are header rows or not, save?
+ // Try to match header rows with fields
+ // Try to match data with fields
+ // Add data to GUI elements
+ Object[][] tableData = splitter.splitFieldData(info.getDelimiter());
+ // possible to ignore blank columns here
+ _currentDelimiter = info.getDelimiter();
+ _fileExtractTableModel.updateData(tableData);
+ _fieldTableModel = new FieldSelectionTableModel();
+
+ // Check number of fields and use last ones if count matches
+ Field[] startFieldArray = null;
+ if (_lastSelectedFields != null && splitter.getNumColumns() == _lastSelectedFields.length)
+ startFieldArray = _lastSelectedFields;
+ else
+ startFieldArray = splitter.makeDefaultFields();
+ _fieldTableModel.updateData(startFieldArray);
+ _fieldTable.setModel(_fieldTableModel);
+ // add dropdowns to second column
+ JComboBox fieldTypesBox = new JComboBox();
+ for (int i=0; i<Field.ALL_AVAILABLE_FIELDS.length; i++)
+ {
+ fieldTypesBox.addItem(Field.ALL_AVAILABLE_FIELDS[i].getName());
+ }
+ _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
+
+ // Set altitude format to same as last time if available
+ if (_lastAltitudeFormat == Altitude.FORMAT_METRES)
+ _unitsDropDown.setSelectedIndex(0);
+ else if (_lastAltitudeFormat == Altitude.FORMAT_FEET)
+ _unitsDropDown.setSelectedIndex(1);
+ // no selection on field list
+ selectField(-1);
+ }
+
+
+ /**
+ * All options have been selected, so load file
+ */
+ private void finished()
+ {
+ // Save delimiter, field array and altitude format for later use
+ _lastUsedDelimiter = _currentDelimiter;
+ _lastSelectedFields = _fieldTableModel.getFieldArray();
+ int altitudeFormat = Altitude.FORMAT_METRES;
+ if (_unitsDropDown.getSelectedIndex() == 1)
+ {
+ altitudeFormat = Altitude.FORMAT_FEET;
+ }
+ _lastAltitudeFormat = altitudeFormat;
+ // give data to App
+ _app.informDataLoaded(_fieldTableModel.getFieldArray(),
+ _fileExtractTableModel.getData(), altitudeFormat,
+ _file.getName());
+ // clear up file cacher
+ _fileCacher.clear();
+ // dispose of dialog
+ _dialog.dispose();
+ }
+
+
+ /**
+ * Make a panel with a label and a component
+ * @param inLabelKey label key to use
+ * @param inComponent component for main area of panel
+ * @return labelled Panel
+ */
+ private static JPanel makeLabelledPanel(String inLabelKey, JComponent inComponent)
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout());
+ panel.add(new JLabel(I18nManager.getText(inLabelKey)), BorderLayout.NORTH);
+ panel.add(inComponent, BorderLayout.CENTER);
+ return panel;
+ }
+
+
+ /**
+ * An entry in the field list has been selected
+ * @param inFieldNum index of field, starting with 0
+ */
+ private void selectField(int inFieldNum)
+ {
+ if (inFieldNum == -1 || inFieldNum != _selectedField)
+ {
+ _selectedField = inFieldNum;
+ _moveUpButton.setEnabled(inFieldNum > 0);
+ _moveDownButton.setEnabled(inFieldNum >= 0
+ && inFieldNum < (_fieldTableModel.getRowCount()-1));
+ }
+ }
+
+
+ /**
+ * @return the last delimiter character used for a load
+ */
+ public char getLastUsedDelimiter()
+ {
+ return _lastUsedDelimiter;
+ }
+}
--- /dev/null
+package tim.prune.load.xml;
+
+import java.util.ArrayList;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import tim.prune.data.Field;
+
+
+/**
+ * Class for handling specifics of parsing Gpx files
+ */
+public class GpxHandler extends XmlHandler
+{
+ private boolean _insideName = false;
+ private boolean _insideElevation = false;
+ private boolean _insideTime = false;
+ private String _name = null, _latitude = null, _longitude = null;
+ private String _elevation = null;
+ private String _time = null;
+ private ArrayList _pointList = new ArrayList();
+
+
+ /**
+ * Receive the start of a tag
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException
+ {
+ // Read the parameters for waypoints and track points
+ if (qName.equalsIgnoreCase("wpt") || qName.equalsIgnoreCase("trkpt"))
+ {
+ int numAttributes = attributes.getLength();
+ for (int i=0; i<numAttributes; i++)
+ {
+ String att = attributes.getQName(i);
+ if (att.equals("lat")) {_latitude = attributes.getValue(i);}
+ else if (att.equals("lon")) {_longitude = attributes.getValue(i);}
+ }
+ _elevation = null;
+ _name = null;
+ _time = null;
+ }
+ else if (qName.equalsIgnoreCase("ele"))
+ {
+ _insideElevation = true;
+ }
+ else if (qName.equalsIgnoreCase("name"))
+ {
+ _insideName = true;
+ }
+ else if (qName.equalsIgnoreCase("time"))
+ {
+ _insideTime = true;
+ }
+ super.startElement(uri, localName, qName, attributes);
+ }
+
+
+ /**
+ * Process end tag
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException
+ {
+ if (qName.equalsIgnoreCase("wpt") || qName.equalsIgnoreCase("trkpt"))
+ {
+ processPoint();
+ }
+ else if (qName.equalsIgnoreCase("ele"))
+ {
+ _insideElevation = false;
+ }
+ else if (qName.equalsIgnoreCase("name"))
+ {
+ _insideName = false;
+ }
+ else if (qName.equalsIgnoreCase("time"))
+ {
+ _insideTime = false;
+ }
+ super.endElement(uri, localName, qName);
+ }
+
+
+ /**
+ * Process character text (inside tags or between them)
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException
+ {
+ String value = new String(ch, start, length);
+ if (_insideName) {_name = checkCharacters(_name, value);}
+ else if (_insideElevation) {_elevation = checkCharacters(_elevation, value);}
+ else if (_insideTime) {_time = checkCharacters(_time, value);}
+ super.characters(ch, start, length);
+ }
+
+
+ /**
+ * Check to concatenate partially-received values, if necessary
+ * @param inVariable variable containing characters received until now
+ * @param inValue new value received
+ * @return concatenation
+ */
+ private static String checkCharacters(String inVariable, String inValue)
+ {
+ if (inVariable == null) {return inValue;}
+ return inVariable + inValue;
+ }
+
+
+ /**
+ * Process a point, either a waypoint or track point
+ */
+ private void processPoint()
+ {
+ // Put the values into a String array matching the order in getFieldArray()
+ String[] values = new String[5];
+ values[0] = _latitude; values[1] = _longitude;
+ values[2] = _elevation; values[3] = _name;
+ values[4] = _time;
+ _pointList.add(values);
+ }
+
+
+ /**
+ * @see tim.prune.load.xml.XmlHandler#getFieldArray()
+ */
+ public Field[] getFieldArray()
+ {
+ final Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE,
+ Field.WAYPT_NAME, Field.TIMESTAMP};
+ return fields;
+ }
+
+
+ /**
+ * Return the parsed information as a 2d array
+ * @see tim.prune.load.xml.XmlHandler#getDataArray()
+ */
+ public String[][] getDataArray()
+ {
+ int numPoints = _pointList.size();
+ // construct data array
+ String[][] result = new String[numPoints][];
+ for (int i=0; i<numPoints; i++)
+ {
+ result[i] = (String[]) _pointList.get(i);
+ }
+ return result;
+ }
+}
--- /dev/null
+package tim.prune.load.xml;
+
+import java.util.ArrayList;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import tim.prune.data.Field;
+
+
+/**
+ * Class for handling specifics of parsing Kml files
+ */
+public class KmlHandler extends XmlHandler
+{
+ private boolean _insidePlacemark = false;
+ private boolean _insideName = false;
+ private boolean _insideCoordinates = false;
+ private String _name = null;
+ private StringBuffer _coordinates = null;
+ private ArrayList _pointList = new ArrayList();
+
+
+ /**
+ * Receive the start of a tag
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException
+ {
+ String tagName = localName;
+ if (tagName == null || tagName.equals("")) {tagName = qName;}
+ if (tagName.equalsIgnoreCase("Placemark")) _insidePlacemark = true;
+ else if (tagName.equalsIgnoreCase("coordinates")) {_insideCoordinates = true; _coordinates = null;}
+ else if (tagName.equalsIgnoreCase("name")) {_insideName = true; _name = null;}
+ super.startElement(uri, localName, qName, attributes);
+ }
+
+
+ /**
+ * Process end tag
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException
+ {
+ String tagName = localName;
+ if (tagName == null || tagName.equals("")) {tagName = qName;}
+ if (tagName.equalsIgnoreCase("Placemark"))
+ {
+ processPlacemark();
+ _insidePlacemark = false;
+ }
+ else if (tagName.equalsIgnoreCase("coordinates")) _insideCoordinates = false;
+ else if (tagName.equalsIgnoreCase("name")) _insideName = false;
+ super.endElement(uri, localName, qName);
+ }
+
+
+ /**
+ * Process character text (inside tags or between them)
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException
+ {
+ if (_insidePlacemark && (_insideName || _insideCoordinates))
+ {
+ String value = new String(ch, start, length);
+ if (_insideName) {_name = value;}
+ else if (_insideCoordinates)
+ {
+ if (_coordinates == null)
+ {
+ _coordinates = new StringBuffer();
+ }
+ _coordinates.append(value);
+ }
+ }
+ super.characters(ch, start, length);
+ }
+
+
+ /**
+ * Process a placemark entry, either a single waypoint or a whole track
+ */
+ private void processPlacemark()
+ {
+ if (_coordinates == null) return;
+ String allCoords = _coordinates.toString();
+ String[] coordArray = allCoords.split("[ \n]");
+ int numPoints = coordArray.length;
+ if (numPoints == 1)
+ {
+ // Add single waypoint to list
+ _pointList.add(makeStringArray(allCoords, _name));
+ }
+ else if (numPoints > 1)
+ {
+ // Add each of the unnamed track points to list
+ for (int p=0; p<numPoints; p++)
+ {
+ _pointList.add(makeStringArray(coordArray[p], null));
+ }
+ }
+ }
+
+
+ /**
+ * Construct the String array for the given coordinates and name
+ * @param inCoordinates coordinate string in Kml format
+ * @param inName name of waypoint, or null if track point
+ * @return String array for point
+ */
+ private static String[] makeStringArray(String inCoordinates, String inName)
+ {
+ String[] result = new String[4];
+ String[] values = inCoordinates.split(",");
+ if (values.length == 3) {System.arraycopy(values, 0, result, 0, 3);}
+ result[3] = inName;
+ return result;
+ }
+
+
+ /**
+ * @see tim.prune.load.xml.XmlHandler#getFieldArray()
+ */
+ public Field[] getFieldArray()
+ {
+ final Field[] fields = {Field.LONGITUDE, Field.LATITUDE, Field.ALTITUDE, Field.WAYPT_NAME};
+ return fields;
+ }
+
+
+ /**
+ * Return the parsed information as a 2d array
+ * @see tim.prune.load.xml.XmlHandler#getDataArray()
+ */
+ public String[][] getDataArray()
+ {
+ int numPoints = _pointList.size();
+ // construct data array
+ String[][] result = new String[numPoints][];
+ for (int i=0; i<numPoints; i++)
+ {
+ result[i] = (String[]) _pointList.get(i);
+ }
+ return result;
+ }
+
+}
--- /dev/null
+package tim.prune.load.xml;
+
+import java.io.File;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.Altitude;
+
+/**
+ * Class for handling loading of Xml files, and passing the
+ * loaded data back to the App object
+ */
+public class XmlFileLoader extends DefaultHandler implements Runnable
+{
+ private File _file = null;
+ private App _app = null;
+ private JFrame _parentFrame = null;
+ private XmlHandler _handler = null;
+ private String _unknownType = null;
+
+
+ /**
+ * Constructor
+ * @param inApp Application object to inform of track load
+ * @param inParentFrame parent frame to reference for dialogs
+ */
+ public XmlFileLoader(App inApp, JFrame inParentFrame)
+ {
+ _app = inApp;
+ _parentFrame = inParentFrame;
+ }
+
+
+ /**
+ * Open the selected file and show the GUI dialog
+ * to select load options
+ */
+ public void openFile(File inFile)
+ {
+ _file = inFile;
+ _handler = null;
+ _unknownType = null;
+ // start new thread in case xml parsing is time-consuming
+ new Thread(this).start();
+ }
+
+
+ /**
+ * Run method, to parse the file
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ try
+ {
+ // Construct a SAXParser and use this as a default handler
+ SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+ saxParser.parse(_file, this);
+
+ // Check whether handler was properly instantiated
+ if (_handler == null)
+ {
+ // Wasn't either kml or gpx
+ JOptionPane.showMessageDialog(_parentFrame,
+ I18nManager.getText("error.load.unknownxml") + " " + _unknownType,
+ I18nManager.getText("error.load.dialogtitle"), JOptionPane.ERROR_MESSAGE);
+ }
+ else
+ {
+ // Pass information back to app
+ _app.informDataLoaded(_handler.getFieldArray(), _handler.getDataArray(),
+ Altitude.FORMAT_METRES, _file.getName());
+ }
+ }
+ catch (Exception e)
+ {
+ // Show error dialog
+ JOptionPane.showMessageDialog(_parentFrame,
+ I18nManager.getText("error.load.othererror") + " " + e.getMessage(),
+ I18nManager.getText("error.load.dialogtitle"), JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Receive a tag
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException
+ {
+ // Check for "kml" or "gpx" tags
+ if (_handler == null)
+ {
+ if (qName.equals("kml")) {_handler = new KmlHandler();}
+ else if (qName.equals("gpx")) {_handler = new GpxHandler();}
+ else if (_unknownType == null && qName != null && !qName.equals(""))
+ {
+ _unknownType = qName;
+ }
+ }
+ else
+ {
+ // Handler instantiated so pass tags on to it
+ _handler.startElement(uri, localName, qName, attributes);
+ }
+ super.startElement(uri, localName, qName, attributes);
+ }
+
+
+ /**
+ * Receive characters, either between or inside tags
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException
+ {
+ if (_handler != null)
+ {
+ // Handler instantiated so pass tags on to it
+ _handler.characters(ch, start, length);
+ }
+ super.characters(ch, start, length);
+ }
+
+
+ /**
+ * Receive end of element
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException
+ {
+ if (_handler != null)
+ {
+ // Handler instantiated so pass tags on to it
+ _handler.endElement(uri, localName, qName);
+ }
+ super.endElement(uri, localName, qName);
+ }
+}
--- /dev/null
+package tim.prune.load.xml;
+
+import org.xml.sax.helpers.DefaultHandler;
+
+import tim.prune.data.Field;
+
+/**
+ * Abstract superclass of xml handlers
+ */
+public abstract class XmlHandler extends DefaultHandler
+{
+ /**
+ * Method for returning data loaded from file
+ * @return 2d String array containing data
+ */
+ public abstract String[][] getDataArray();
+
+ /**
+ * @return field array describing fields of data
+ */
+ public abstract Field[] getFieldArray();
+}
-Prune version 2
+Prune version 3
===============
Prune is an application for viewing, editing and managing coordinate data from GPS systems.
=======
To run Prune from the jar file, simply call it from a command prompt or shell:
- java -jar prune_02.jar
+ java -jar prune_03.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
can of course be made should you wish.
+Updates since version 2
+=======================
+
+The following features were added since version 2:
+ - Loading of GPX and KML files
+ - Loading of jpeg photos with or without coordinate data
+ - Manual correlation of photos with points
+ - Saving of coordinates in exif data of jpegs
+ - Exporting to KMZ format including thumbnails of photos
+ - Four-panel layout with toolbar
+
Updates since version 1
=======================
--- /dev/null
+package tim.prune.save;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import tim.prune.ExternalTools;
+import tim.prune.I18nManager;
+import tim.prune.data.Altitude;
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Photo;
+import tim.prune.data.PhotoList;
+import tim.prune.data.PhotoStatus;
+
+/**
+ * Class to call Exiftool to save coordinate information in jpg files
+ */
+public class ExifSaver implements Runnable
+{
+ private Frame _parentFrame = null;
+ private JDialog _dialog = null;
+ private JCheckBox _overwriteCheckbox = null;
+ private JProgressBar _progressBar = null;
+ private PhotoTableModel _photoTableModel = null;
+
+
+ // To preserve timestamps of file use parameter -P
+ // To overwrite file (careful!) use parameter -overwrite_original_in_place
+
+ // To read all GPS tags, use -GPS:All
+ // To delete all GPS tags, use -GPS:All=
+
+ // To set Altitude, use -GPSAltitude= and -GPSAltitudeRef=
+ // To set Latitude, use -GPSLatitude= and -GPSLatitudeRef=
+
+ // To delete all tags with overwrite: exiftool -P -overwrite_original_in_place -GPS:All= <filename>
+
+ // To set altitude with overwrite: exiftool -P -overwrite_original_in_place -GPSAltitude=1234 -GPSAltitudeRef='Above Sea Level' <filename>
+ // (setting altitude ref to 0 doesn't work)
+ // To set latitude with overwrite: exiftool -P -overwrite_original_in_place -GPSLatitude='12 34 56.78' -GPSLatitudeRef=N <filename>
+ // (latitude as space-separated deg min sec, reference as either N or S)
+ // Same for longitude, reference E or W
+
+
+ /**
+ * Constructor
+ * @param inParentFrame parent frame
+ */
+ public ExifSaver(Frame inParentFrame)
+ {
+ _parentFrame = inParentFrame;
+ }
+
+
+ /**
+ * Save exif information to all photos in the list
+ * whose coordinate information has changed since loading
+ * @param inPhotoList list of photos to save
+ */
+ public boolean saveExifInformation(PhotoList inPhotoList)
+ {
+ // Check if external exif tool can be called
+ boolean exifToolInstalled = ExternalTools.isExiftoolInstalled();
+ if (!exifToolInstalled)
+ {
+ // show warning
+ int answer = JOptionPane.showConfirmDialog(_dialog, I18nManager.getText("dialog.saveexif.noexiftool"),
+ I18nManager.getText("dialog.saveexif.title"),
+ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (answer == JOptionPane.NO_OPTION || answer == JOptionPane.CLOSED_OPTION)
+ {
+ return false;
+ }
+ }
+ // Make model and add all photos to it
+ _photoTableModel = new PhotoTableModel(inPhotoList.getNumPhotos());
+ for (int i=0; i<inPhotoList.getNumPhotos(); i++)
+ {
+ Photo photo = inPhotoList.getPhoto(i);
+ PhotoTableEntry entry = new PhotoTableEntry(photo);
+ _photoTableModel.addPhotoInfo(entry);
+ }
+ // Check if there are any modified photos to save
+ if (_photoTableModel.getNumSaveablePhotos() < 1)
+ {
+ JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.saveexif.nothingtosave"),
+ I18nManager.getText("dialog.saveexif.title"), JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+ // Construct dialog
+ _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveexif.title"), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ // set progress bar and show dialog
+ _progressBar.setVisible(false);
+ _dialog.show();
+ return true;
+ }
+
+
+ /**
+ * Put together the dialog components for adding to the gui
+ * @return panel containing all gui components
+ */
+ private JPanel makeDialogComponents()
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout());
+ panel.add(new JLabel(I18nManager.getText("dialog.saveexif.intro")), BorderLayout.NORTH);
+ // centre panel with most controls
+ JPanel centrePanel = new JPanel();
+ centrePanel.setLayout(new BorderLayout());
+ // table panel with table and checkbox
+ JPanel tablePanel = new JPanel();
+ tablePanel.setLayout(new BorderLayout());
+ JTable photoTable = new JTable(_photoTableModel);
+ JScrollPane scrollPane = new JScrollPane(photoTable);
+ scrollPane.setPreferredSize(new Dimension(300, 160));
+ tablePanel.add(scrollPane, BorderLayout.CENTER);
+ _overwriteCheckbox = new JCheckBox(I18nManager.getText("dialog.saveexif.overwrite"));
+ _overwriteCheckbox.setSelected(false);
+ tablePanel.add(_overwriteCheckbox, BorderLayout.SOUTH);
+ centrePanel.add(tablePanel, BorderLayout.CENTER);
+ // progress bar below main controls
+ _progressBar = new JProgressBar(0, 100);
+ centrePanel.add(_progressBar, BorderLayout.SOUTH);
+ panel.add(centrePanel, BorderLayout.CENTER);
+ // Right-hand panel with select all, none buttons
+ JPanel rightPanel = new JPanel();
+ rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
+ JButton selectAllButton = new JButton(I18nManager.getText("button.selectall"));
+ selectAllButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ selectPhotos(true);
+ }
+ });
+ rightPanel.add(selectAllButton);
+ JButton selectNoneButton = new JButton(I18nManager.getText("button.selectnone"));
+ selectNoneButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ selectPhotos(false);
+ }
+ });
+ rightPanel.add(selectNoneButton);
+ panel.add(rightPanel, BorderLayout.EAST);
+ // Lower panel with ok and cancel buttons
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton okButton = new JButton(I18nManager.getText("button.ok"));
+ okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ // start new thread to do save
+ new Thread(ExifSaver.this).start();
+ }
+ });
+ 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);
+ panel.add(buttonPanel, BorderLayout.SOUTH);
+ return panel;
+ }
+
+
+ /**
+ * Select all or select none
+ * @param inSelected true to select all photos, false to deselect all
+ */
+ private void selectPhotos(boolean inSelected)
+ {
+ int numPhotos = _photoTableModel.getRowCount();
+ for (int i=0; i<numPhotos; i++)
+ {
+ _photoTableModel.getPhotoTableEntry(i).setSaveFlag(inSelected);
+ }
+ _photoTableModel.fireTableDataChanged();
+ }
+
+
+ /**
+ * Run method for saving in separate thread
+ */
+ public void run()
+ {
+ PhotoTableEntry entry = null;
+ Photo photo = null;
+ int numPhotos = _photoTableModel.getRowCount();
+ _progressBar.setMaximum(numPhotos);
+ _progressBar.setValue(0);
+ _progressBar.setVisible(true);
+ boolean overwriteFlag = _overwriteCheckbox.isSelected();
+ int numSaved = 0;
+ // Loop over all photos in list
+ for (int i=0; i<numPhotos; i++)
+ {
+ entry = _photoTableModel.getPhotoTableEntry(i);
+ if (entry != null && entry.getSaveFlag())
+ {
+ // Only look at photos which are selected and whose status has changed since load
+ photo = entry.getPhoto();
+ if (photo != null && photo.getOriginalStatus() != photo.getCurrentStatus())
+ {
+ // Increment counter if save successful
+ if (savePhoto(photo, overwriteFlag))
+ {
+ numSaved++;
+ }
+ }
+ }
+ // update progress bar
+ _progressBar.setValue(i + 1);
+ }
+ _progressBar.setVisible(false);
+ // Show confirmation dialog
+ JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.saveexif.ok1") + " "
+ + numSaved + " " + I18nManager.getText("dialog.saveexif.ok2"),
+ I18nManager.getText("dialog.saveexif.title"), JOptionPane.INFORMATION_MESSAGE);
+ // close dialog, all finished
+ _dialog.dispose();
+ }
+
+
+ /**
+ * Save the details for the given photo
+ * @param inPhoto Photo object
+ * @param inOverwriteFlag true to overwrite file, false otherwise
+ * @return true if details saved ok
+ */
+ private boolean savePhoto(Photo inPhoto, boolean inOverwriteFlag)
+ {
+ // Check whether photo file still exists
+ if (!inPhoto.getFile().exists())
+ {
+ // photo file doesn't exist any more
+ JOptionPane.showMessageDialog(_parentFrame,
+ I18nManager.getText("error.saveexif.filenotfound") + " : " + inPhoto.getFile().getAbsolutePath(),
+ I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ // Warn if file read-only and selected to overwrite
+ if (inOverwriteFlag && !inPhoto.getFile().canWrite())
+ {
+ // eek, can't overwrite file
+ int answer = JOptionPane.showConfirmDialog(_parentFrame,
+ I18nManager.getText("error.saveexif.cannotoverwrite1") + " " + inPhoto.getFile().getAbsolutePath()
+ + " " + I18nManager.getText("error.saveexif.cannotoverwrite2"),
+ I18nManager.getText("dialog.saveexif.title"),
+ JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);
+ if (answer == JOptionPane.YES_OPTION)
+ {
+ // don't overwrite this image but write to copy
+ inOverwriteFlag = false;
+ }
+ else
+ {
+ // don't do anything with this file
+ return false;
+ }
+ }
+ String[] command = null;
+ if (inPhoto.getCurrentStatus() == PhotoStatus.NOT_CONNECTED)
+ {
+ // Photo is no longer connected, so delete gps tags
+ command = getDeleteGpsExifTagsCommand(inPhoto.getFile(), inOverwriteFlag);
+ }
+ else
+ {
+ // Photo is now connected, so write new gps tags
+ command = getWriteGpsExifTagsCommand(inPhoto.getFile(), inPhoto.getDataPoint(), inOverwriteFlag);
+ }
+ // Execute exif command
+ try
+ {
+ Runtime.getRuntime().exec(command);
+ }
+ catch (Exception e)
+ {
+ // show error message
+ JOptionPane.showMessageDialog(_parentFrame, "Exception: '" + e.getClass().getName() + "' : "
+ + e.getMessage(), I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create the command to delete the gps exif tags from the specified file
+ * @param inFile file from which to delete tags
+ * @param inOverwrite true to overwrite file, false to create copy
+ * @return external command to delete gps tags
+ */
+ private static String[] getDeleteGpsExifTagsCommand(File inFile, boolean inOverwrite)
+ {
+ // Make a string array to construct the command and its parameters
+ String[] result = new String[inOverwrite?5:4];
+ result[0] = "exiftool";
+ result[1] = "-P";
+ if (inOverwrite) {result[2] = " -overwrite_original_in_place";}
+ // remove all gps tags
+ int paramOffset = inOverwrite?3:2;
+ result[paramOffset] = "-GPS:All=";
+ result[paramOffset + 1] = inFile.getAbsolutePath();
+ return result;
+ }
+
+
+ /**
+ * Create the comand to write the gps exif tags to the specified file
+ * @param inFile file to which to write the tags
+ * @param inPoint DataPoint object containing coordinate information
+ * @param inOverwrite true to overwrite file, false to create copy
+ * @return external command to write gps tags
+ */
+ private static String[] getWriteGpsExifTagsCommand(File inFile, DataPoint inPoint, boolean inOverwrite)
+ {
+ // Make a string array to construct the command and its parameters
+ String[] result = new String[inOverwrite?10:9];
+ result[0] = "exiftool";
+ result[1] = "-P";
+ if (inOverwrite) {result[2] = "-overwrite_original_in_place";}
+ int paramOffset = inOverwrite?3:2;
+ // To set latitude : -GPSLatitude='12 34 56.78' -GPSLatitudeRef='N'
+ // (latitude as space-separated deg min sec, reference as either N or S)
+ result[paramOffset] = "-GPSLatitude='" + inPoint.getLatitude().output(Coordinate.FORMAT_DEG_MIN_SEC_WITH_SPACES)
+ + "'";
+ result[paramOffset + 1] = "-GPSLatitudeRef=" + inPoint.getLatitude().output(Coordinate.FORMAT_CARDINAL);
+ // same for longitude with space-separated deg min sec, reference as either E or W
+ result[paramOffset + 2] = "-GPSLongitude='" + inPoint.getLongitude().output(Coordinate.FORMAT_DEG_MIN_SEC_WITH_SPACES)
+ + "'";
+ result[paramOffset + 3] = "-GPSLongitudeRef=" + inPoint.getLongitude().output(Coordinate.FORMAT_CARDINAL);
+ // add altitude if it has it
+ result[paramOffset + 4] = "-GPSAltitude="
+ + (inPoint.hasAltitude()?inPoint.getAltitude().getValue(Altitude.FORMAT_METRES):0);
+ result[paramOffset + 5] = "-GPSAltitudeRef='Above Sea Level'";
+ // add the filename to modify
+ result[paramOffset + 6] = inFile.getAbsolutePath();
+ return result;
+ }
+}
package tim.prune.save;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.io.Writer;
+import java.util.Iterator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
+import javax.swing.JLabel;
import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
import javax.swing.filechooser.FileFilter;
-import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.ImageUtils;
/**
* Class to export track information
* into a specified Kml file
*/
-public class KmlExporter
+public class KmlExporter implements Runnable
{
- private App _app = null;
private JFrame _parentFrame = null;
+ private TrackInfo _trackInfo = null;
private Track _track = null;
+ private JDialog _dialog = null;
+ private JTextField _descriptionField = null;
+ private JCheckBox _kmzCheckbox = null;
+ private JCheckBox _exportImagesCheckbox = null;
+ private JProgressBar _progressBar = null;
private JFileChooser _fileChooser = null;
+ private File _exportFile = null;
+
+ // Filename of Kml file within zip archive
+ private static final String KML_FILENAME_IN_KMZ = "doc.kml";
+ // Width and height of thumbnail images in Kmz
+ private static final int THUMBNAIL_WIDTH = 240;
+ private static final int THUMBNAIL_HEIGHT = 180;
/**
- * Constructor giving App object, frame and track
- * @param inApp application object to inform of success
+ * Constructor giving frame and track
* @param inParentFrame parent frame
- * @param inTrack track object to save
+ * @param inTrackInfo track info object to save
*/
- public KmlExporter(App inApp, JFrame inParentFrame, Track inTrack)
+ public KmlExporter(JFrame inParentFrame, TrackInfo inTrackInfo)
{
- _app = inApp;
_parentFrame = inParentFrame;
- _track = inTrack;
+ _trackInfo = inTrackInfo;
+ _track = inTrackInfo.getTrack();
}
/**
* Show the dialog to select options and export file
*/
- public boolean showDialog()
+ public void showDialog()
{
- boolean fileSaved = false;
- Object description = JOptionPane.showInputDialog(_parentFrame,
- I18nManager.getText("dialog.exportkml.text"),
- I18nManager.getText("dialog.exportkml.title"),
- JOptionPane.QUESTION_MESSAGE, null, null, "");
- // TODO: Make dialog window including colour selection, line width, track description
- if (description != null)
+ // Make dialog window including whether to compress to kmz (and include pictures) or not
+ if (_dialog == null)
{
- // OK pressed, so choose output file
- if (_fileChooser == null)
- _fileChooser = new JFileChooser();
- _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
- _fileChooser.setFileFilter(new FileFilter() {
- public boolean accept(File f)
- {
- return (f != null && (f.isDirectory() || f.getName().toLowerCase().endsWith(".kml")));
- }
- public String getDescription()
- {
- return I18nManager.getText("dialog.exportkml.filetype");
- }
- });
- _fileChooser.setAcceptAllFileFilterUsed(false);
- // Allow choose again if an existing file is selected
- boolean chooseAgain = false;
- do
- {
- chooseAgain = false;
- if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
- {
- // OK pressed and file chosen
- File file = _fileChooser.getSelectedFile();
- if (!file.getName().toLowerCase().endsWith(".kml"))
- {
- file = new File(file.getAbsolutePath() + ".kml");
- }
- // Check if file exists and if necessary prompt for overwrite
- Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
- if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
- I18nManager.getText("dialog.save.overwrite.text"),
- I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
- == JOptionPane.YES_OPTION)
- {
- if (exportFile(file, description.toString()))
- {
- fileSaved = true;
- }
- else
- {
- chooseAgain = true;
- }
- }
- else
- {
- chooseAgain = true;
- }
- }
- } while (chooseAgain);
+ _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.exportkml.title"), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
}
- return fileSaved;
+ enableCheckboxes();
+ _progressBar.setVisible(false);
+ _dialog.show();
}
/**
- * Export the track data to the specified file with description
- * @param inFile File object to save to
- * @param inDescription description to use, if any
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
*/
- private boolean exportFile(File inFile, String inDescription)
+ private Component makeDialogComponents()
{
- FileWriter writer = null;
- try
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ // Make a central panel with the text box and checkboxes
+ JPanel descPanel = new JPanel();
+ descPanel.setLayout(new FlowLayout());
+ descPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.text")));
+ _descriptionField = new JTextField(20);
+ descPanel.add(_descriptionField);
+ mainPanel.add(descPanel);
+ dialogPanel.add(mainPanel, BorderLayout.CENTER);
+ // Checkboxes for kmz export and image export
+ _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
+ _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
+ _kmzCheckbox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ // enable image checkbox if kmz activated
+ enableCheckboxes();
+ }
+ });
+ mainPanel.add(_kmzCheckbox);
+ _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
+ _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
+ mainPanel.add(_exportImagesCheckbox);
+ mainPanel.add(Box.createVerticalStrut(10));
+ _progressBar = new JProgressBar(0, 100);
+ _progressBar.setVisible(false);
+ mainPanel.add(_progressBar);
+ mainPanel.add(Box.createVerticalStrut(10));
+ // 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)
+ {
+ startExport();
+ }
+ };
+ okButton.addActionListener(okListener);
+ _descriptionField.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);
+ return dialogPanel;
+ }
+
+
+ /**
+ * Enable the checkboxes according to data
+ */
+ private void enableCheckboxes()
+ {
+ boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
+ _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
+ _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
+ }
+
+
+ /**
+ * Start the export process based on the input parameters
+ */
+ private void startExport()
+ {
+ // OK pressed, so choose output file
+ if (_fileChooser == null)
+ {_fileChooser = new JFileChooser();}
+ _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+ _fileChooser.setFileFilter(new FileFilter() {
+ public boolean accept(File f)
+ {
+ return (f != null && (f.isDirectory()
+ || f.getName().toLowerCase().endsWith(".kml") || f.getName().toLowerCase().endsWith(".kmz")));
+ }
+ public String getDescription()
+ {
+ return I18nManager.getText("dialog.exportkml.filetype");
+ }
+ });
+ String requiredExtension = null, otherExtension = null;
+ if (_kmzCheckbox.isSelected())
{
- writer = new FileWriter(inFile);
- writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
- writer.write("\t<name>");
- writer.write(inDescription);
- writer.write("</name>\n");
-
- int i = 0;
- DataPoint point = null;
- boolean hasTrackpoints = false;
- // Loop over waypoints
- boolean writtenPhotoHeader = false;
- int numPoints = _track.getNumPoints();
- for (i=0; i<numPoints; i++)
+ requiredExtension = ".kmz"; otherExtension = ".kml";
+ }
+ else
+ {
+ requiredExtension = ".kml"; otherExtension = ".kmz";
+ }
+ _fileChooser.setAcceptAllFileFilterUsed(false);
+ // Allow choose again if an existing file is selected
+ boolean chooseAgain = false;
+ do
+ {
+ chooseAgain = false;
+ if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
{
- point = _track.getPoint(i);
- if (point.isWaypoint())
+ // OK pressed and file chosen
+ File file = _fileChooser.getSelectedFile();
+ if (file.getName().toLowerCase().endsWith(otherExtension))
+ {
+ String path = file.getAbsolutePath();
+ file = new File(path.substring(0, path.length()-otherExtension.length()) + requiredExtension);
+ }
+ else if (!file.getName().toLowerCase().endsWith(requiredExtension))
{
- exportWaypoint(point, writer);
+ file = new File(file.getAbsolutePath() + requiredExtension);
}
- else if (point.getPhoto() != null)
+ // Check if file exists and if necessary prompt for overwrite
+ Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
+ if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
+ I18nManager.getText("dialog.save.overwrite.text"),
+ I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+ == JOptionPane.YES_OPTION)
{
- if (!writtenPhotoHeader)
- {
- writer.write("<Style id=\"camera_icon\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon></IconStyle></Style>");
- writtenPhotoHeader = true;
- }
- exportPhotoPoint(point, writer);
+ // New file or overwrite confirmed, so initiate export in separate thread
+ _exportFile = file;
+ new Thread(this).start();
}
else
{
- hasTrackpoints = true;
+ chooseAgain = true;
}
}
- if (hasTrackpoints)
+ } while (chooseAgain);
+ }
+
+
+ /**
+ * Run method for controlling separate thread for exporting
+ */
+ public void run()
+ {
+ // Initialise progress bar
+ _progressBar.setVisible(true);
+ _progressBar.setValue(0);
+ boolean exportToKmz = _kmzCheckbox.isSelected();
+ boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
+ _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
+ OutputStreamWriter writer = null;
+ ZipOutputStream zipOutputStream = null;
+ try
+ {
+ // Select writer according to whether kmz requested or not
+ if (!_kmzCheckbox.isSelected())
+ {
+ // normal writing to file
+ writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
+ }
+ else
{
- writer.write("\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
- + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
- + "\t\t</Style>\n\t\t<LineString>\n\t\t\t<coordinates>");
- // Loop over track points
- for (i=0; i<numPoints; i++)
+ // kmz requested - need zip output stream
+ zipOutputStream = new ZipOutputStream(new FileOutputStream(_exportFile));
+ writer = new OutputStreamWriter(zipOutputStream);
+ // Make an entry in the zip file for the kml file
+ ZipEntry kmlEntry = new ZipEntry(KML_FILENAME_IN_KMZ);
+ zipOutputStream.putNextEntry(kmlEntry);
+ }
+ // write file
+ int numPoints = exportData(writer, exportImages);
+ // update progress bar
+ _progressBar.setValue(1);
+
+ // close zip entry if necessary
+ if (zipOutputStream != null)
+ {
+ // Make sure all buffered data in writer is flushed
+ writer.flush();
+ // Close off this entry in the zip file
+ zipOutputStream.closeEntry();
+ // Export images into zip file too if requested
+ if (exportImages)
{
- point = _track.getPoint(i);
- if (!point.isWaypoint())
- {
- exportTrackpoint(point, writer);
- }
+ // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
+ exportThumbnails(zipOutputStream);
}
- writer.write("\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>");
}
- writer.write("</Folder>\n</kml>");
+
+ // close file
writer.close();
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1")
+ " " + numPoints + " " + I18nManager.getText("dialog.save.ok2")
- + " " + inFile.getAbsolutePath(),
+ + " " + _exportFile.getAbsolutePath(),
I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE);
- return true;
+ // export successful so need to close dialog and return
+ _dialog.dispose();
+ return;
}
catch (IOException ioe)
{
+ // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
try {
if (writer != null) writer.close();
}
catch (IOException ioe2) {}
- JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(),
+ JOptionPane.showMessageDialog(_parentFrame,
+ I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
- return false;
+ // if not returned already, export failed so need to recall the file selection
+ startExport();
+ }
+
+
+ /**
+ * Export the information to the given writer
+ * @param inWriter writer object
+ * @param inExportImages true if image thumbnails are to be referenced
+ * @return number of points written
+ */
+ private int exportData(OutputStreamWriter inWriter, boolean inExportImages)
+ throws IOException
+ {
+ // TODO: Look at segments of track, and split into separate lines in Kml if necessary
+ inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
+ inWriter.write("\t<name>");
+ if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
+ {
+ inWriter.write(_descriptionField.getText());
+ }
+ else
+ {
+ inWriter.write("Export from Prune");
+ }
+ inWriter.write("</name>\n");
+
+ int i = 0;
+ DataPoint point = null;
+ boolean hasTrackpoints = false;
+ // Loop over waypoints
+ boolean writtenPhotoHeader = false;
+ int numPoints = _track.getNumPoints();
+ int photoNum = 0;
+ for (i=0; i<numPoints; i++)
+ {
+ point = _track.getPoint(i);
+ // Make a blob for each waypoint
+ if (point.isWaypoint())
+ {
+ exportWaypoint(point, inWriter);
+ }
+ // Make a blob with description for each photo
+ if (point.getPhoto() != null)
+ {
+ if (!writtenPhotoHeader)
+ {
+ inWriter.write("<Style id=\"camera_icon\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon></IconStyle></Style>");
+ writtenPhotoHeader = true;
+ }
+ photoNum++;
+ exportPhotoPoint(point, inWriter, inExportImages, photoNum);
+ }
+ else
+ {
+ hasTrackpoints = true;
+ }
+ }
+ // Make a line for the track, if there is one
+ if (hasTrackpoints)
+ {
+ inWriter.write("\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
+ + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+ + "\t\t</Style>\n\t\t<LineString>\n\t\t\t<coordinates>");
+ // Loop over track points
+ for (i=0; i<numPoints; i++)
+ {
+ point = _track.getPoint(i);
+ if (!point.isWaypoint())
+ {
+ exportTrackpoint(point, inWriter);
+ }
+ }
+ inWriter.write("\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>");
+ }
+ inWriter.write("</Folder>\n</kml>");
+ return numPoints;
}
* Export the specified photo into the file
* @param inPoint data point including photo
* @param inWriter writer object
+ * @param inImageLink flag to set whether to export image links or not
+ * @param inImageNumber number of image for filename
* @throws IOException on write failure
*/
- private void exportPhotoPoint(DataPoint inPoint, Writer inWriter) throws IOException
+ private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, int inImageNumber)
+ throws IOException
{
- // TODO: Export photos to KML too - for photos need kmz!
inWriter.write("\t<Placemark>\n\t\t<name>");
inWriter.write(inPoint.getPhoto().getFile().getName());
inWriter.write("</name>\n");
+ if (inImageLink)
+ {
+ // Work out image dimensions of thumbnail
+ Dimension picSize = inPoint.getPhoto().getSize();
+ Dimension thumbSize = ImageUtils.getThumbnailSize(picSize.width, picSize.height, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
+ // Write out some html for the thumbnail images
+ inWriter.write("<description><![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
+ + inImageNumber + ".jpg' width='" + thumbSize.width + "' height='" + thumbSize.height + "'></center></td></tr>"
+ + "<tr><td><center>Caption for the photo</center></td></tr></table>]]></description>");
+ }
inWriter.write("<styleUrl>#camera_icon</styleUrl>\n");
inWriter.write("\t\t<Point>\n\t\t\t<coordinates>");
inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL));
// Altitude not exported, locked to ground by Google Earth
inWriter.write(",0\n");
}
+
+
+ /**
+ * Loop through the photos and create thumbnails
+ * @param inZipStream zip stream to save image files to
+ */
+ private void exportThumbnails(ZipOutputStream inZipStream) throws IOException
+ {
+ // set up image writer
+ Iterator writers = ImageIO.getImageWritersByFormatName("jpg");
+ if (writers == null || !writers.hasNext())
+ {
+ throw new IOException("no JPEG writer found");
+ }
+ ImageWriter imageWriter = (ImageWriter) writers.next();
+
+ int numPoints = _track.getNumPoints();
+ DataPoint point = null;
+ int photoNum = 0;
+ // Loop over all points in track
+ for (int i=0; i<numPoints; i++)
+ {
+ point = _track.getPoint(i);
+ if (point.getPhoto() != null)
+ {
+ photoNum++;
+ // Make a new entry in zip file
+ ZipEntry entry = new ZipEntry("images/image" + photoNum + ".jpg");
+ inZipStream.putNextEntry(entry);
+ // Load image and write to outstream
+ ImageIcon icon = new ImageIcon(point.getPhoto().getFile().getAbsolutePath());
+
+ // Scale and smooth image to required size
+ Dimension outputSize = ImageUtils.getThumbnailSize(
+ point.getPhoto().getWidth(), point.getPhoto().getHeight(),
+ THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
+ BufferedImage bufferedImage = ImageUtils.createScaledImage(icon.getImage(), outputSize.width, outputSize.height);
+
+ imageWriter.setOutput(ImageIO.createImageOutputStream(inZipStream));
+ imageWriter.write(bufferedImage);
+ // Close zip file entry
+ inZipStream.closeEntry();
+ // Update progress bar
+ _progressBar.setValue(photoNum+1);
+ }
+ }
+ }
+
+
+ /**
+ * @return number of correlated photos in the track
+ */
+ private int getNumPhotosToExport()
+ {
+ int numPoints = _track.getNumPoints();
+ int numPhotos = 0;
+ DataPoint point = null;
+ // Loop over all points in track
+ for (int i=0; i<numPoints; i++)
+ {
+ point = _track.getPoint(i);
+ if (point.getPhoto() != null)
+ {
+ numPhotos++;
+ }
+ }
+ return numPhotos;
+ }
}
--- /dev/null
+package tim.prune.save;
+
+import tim.prune.I18nManager;
+import tim.prune.data.Photo;
+import tim.prune.data.PhotoStatus;
+
+/**
+ * Class to represent a row of the photo table for saving exif
+ */
+public class PhotoTableEntry
+{
+ private Photo _photo = null;
+ private String _photoName = null;
+ private boolean _save = true;
+ private String _status = null;
+
+ /**
+ * Constructor
+ * @param inPhoto photo object
+ */
+ public PhotoTableEntry(Photo inPhoto)
+ {
+ _photo = inPhoto;
+ if (inPhoto != null)
+ {
+ _photoName = inPhoto.getFile().getName();
+ _status = getStatusString(inPhoto.getOriginalStatus(), inPhoto.getCurrentStatus());
+ }
+ }
+
+
+ /**
+ * Make a status string from the given status bytes
+ * @param inOriginalStatus original status of photo
+ * @param inCurrentStatus current status of photo
+ * @return status string for display
+ */
+ private static String getStatusString (byte inOriginalStatus, byte inCurrentStatus)
+ {
+ if (inOriginalStatus != inCurrentStatus)
+ {
+ if (inOriginalStatus == PhotoStatus.NOT_CONNECTED)
+ {
+ // originally didn't have a point, now it has
+ return I18nManager.getText("dialog.saveexif.photostatus.connected");
+ }
+ if (inCurrentStatus == PhotoStatus.NOT_CONNECTED)
+ {
+ // originally had a point, now it doesn't
+ return I18nManager.getText("dialog.saveexif.photostatus.disconnected");
+ }
+ // originally had a point, now it has a different one
+ return I18nManager.getText("dialog.saveexif.photostatus.modified");
+ }
+ // unrecognised status
+ return null;
+ }
+
+ /**
+ * @return Photo object
+ */
+ public Photo getPhoto()
+ {
+ return _photo;
+ }
+
+ /**
+ * @return photo filename
+ */
+ public String getName()
+ {
+ return _photoName;
+ }
+
+ /**
+ * @return photo status as string
+ */
+ public String getStatus()
+ {
+ return _status;
+ }
+
+ /**
+ * @param inFlag true to save exif, false otherwise
+ */
+ public void setSaveFlag(boolean inFlag)
+ {
+ _save = inFlag;
+ }
+
+ /**
+ * @return true to save exif, false otherwise
+ */
+ public boolean getSaveFlag()
+ {
+ return _save;
+ }
+}
--- /dev/null
+package tim.prune.save;
+
+import javax.swing.table.AbstractTableModel;
+
+import tim.prune.I18nManager;
+
+/**
+ * Class to hold table model information for save exif dialog
+ */
+public class PhotoTableModel extends AbstractTableModel
+{
+ private PhotoTableEntry[] _photos = null;
+ private int _addIndex = 0;
+
+
+ /**
+ * Constructor giving list size
+ */
+ public PhotoTableModel(int inSize)
+ {
+ _photos = new PhotoTableEntry[inSize];
+ }
+
+
+ /**
+ * Set the given PhotoTableEntry object in the array
+ * @param inEntry PhotoTableEntry object describing the photo
+ */
+ public void addPhotoInfo(PhotoTableEntry inEntry)
+ {
+ if (_addIndex < _photos.length && inEntry != null
+ && inEntry.getStatus() != null)
+ {
+ _photos[_addIndex] = inEntry;
+ _addIndex++;
+ }
+ }
+
+ /**
+ * @return the number of photos in the list whose status has changed
+ */
+ public int getNumSaveablePhotos()
+ {
+ return _addIndex;
+ }
+
+ /**
+ * @see javax.swing.table.TableModel#getColumnCount()
+ */
+ public int getColumnCount()
+ {
+ return 3;
+ }
+
+
+ /**
+ * @see javax.swing.table.TableModel#getRowCount()
+ */
+ public int getRowCount()
+ {
+ return _addIndex;
+ }
+
+
+ /**
+ * @see javax.swing.table.TableModel#getValueAt(int, int)
+ */
+ public Object getValueAt(int inRowIndex, int inColumnIndex)
+ {
+ if (inColumnIndex == 0)
+ {
+ return _photos[inRowIndex].getName();
+ }
+ else if (inColumnIndex == 1)
+ {
+ return _photos[inRowIndex].getStatus();
+ }
+ return new Boolean(_photos[inRowIndex].getSaveFlag());
+ }
+
+
+ /**
+ * @return true if cell is editable
+ */
+ public boolean isCellEditable(int inRowIndex, int inColumnIndex)
+ {
+ // only the save column is editable
+ return inColumnIndex == 2;
+ }
+
+
+ /**
+ * Set the given cell value
+ * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
+ */
+ public void setValueAt(Object inValue, int inRowIndex, int inColumnIndex)
+ {
+ // ignore edits to other columns
+ if (inColumnIndex == 2)
+ _photos[inRowIndex].setSaveFlag(((Boolean) inValue).booleanValue());
+ }
+
+
+ /**
+ * @return Class of cell data
+ */
+ public Class getColumnClass(int inColumnIndex)
+ {
+ if (inColumnIndex < 2) return String.class;
+ return Boolean.class;
+ }
+
+
+ /**
+ * Get the name of the column
+ */
+ public String getColumnName(int inColNum)
+ {
+ if (inColNum == 0) return I18nManager.getText("dialog.saveexif.table.photoname");
+ else if (inColNum == 1) return I18nManager.getText("dialog.saveexif.table.status");
+ return I18nManager.getText("dialog.saveexif.table.save");
+ }
+
+
+ /**
+ * Retrieve the object at the given index
+ * @param inIndex index, starting at 0
+ * @return PhotoTableEntry object at this position
+ */
+ public PhotoTableEntry getPhotoTableEntry(int inIndex)
+ {
+ if (inIndex < 0 || inIndex >= _photos.length)
+ {
+ return null;
+ }
+ return _photos[inIndex];
+ }
+}
import javax.swing.SwingConstants;
import javax.swing.filechooser.FileFilter;
-import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.data.Track;
+import tim.prune.threedee.LineDialog;
import tim.prune.threedee.ThreeDModel;
/**
*/
public class PovExporter
{
- private App _app = null;
private JFrame _parentFrame = null;
private Track _track = null;
private JDialog _dialog = null;
/**
- * Constructor giving App object, frame and track
- * @param inApp application object to inform of success
+ * Constructor giving frame and track
* @param inParentFrame parent frame
* @param inTrack track object to save
*/
- public PovExporter(App inApp, JFrame inParentFrame, Track inTrack)
+ public PovExporter(JFrame inParentFrame, Track inTrack)
{
- _app = inApp;
_parentFrame = inParentFrame;
_track = inTrack;
// Set default camera coordinates
JPanel flowPanel = new JPanel();
flowPanel.add(centralPanel);
+
+ // show lines button
+ JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
+ showLinesButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ // Need to scale model to find lines
+ ThreeDModel model = new ThreeDModel(_track);
+ model.scale();
+ double[] latLines = model.getLatitudeLines();
+ double[] lonLines = model.getLongitudeLines();
+ LineDialog dialog = new LineDialog(_parentFrame, latLines, lonLines);
+ dialog.showDialog();
+ }
+ });
+ flowPanel.add(showLinesButton);
panel.add(flowPanel, BorderLayout.CENTER);
return panel;
}
_cameraZ = checkCoordinate(_cameraZField.getText());
// OK pressed, so choose output file
- boolean fileSaved = false;
if (_fileChooser == null)
_fileChooser = new JFileChooser();
_fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
// Export the file
if (exportFile(file))
{
- fileSaved = true;
+ // file saved
}
else
{
{
inWriter.write("// Latitude and longitude lines:");
inWriter.write(inLineSeparator);
- int numlines = inModel.getNumLatitudeLines();
+ int numlines = inModel.getLatitudeLines().length;
for (int i=0; i<numlines; i++)
{
// write cylinder to file
inWriter.write("object { lat_line translate <0, 0, " + inModel.getScaledLatitudeLine(i) + "> }");
inWriter.write(inLineSeparator);
}
- numlines = inModel.getNumLongitudeLines();
+ numlines = inModel.getLongitudeLines().length;
for (int i=0; i<numlines; i++)
{
// write cylinder to file
package tim.prune.threedee;
-import java.awt.BorderLayout;
import java.awt.FlowLayout;
+import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
private Track _track = null;
private JFrame _parentFrame = null;
private JFrame _frame = null;
+ private ThreeDModel _model = null;
private OrbitBehavior _orbit = null;
private int _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP;
// Constants
private static final double INITIAL_Y_ROTATION = -25.0;
private static final double INITIAL_X_ROTATION = 15.0;
- private static final int INITIAL_ALTITUDE_CAP = 500;
private static final String CARDINALS_FONT = "Arial";
private static final int MAX_TRACK_SIZE = 2500; // threshold for warning
}
}});
panel.add(renderButton);
+ // Display coordinates of lat/long lines of 3d graph in separate dialog
+ JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
+ showLinesButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ double[] latLines = _model.getLatitudeLines();
+ double[] lonLines = _model.getLongitudeLines();
+ LineDialog dialog = new LineDialog(_frame, latLines, lonLines);
+ dialog.showDialog();
+ }
+ });
+ panel.add(showLinesButton);
+ // Close button
JButton closeButton = new JButton(I18nManager.getText("button.close"));
closeButton.addActionListener(new ActionListener()
{
// Base plane
Appearance planeAppearance = null;
Box plane = null;
- Transform3D planeShift = null;
- TransformGroup planeTrans = null;
planeAppearance = new Appearance();
planeAppearance.setMaterial(new Material(new Color3f(0.1f, 0.2f, 0.2f),
new Color3f(0.0f, 0.0f, 0.0f), new Color3f(0.3f, 0.4f, 0.4f),
objTrans.addChild(createCompassPoint(I18nManager.getText("cardinal.e"), new Point3f(10f, 0f, 0f), compassFont));
// create and scale model
- ThreeDModel model = new ThreeDModel(_track);
- model.setAltitudeCap(_altitudeCap);
- model.scale();
+ _model = new ThreeDModel(_track);
+ _model.setAltitudeCap(_altitudeCap);
+ _model.scale();
// Lat/Long lines
- objTrans.addChild(createLatLongs(model));
+ objTrans.addChild(createLatLongs(_model));
// Add points to model
- objTrans.addChild(createDataPoints(model));
+ objTrans.addChild(createDataPoints(_model));
// Create lights
BoundingSphere bounds =
private static Group createLatLongs(ThreeDModel inModel)
{
Group group = new Group();
- int numlines = inModel.getNumLatitudeLines();
+ int numlines = inModel.getLatitudeLines().length;
for (int i=0; i<numlines; i++)
{
group.addChild(createLatLine(inModel.getScaledLatitudeLine(i), inModel.getModelSize()));
}
- numlines = inModel.getNumLongitudeLines();
+ numlines = inModel.getLongitudeLines().length;
for (int i=0; i<numlines; i++)
{
group.addChild(createLonLine(inModel.getScaledLongitudeLine(i), inModel.getModelSize()));
--- /dev/null
+package tim.prune.threedee;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JEditorPane;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
+
+/**
+ * Class to show a dialog displaying the line coordinates
+ * for a 3d view (either java3d or povray)
+ */
+public class LineDialog
+{
+ private JDialog _dialog = null;
+ private JFrame _parent = null;
+ private double[] _latLines = null;
+ private double[] _lonLines = null;
+
+
+ /**
+ * Constructor giving parent frame, latitude and longitude lines
+ * @param inParent parent frame for dialog
+ * @param inLatLines latitude lines as doubles
+ * @param inLonLines longitude lines as doubles
+ */
+ public LineDialog(JFrame inParent, double[] inLatLines, double[] inLonLines)
+ {
+ _parent = inParent;
+ _latLines = inLatLines;
+ _lonLines = inLonLines;
+ }
+
+
+ /**
+ * Show the dialog with the lines
+ */
+ public void showDialog()
+ {
+ _dialog = new JDialog(_parent, I18nManager.getText("dialog.3dlines.title"), true);
+ _dialog.setLocationRelativeTo(_parent);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ _dialog.show();
+ }
+
+
+ /**
+ * @return dialog components
+ */
+ private JPanel makeDialogComponents()
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout());
+ StringBuffer descBuffer = new StringBuffer();
+ if (_latLines == null || _latLines.length == 0 || _lonLines == null || _lonLines.length == 0)
+ {
+ descBuffer.append("<p>").append(I18nManager.getText("dialog.3dlines.empty")).append("</p>");
+ }
+ else
+ {
+ descBuffer.append("<p>").append(I18nManager.getText("dialog.3dlines.intro")).append(":</p>");
+ descBuffer.append("<p>").append(I18nManager.getText("fieldname.latitude")).append("<ul>");
+ Latitude lat = null;
+ for (int i=0; i<_latLines.length; i++)
+ {
+ lat = new Latitude(_latLines[i], Latitude.FORMAT_DEG);
+ descBuffer.append("<li>").append(lat.output(Latitude.FORMAT_DEG_WHOLE_MIN)).append("</li>");
+ }
+ descBuffer.append("</ul></p>");
+ descBuffer.append("<p>").append(I18nManager.getText("fieldname.longitude")).append("<ul>");
+ Longitude lon = null;
+ for (int i=0; i<_lonLines.length; i++)
+ {
+ lon = new Longitude(_lonLines[i], Longitude.FORMAT_DEG);
+ descBuffer.append("<li>").append(lon.output(Longitude.FORMAT_DEG_WHOLE_MIN)).append("</li>");
+ }
+ descBuffer.append("</ul></p>");
+ }
+ JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
+ descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+ descPane.setEditable(false);
+ descPane.setOpaque(false);
+ panel.add(descPane, BorderLayout.CENTER);
+ // ok button
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton okButton = new JButton(I18nManager.getText("button.ok"));
+ okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _dialog.dispose();
+ _dialog = null;
+ }
+ });
+ buttonPanel.add(okButton);
+ panel.add(buttonPanel, BorderLayout.SOUTH);
+ return panel;
+ }
+}
/**
- * @return number of latitude lines
+ * @return latitude lines
*/
- public int getNumLatitudeLines()
+ public double[] getLatitudeLines()
{
- return _scaler.getLatitudeLines().length;
+ return _scaler.getLatitudeLines();
}
/**
}
/**
- * @return number of longitude lines
+ * @return longitude lines
*/
- public int getNumLongitudeLines()
+ public double[] getLongitudeLines()
{
- return _scaler.getLongitudeLines().length;
+ return _scaler.getLongitudeLines();
}
/**
/**
* @return true if 3d capability is installed
*/
- private static boolean isJava3dEnabled()
+ public static boolean isJava3dEnabled()
{
boolean has3d = false;
try
{
Class universeClass = Class.forName("com.sun.j3d.utils.universe.SimpleUniverse");
- has3d = true;
+ has3d = (universeClass != null);
}
catch (ClassNotFoundException e)
{
--- /dev/null
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.Photo;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo the connection of a photo to a point\r
+ */\r
+public class UndoConnectPhoto implements UndoOperation\r
+{\r
+ private DataPoint _point = null;\r
+ private String _filename = null;\r
+\r
+\r
+ /**\r
+ * Constructor\r
+ * @param inPoint data point\r
+ * @param inFilename filename of photo\r
+ */\r
+ public UndoConnectPhoto(DataPoint inPoint, String inFilename)\r
+ {\r
+ _point = inPoint;\r
+ _filename = inFilename;\r
+ }\r
+\r
+\r
+ /**\r
+ * @return description of operation including photo filename\r
+ */\r
+ public String getDescription()\r
+ {\r
+ String desc = I18nManager.getText("undo.connectphoto") + " " + _filename;\r
+ return desc;\r
+ }\r
+\r
+\r
+ /**\r
+ * Perform the undo operation on the given Track\r
+ * @param inTrackInfo TrackInfo object on which to perform the operation\r
+ */\r
+ public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+ {\r
+ // Disconnect again\r
+ Photo photo = _point.getPhoto();\r
+ if (photo != null)\r
+ {\r
+ _point.setPhoto(null);\r
+ photo.setDataPoint(null);\r
+ }\r
+ else\r
+ {\r
+ // throw exception if failed\r
+ throw new UndoException(getDescription());\r
+ }\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.Photo;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo a delete of a single photo, either with or without point\r
+ */\r
+public class UndoDeletePhoto implements UndoOperation\r
+{\r
+ private int _photoIndex = -1;\r
+ private Photo _photo = null;\r
+ private int _pointIndex = -1;\r
+ private DataPoint _point = null;\r
+\r
+\r
+ /**\r
+ * Constructor\r
+ * @param inPhoto photo\r
+ * @param inPhotoIndex index number of photo within photo list\r
+ * @param inPoint data point\r
+ * @param inPointIndex index number of point within track\r
+ */\r
+ public UndoDeletePhoto(Photo inPhoto, int inPhotoIndex, DataPoint inPoint, int inPointIndex)\r
+ {\r
+ _photo = inPhoto;\r
+ _photoIndex = inPhotoIndex;\r
+ _point = inPoint;\r
+ _pointIndex = inPointIndex;\r
+ }\r
+\r
+\r
+ /**\r
+ * @return description of operation including photo name\r
+ */\r
+ public String getDescription()\r
+ {\r
+ String desc = I18nManager.getText("undo.deletephoto") + " " + _photo.getFile().getName();\r
+ return desc;\r
+ }\r
+\r
+\r
+ /**\r
+ * Perform the undo operation on the given Track\r
+ * @param inTrackInfo TrackInfo object on which to perform the operation\r
+ */\r
+ public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+ {\r
+ // restore photo\r
+ inTrackInfo.getPhotoList().addPhoto(_photo, _photoIndex);\r
+ // if there's a point to restore, restore it\r
+ if (_point != null)\r
+ {\r
+ if (!inTrackInfo.getTrack().insertPoint(_point, _pointIndex))\r
+ {\r
+ throw new UndoException(getDescription());\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // update needed if not already triggered by track update\r
+ inTrackInfo.triggerUpdate();\r
+ }\r
+ // Ensure that photo is associated with point and vice versa\r
+ _photo.setDataPoint(_point);\r
+ if (_point != null)\r
+ {\r
+ _point.setPhoto(_photo);\r
+ }\r
+ }\r
+}\r
{\r
private int _pointIndex = -1;\r
private DataPoint _point = null;\r
+ private int _photoIndex = -1;\r
\r
\r
/**\r
* Constructor\r
- * @param inIndex index number of point within track\r
+ * @param inPointIndex index number of point within track\r
* @param inPoint data point\r
+ * @param inPhotoIndex index number of photo within photo list\r
*/\r
- public UndoDeletePoint(int inIndex, DataPoint inPoint)\r
+ public UndoDeletePoint(int inPointIndex, DataPoint inPoint, int inPhotoIndex)\r
{\r
- _pointIndex = inIndex;\r
+ _pointIndex = inPointIndex;\r
_point = inPoint;\r
+ _photoIndex = inPhotoIndex;\r
}\r
\r
\r
\r
/**\r
* Perform the undo operation on the given Track\r
- * @param inTrack Track object on which to perform the operation\r
+ * @param inTrackInfo TrackInfo object on which to perform the operation\r
*/\r
public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
{\r
{\r
throw new UndoException(getDescription());\r
}\r
- // TODO: Reinsert photo into list if necessary\r
+ // Re-attach / Re-insert photo into list if necessary\r
+ if (_point.getPhoto() != null && _photoIndex > -1)\r
+ {\r
+ // Check if photo is still in list\r
+ if (!inTrackInfo.getPhotoList().contains(_point.getPhoto()))\r
+ {\r
+ // photo has been removed - need to reinsert\r
+ inTrackInfo.getPhotoList().addPhoto(_point.getPhoto(), _photoIndex);\r
+ }\r
+ // Ensure that photo is associated with point\r
+ _point.getPhoto().setDataPoint(_point);\r
+ }\r
}\r
-}
\ No newline at end of file
+}\r
\r
import tim.prune.I18nManager;\r
import tim.prune.data.DataPoint;\r
+import tim.prune.data.PhotoList;\r
import tim.prune.data.TrackInfo;\r
\r
/**\r
{\r
private int _startIndex = -1;\r
private DataPoint[] _points = null;\r
+ private PhotoList _photoList = null;\r
\r
\r
/**\r
{\r
_startIndex = inTrackInfo.getSelection().getStart();\r
_points = inTrackInfo.cloneSelectedRange();\r
+ _photoList = inTrackInfo.getPhotoList().cloneList();\r
}\r
\r
\r
*/\r
public void performUndo(TrackInfo inTrackInfo)\r
{\r
+ // restore photos to how they were before\r
+ inTrackInfo.getPhotoList().restore(_photoList);\r
+ // reconnect photos to points\r
+ for (int i=0; i<_points.length; i++)\r
+ {\r
+ DataPoint point = _points[i];\r
+ if (point != null && point.getPhoto() != null)\r
+ {\r
+ point.getPhoto().setDataPoint(point);\r
+ }\r
+ }\r
// restore point array into track\r
inTrackInfo.getTrack().insertRange(_points, _startIndex);\r
}\r
\r
/**\r
* Perform the undo operation on the given Track\r
- * @param inTrack Track object on which to perform the operation\r
+ * @param inTrackInfo TrackInfo object on which to perform the operation\r
*/\r
public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
{\r
// throw exception if failed\r
throw new UndoException(getDescription());\r
}\r
- // TODO: Deal with photo if necessary\r
}\r
}
\ No newline at end of file
\r
import tim.prune.I18nManager;\r
import tim.prune.data.DataPoint;\r
+import tim.prune.data.PhotoList;\r
import tim.prune.data.TrackInfo;\r
\r
/**\r
private int _numLoaded = -1;\r
private DataPoint[] _contents = null;\r
private String _previousFilename = null;\r
+ private PhotoList _photoList = null;\r
\r
\r
/**\r
* Constructor for replacing\r
* @param inOldTrack track being replaced\r
* @param inNumLoaded number of points loaded\r
+ * @param inPhotoList photo list, if any\r
*/\r
- public UndoLoad(TrackInfo inOldTrackInfo, int inNumLoaded)\r
+ public UndoLoad(TrackInfo inOldTrackInfo, int inNumLoaded, PhotoList inPhotoList)\r
{\r
_cropIndex = -1;\r
_numLoaded = inNumLoaded;\r
_contents = inOldTrackInfo.getTrack().cloneContents();\r
if (inOldTrackInfo.getFileInfo().getNumFiles() == 1)\r
_previousFilename = inOldTrackInfo.getFileInfo().getFilename();\r
+ _photoList = inPhotoList;\r
}\r
\r
\r
}\r
else\r
{\r
+ // replace photos how they were\r
+ if (_photoList != null)\r
+ {\r
+ inTrackInfo.getPhotoList().restore(_photoList);\r
+ }\r
// replace track contents with old\r
if (!inTrackInfo.getTrack().replaceContents(_contents))\r
{\r
*/\r
public class UndoLoadPhotos implements UndoOperation\r
{\r
- private int _numLoaded = -1;\r
+ private int _numPhotos = -1;\r
+ private int _numPoints = -1;\r
\r
- // TODO: Handle possibility of photos not having datapoints (yet)\r
\r
/**\r
* Constructor\r
- * @param inNumLoaded number of photos loaded\r
+ * @param inNumPhotos number of photos loaded\r
+ * @param inNumPoints number of points loaded\r
*/\r
- public UndoLoadPhotos(int inNumLoaded)\r
+ public UndoLoadPhotos(int inNumPhotos, int inNumPoints)\r
{\r
- _numLoaded = inNumLoaded;\r
+ _numPhotos = inNumPhotos;\r
+ _numPoints = inNumPoints;\r
}\r
\r
\r
public String getDescription()\r
{\r
String desc = I18nManager.getText("undo.loadphotos");\r
- if (_numLoaded > 0)\r
- desc = desc + " (" + _numLoaded + ")";\r
+ if (_numPhotos > 0)\r
+ desc = desc + " (" + _numPhotos + ")";\r
return desc;\r
}\r
\r
*/\r
public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
{\r
+ int cropIndex;\r
// crop track to previous size\r
- int cropIndex = inTrackInfo.getTrack().getNumPoints() - _numLoaded;\r
- inTrackInfo.getTrack().cropTo(cropIndex);\r
+ if (_numPoints > 0)\r
+ {\r
+ cropIndex = inTrackInfo.getTrack().getNumPoints() - _numPoints;\r
+ inTrackInfo.getTrack().cropTo(cropIndex);\r
+ }\r
// crop photo list to previous size\r
- // (currently it is assumed that the number of points is the same as number of photos)\r
- cropIndex = inTrackInfo.getPhotoList().getNumPhotos() - _numLoaded;\r
+ cropIndex = inTrackInfo.getPhotoList().getNumPhotos() - _numPhotos;\r
inTrackInfo.getPhotoList().cropTo(cropIndex);\r
// clear selection\r
inTrackInfo.getSelection().clearAll();\r