package tim.prune;
+import java.io.File;
+import java.util.ArrayList;
import java.util.EmptyStackException;
-import java.util.List;
+import java.util.Set;
import java.util.Stack;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
+import tim.prune.config.Config;
+import tim.prune.data.Altitude;
+import tim.prune.data.Checker;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
+import tim.prune.data.LatLonRectangle;
+import tim.prune.data.NumberUtils;
import tim.prune.data.Photo;
import tim.prune.data.PhotoList;
+import tim.prune.data.RecentFile;
+import tim.prune.data.SourceInfo;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
-import tim.prune.edit.FieldEditList;
-import tim.prune.edit.PointEditor;
-import tim.prune.edit.PointNameEditor;
+import tim.prune.data.SourceInfo.FILE_TYPE;
+import tim.prune.function.AsyncMediaLoader;
+import tim.prune.function.SaveConfig;
+import tim.prune.function.SelectTracksFunction;
+import tim.prune.function.browser.BrowserLauncher;
+import tim.prune.function.browser.UrlGenerator;
+import tim.prune.function.edit.FieldEditList;
+import tim.prune.function.edit.PointEditor;
import tim.prune.gui.MenuManager;
+import tim.prune.gui.SidebarController;
import tim.prune.gui.UndoManager;
+import tim.prune.gui.Viewport;
import tim.prune.load.FileLoader;
import tim.prune.load.JpegLoader;
-import tim.prune.load.PhotoMeasurer;
+import tim.prune.load.MediaLinkInfo;
+import tim.prune.load.TrackNameList;
import tim.prune.save.ExifSaver;
import tim.prune.save.FileSaver;
-import tim.prune.save.KmlExporter;
-import tim.prune.save.PovExporter;
-import tim.prune.threedee.ThreeDException;
-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;
-import tim.prune.undo.UndoException;
-import tim.prune.undo.UndoInsert;
-import tim.prune.undo.UndoLoad;
-import tim.prune.undo.UndoLoadPhotos;
-import tim.prune.undo.UndoOperation;
-import tim.prune.undo.UndoRearrangeWaypoints;
-import tim.prune.undo.UndoReverseSection;
+import tim.prune.undo.*;
/**
private TrackInfo _trackInfo = null;
private int _lastSavePosition = 0;
private MenuManager _menuManager = null;
+ private SidebarController _sidebarController = 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;
- private boolean _reversePointsConfirmed = false;
-
- // Constants
- public static final int REARRANGE_TO_START = 0;
- public static final int REARRANGE_TO_END = 1;
- public static final int REARRANGE_TO_NEAREST = 2;
+ private FileSaver _fileSaver = null;
+ private Stack<UndoOperation> _undoStack = null;
+ private boolean _mangleTimestampsConfirmed = false;
+ private Viewport _viewport = null;
+ private ArrayList<File> _dataFiles = null;
+ private boolean _firstDataFile = true;
+ private boolean _busyLoading = false;
/**
* Constructor
* @param inFrame frame object for application
- * @param inBroker message broker
*/
- public App(JFrame inFrame, UpdateMessageBroker inBroker)
+ public App(JFrame inFrame)
{
_frame = inFrame;
- _undoStack = new Stack();
- _broker = inBroker;
- _track = new Track(_broker);
- _trackInfo = new TrackInfo(_track, _broker);
+ _undoStack = new Stack<UndoOperation>();
+ _track = new Track();
+ _trackInfo = new TrackInfo(_track);
+ FunctionLibrary.initialise(this);
}
return _trackInfo;
}
+ /**
+ * @return the dialog frame
+ */
+ public JFrame getFrame()
+ {
+ return _frame;
+ }
+
/**
* Check if the application has unsaved data
* @return true if data is unsaved, false otherwise
/**
* @return the undo stack
*/
- public Stack getUndoStack()
+ public Stack<UndoOperation> getUndoStack()
{
return _undoStack;
}
+ /**
+ * Load the specified data files one by one
+ * @param inDataFiles arraylist containing File objects to load
+ */
+ public void loadDataFiles(ArrayList<File> inDataFiles)
+ {
+ if (inDataFiles == null || inDataFiles.size() == 0) {
+ _dataFiles = null;
+ }
+ else {
+ _dataFiles = inDataFiles;
+ File f = _dataFiles.get(0);
+ _dataFiles.remove(0);
+ // Start load of specified file
+ if (_fileLoader == null)
+ _fileLoader = new FileLoader(this, _frame);
+ _firstDataFile = true;
+ _fileLoader.openFile(f);
+ }
+ }
+
+ /**
+ * Complete a function execution
+ * @param inUndo undo object to be added to stack
+ * @param inConfirmText confirmation text
+ */
+ public void completeFunction(UndoOperation inUndo, String inConfirmText)
+ {
+ _undoStack.add(inUndo);
+ UpdateMessageBroker.informSubscribers(inConfirmText);
+ }
+
/**
* Set the MenuManager object to be informed about changes
* @param inManager MenuManager object
/**
- * Add a photo or a directory of photos which are already correlated
+ * Add a photo or a directory of photos
*/
public void addPhotos()
{
if (_jpegLoader == null)
_jpegLoader = new JpegLoader(this, _frame);
- _jpegLoader.openFile();
+ _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
}
-
/**
* Save the file in the selected format
*/
public void saveFile()
{
- if (_track == null)
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
- I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
+ if (_track == null) {
+ showErrorMessage("error.save.dialogtitle", "error.save.nodata");
}
else
{
- FileSaver saver = new FileSaver(this, _frame, _track);
- saver.showDialog(_fileLoader.getLastUsedDelimiter());
- }
- }
-
-
- /**
- * Export track data as Kml
- */
- public void exportKml()
- {
- if (_track == null)
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
- I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
- }
- else
- {
- // Invoke the export
- if (_exporter == null)
- {
- _exporter = new KmlExporter(_frame, _trackInfo);
+ if (_fileSaver == null) {
+ _fileSaver = new FileSaver(this, _frame);
}
- _exporter.showDialog();
- }
- }
-
-
- /**
- * Export track data as Pov without specifying settings
- */
- public void exportPov()
- {
- exportPov(false, 0.0, 0.0, 0.0, 0);
- }
-
- /**
- * Export track data as Pov and also specify settings
- * @param inX X component of unit vector
- * @param inY Y component of unit vector
- * @param inZ Z component of unit vector
- * @param inAltitudeCap altitude cap
- */
- public void exportPov(double inX, double inY, double inZ, int inAltitudeCap)
- {
- exportPov(true, inX, inY, inZ, inAltitudeCap);
- }
-
- /**
- * Export track data as Pov with optional angle specification
- * @param inDefineAngles true to define angles, false to ignore
- * @param inX X component of unit vector
- * @param inY Y component of unit vector
- * @param inZ Z component of unit vector
- */
- private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap)
- {
- // Check track has data to export
- if (_track == null || _track.getNumPoints() <= 0)
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
- I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
- }
- else
- {
- // Make new exporter if necessary
- if (_povExporter == null)
- {
- _povExporter = new PovExporter(_frame, _track);
- }
- // Specify angles if necessary
- if (inDefineSettings)
- {
- _povExporter.setCameraCoordinates(inX, inY, inZ);
- _povExporter.setAltitudeCap(inAltitudeCap);
- }
- // Initiate export
- _povExporter.showDialog();
+ char delim = ',';
+ if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
+ _fileSaver.showDialog(delim);
}
}
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.YES_OPTION)
{
+ // save settings
+ if (Config.getConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS)) {
+ new SaveConfig(this).silentSave();
+ }
System.exit(0);
}
}
/**
* Complete the point edit
- * @param inEditList list of edits
+ * @param inEditList field values to edit
+ * @param inUndoList field values before edit
*/
public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
{
// add information to undo stack
UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
// pass to track for completion
- if (_track.editPoint(currentPoint, inEditList))
+ if (_track.editPoint(currentPoint, inEditList, false))
{
_undoStack.push(undo);
- }
- }
- }
-
-
- /**
- * Edit the name of the currently selected (way)point
- */
- public void editCurrentPointName()
- {
- if (_track != null)
- {
- DataPoint currentPoint = _trackInfo.getCurrentPoint();
- if (currentPoint != null)
- {
- // Open point dialog to display details
- PointNameEditor editor = new PointNameEditor(this, _frame);
- editor.showDialog(currentPoint);
+ // Confirm point edit
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
}
}
}
*/
public void deleteCurrentPoint()
{
- if (_track != null)
+ if (_track == null) {return;}
+ DataPoint currentPoint = _trackInfo.getCurrentPoint();
+ if (currentPoint != null)
{
- 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.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;}
+ }
+ // store necessary information to undo it later
+ int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
+ int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
+ DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
+ // Construct Undo object
+ UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
+ nextTrackPoint != null && nextTrackPoint.getSegmentStart());
+ // call track to delete point
+ if (_trackInfo.deletePoint())
{
- boolean deletePhoto = false;
- Photo currentPhoto = currentPoint.getPhoto();
+ // Delete was successful so add undo info to stack
+ _undoStack.push(undo);
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)
+ // delete photo if necessary
+ if (deletePhoto)
{
- // cancel pressed- abort delete
- return;
+ _trackInfo.getPhotoList().deletePhoto(photoIndex);
}
- if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
- }
- // add information to undo stack
- int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
- 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)
+ else
{
- // delete photo if necessary
- if (deletePhoto)
- {
- _trackInfo.getPhotoList().deletePhoto(photoIndex);
- }
- else
- {
- // decouple photo from point
- currentPhoto.setDataPoint(null);
- }
+ // decouple photo from point
+ currentPhoto.setDataPoint(null);
}
+ UpdateMessageBroker.informSubscribers();
}
+ // Confirm
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
}
}
}
*/
public void deleteSelectedRange()
{
- if (_track != null)
+ if (_track == null) return;
+ // Find out if photos should be deleted or not
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ if (selStart >= 0 && selEnd >= selStart)
{
- // Find out if photos should be deleted or not
- int selStart = _trackInfo.getSelection().getStart();
- int selEnd = _trackInfo.getSelection().getEnd();
- if (selStart >= 0 && selEnd >= selStart)
+ 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++)
{
- 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)
{
- point = _trackInfo.getTrack().getPoint(i + selStart);
- if (point != null && point.getPhoto() != null)
+ if (deleteAll)
{
- 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;}
- }
+ deletePhotos[i] = true;
+ photosToDelete[i] = point.getPhoto();
}
- }
- // 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)
+ else if (deleteNone) {deletePhotos[i] = false;}
+ else
{
- if (deletePhotos[i])
+ int response = JOptionPane.showOptionDialog(_frame,
+ I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().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)
{
- // delete photo from list
- _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
- }
- else
- {
- // decouple from point
- point.getPhoto().setDataPoint(null);
+ deletePhotos[i] = true;
+ photosToDelete[i] = point.getPhoto();
+ if (response == 2) {deleteAll = true;}
}
+ // check for no to all
+ if (response == 3) {deleteNone = true;}
}
}
- // call track to delete range
- if (_trackInfo.deleteRange())
- {
- _undoStack.push(undo);
- }
}
- }
- }
-
-
- /**
- * Delete all the duplicate points in the track
- */
- public void deleteDuplicates()
- {
- if (_track != null)
- {
- // Save undo information
- UndoOperation undo = new UndoDeleteDuplicates(_track);
- // tell track to do it
- int numDeleted = _trackInfo.deleteDuplicates();
- if (numDeleted > 0)
+ // add information to undo stack
+ UndoDeleteRange undo = new UndoDeleteRange(_trackInfo);
+ // delete requested photos
+ for (int i=0; i<numToDelete; i++)
{
- _undoStack.add(undo);
- String message = null;
- if (numDeleted == 1)
- {
- message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text");
- }
- else
+ point = _trackInfo.getTrack().getPoint(i + selStart);
+ if (point != null && point.getPhoto() != null)
{
- message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text");
+ if (deletePhotos[i])
+ {
+ // delete photo from list
+ _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
+ }
+ else
+ {
+ // decouple from point
+ point.getPhoto().setDataPoint(null);
+ }
}
- JOptionPane.showMessageDialog(_frame, message,
- I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
}
- else
+ // call track to delete range
+ if (_trackInfo.deleteRange())
{
- JOptionPane.showMessageDialog(_frame,
- I18nManager.getText("dialog.deleteduplicates.nonefound"),
- I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
+ _undoStack.push(undo);
+ // Confirm
+ UpdateMessageBroker.informSubscribers("" + numToDelete + " "
+ + I18nManager.getText("confirm.deletepoint.multi"));
}
}
}
/**
- * Compress the track
+ * Finish the compression by deleting the marked points
*/
- public void compressTrack()
+ public void finishCompressTrack()
{
UndoCompress undo = new UndoCompress(_track);
- // Get compression parameter
- Object compParam = JOptionPane.showInputDialog(_frame,
- I18nManager.getText("dialog.compresstrack.parameter.text"),
- I18nManager.getText("dialog.compresstrack.title"),
- JOptionPane.QUESTION_MESSAGE, null, null, "100");
- int compNumber = parseNumber(compParam);
- if (compNumber <= 0) return;
// call track to do compress
- int numPointsDeleted = _trackInfo.compress(compNumber);
+ int numPointsDeleted = _trackInfo.deleteMarkedPoints();
// add to undo stack if successful
if (numPointsDeleted > 0)
{
undo.setNumPointsDeleted(numPointsDeleted);
_undoStack.add(undo);
- JOptionPane.showMessageDialog(_frame,
- I18nManager.getText("dialog.compresstrack.text") + " - "
- + numPointsDeleted + " "
- + (numPointsDeleted==1?I18nManager.getText("dialog.compresstrack.single.text"):I18nManager.getText("dialog.compresstrack.multi.text")),
- I18nManager.getText("dialog.compresstrack.title"), JOptionPane.INFORMATION_MESSAGE);
+ UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " "
+ + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi")));
}
- else
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.compresstrack.nonefound"),
- I18nManager.getText("dialog.compresstrack.title"), JOptionPane.WARNING_MESSAGE);
+ else {
+ showErrorMessage("function.compress", "dialog.compress.nonefound");
}
}
-
/**
- * Reverse a section of the track
+ * Reverse the currently selected section of the track
*/
public void reverseRange()
{
int selStart = _trackInfo.getSelection().getStart();
int selEnd = _trackInfo.getSelection().getEnd();
if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
- || _reversePointsConfirmed
+ || _mangleTimestampsConfirmed
|| (JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.confirmreversetrack.text"),
I18nManager.getText("dialog.confirmreversetrack.title"),
- JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true)))
+ JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
{
- UndoReverseSection undo = new UndoReverseSection(selStart, selEnd);
+ UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
// call track to reverse range
if (_track.reverseRange(selStart, selEnd))
{
_undoStack.add(undo);
+ // Confirm
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
+ }
+ }
+ }
+
+ /**
+ * Complete the add time offset function with the specified offset
+ * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
+ */
+ public void finishAddTimeOffset(long inTimeOffset)
+ {
+ // Construct undo information
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
+ if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
+ {
+ _undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
+ }
+ }
+
+
+ /**
+ * Complete the add altitude offset function with the specified offset
+ * @param inOffset altitude offset to add as String
+ * @param inFormat altitude format of offset (eg Feet, Metres)
+ */
+ public void finishAddAltitudeOffset(String inOffset, Altitude.Format inFormat)
+ {
+ // Sanity check
+ if (inOffset == null || inOffset.equals("") || inFormat==Altitude.Format.NO_FORMAT) {
+ return;
+ }
+ // Construct undo information
+ UndoAddAltitudeOffset undo = new UndoAddAltitudeOffset(_trackInfo);
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ // How many decimal places are given in the offset?
+ int numDecimals = NumberUtils.getDecimalPlaces(inOffset);
+ boolean success = false;
+ // Decimal offset given
+ try {
+ double offsetd = Double.parseDouble(inOffset);
+ success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inFormat, numDecimals);
+ }
+ catch (NumberFormatException nfe) {}
+ if (success)
+ {
+ _undoStack.add(undo);
+ _trackInfo.getSelection().markInvalid();
+ UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addaltitudeoffset"));
+ }
+ }
+
+
+ /**
+ * Merge the track segments within the current selection
+ */
+ public void mergeTrackSegments()
+ {
+ if (_trackInfo.getSelection().hasRangeSelected())
+ {
+ // Maybe could check segment start flags to see if it's worth merging
+ // If first track point is already start and no other seg starts then do nothing
+
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ // Make undo object
+ UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
+ // Call track to merge segments
+ if (_trackInfo.mergeTrackSegments(selStart, selEnd)) {
+ _undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
}
}
}
/**
- * Rearrange the waypoints into track order
+ * Average the selected points
*/
- public void rearrangeWaypoints(int inFunction)
+ public void averageSelection()
{
- UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(_track);
- boolean success = false;
- if (inFunction == REARRANGE_TO_START || inFunction == REARRANGE_TO_END)
- {
- // Collect the waypoints to the start or end of the track
- success = _track.collectWaypoints(inFunction == REARRANGE_TO_START);
- }
- else
- {
- // Interleave the waypoints into track order
- success = _track.interleaveWaypoints();
- }
- if (success)
+ // Find following track point
+ DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
+ boolean segFlag = false;
+ if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
+ UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
+ // call track info object to do the averaging
+ if (_trackInfo.average())
{
_undoStack.add(undo);
}
- else
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"),
- I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);
- }
}
/**
- * Open a new window with the 3d view
+ * Create a new point at the given position
+ * @param inPoint point to add
*/
- public void show3dWindow()
+ public void createPoint(DataPoint inPoint)
{
- ThreeDWindow window = WindowFactory.getWindow(this, _frame);
- if (window == null)
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"),
- I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE);
- }
- else
- {
- try
- {
- // Pass the track object and show the window
- window.setTrack(_track);
- window.show();
- }
- catch (ThreeDException e)
- {
- JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(),
- I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE);
- }
- }
+ // create undo object
+ UndoCreatePoint undo = new UndoCreatePoint();
+ _undoStack.add(undo);
+ // add point to track
+ inPoint.setSegmentStart(true);
+ _track.appendPoints(new DataPoint[] {inPoint});
+ // ensure track's field list contains point's fields
+ _track.extendFieldList(inPoint.getFieldList());
+ _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
+ // update listeners
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
}
/**
- * Select all points
+ * Cut the current selection and move it to before the currently selected point
*/
- public void selectAll()
+ public void cutAndMoveSelection()
{
- _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1);
+ int startIndex = _trackInfo.getSelection().getStart();
+ int endIndex = _trackInfo.getSelection().getEnd();
+ int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
+ // If timestamps would be mangled by cut/move, confirm
+ if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
+ || _mangleTimestampsConfirmed
+ || (JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.confirmcutandmove.text"),
+ I18nManager.getText("dialog.confirmcutandmove.title"),
+ JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
+ {
+ // Find points to set segment flags
+ DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
+ DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
+ DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
+ // Make undo object
+ UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
+ // Call track info to move track section
+ if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
+ {
+ // Set segment start flags (first track point, next track point, move to point)
+ if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
+ if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
+ if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
+
+ // Add undo object to stack, set confirm message
+ _undoStack.add(undo);
+ _trackInfo.getSelection().selectRange(-1, -1);
+ UpdateMessageBroker.informSubscribers();
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
+ }
+ }
}
/**
{
// deselect point, range and photo
_trackInfo.getSelection().clearAll();
+ _track.clearDeletionMarkers();
}
+ /**
+ * Receive loaded data and start load
+ * @param inFieldArray array of fields
+ * @param inDataArray array of data
+ * @param inAltFormat altitude format
+ * @param inSourceInfo information about the source of the data
+ */
+ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
+ Altitude.Format inAltFormat, SourceInfo inSourceInfo)
+ {
+ informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo, null, null);
+ }
/**
- * Receive loaded data and optionally merge with current Track
+ * Receive loaded data and determine whether to filter on tracks or not
* @param inFieldArray array of fields
* @param inDataArray array of data
+ * @param inAltFormat altitude format
+ * @param inSourceInfo information about the source of the data
+ * @param inTrackNameList information about the track names
*/
- public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
+ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
+ Altitude.Format inAltFormat, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
+ {
+ // no link array given
+ informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo,
+ inTrackNameList, null);
+ }
+
+ /**
+ * Receive loaded data and determine whether to filter on tracks or not
+ * @param inFieldArray array of fields
+ * @param inDataArray array of data
+ * @param inAltFormat altitude format
+ * @param inSourceInfo information about the source of the data
+ * @param inTrackNameList information about the track names
+ * @param inLinkInfo links to photo/audio clips
+ */
+ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
+ Altitude.Format inAltFormat, SourceInfo inSourceInfo,
+ TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
{
// Check whether loaded array can be properly parsed into a Track
- Track loadedTrack = new Track(_broker);
+ Track loadedTrack = new Track();
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);
+ showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
+ // load next file if there's a queue
+ loadNextFile();
return;
}
+ // Check for doubled track
+ if (Checker.isDoubledTrack(loadedTrack)) {
+ JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
+ I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
+ }
+
+ _busyLoading = true;
+ // Attach photos and/or audio clips to points
+ if (inLinkInfo != null)
+ {
+ String[] linkArray = inLinkInfo.getLinkArray();
+ if (linkArray != null) {
+ new AsyncMediaLoader(this, inLinkInfo.getZipFile(), linkArray, loadedTrack, inSourceInfo.getFile()).begin();
+ }
+ }
+ // Look at TrackNameList, decide whether to filter or not
+ if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
+ {
+ // Launch a dialog to let the user choose which tracks to load, then continue
+ new SelectTracksFunction(this, loadedTrack, inSourceInfo, inTrackNameList).begin();
+ }
+ else {
+ // go directly to load
+ informDataLoaded(loadedTrack, inSourceInfo);
+ }
+ }
+
+
+ /**
+ * Receive loaded data and optionally merge with current Track
+ * @param inLoadedTrack loaded track
+ * @param inSourceInfo information about the source of the data
+ */
+ public void informDataLoaded(Track inLoadedTrack, SourceInfo inSourceInfo)
+ {
// Decide whether to load or append
- if (_track != null && _track.getNumPoints() > 0)
+ if (_track.getNumPoints() > 0)
{
// ask whether to replace or append
- int answer = JOptionPane.showConfirmDialog(_frame,
- I18nManager.getText("dialog.openappend.text"),
- I18nManager.getText("dialog.openappend.title"),
- JOptionPane.YES_NO_CANCEL_OPTION);
+ int answer = 0;
+ if (_dataFiles == null || _firstDataFile) {
+ answer = JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.openappend.text"),
+ I18nManager.getText("dialog.openappend.title"),
+ JOptionPane.YES_NO_CANCEL_OPTION);
+ }
+ else {
+ // Automatically append if there's a file load queue
+ answer = JOptionPane.YES_OPTION;
+ }
if (answer == JOptionPane.YES_OPTION)
{
// append data to current Track
- _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints()));
- _track.combine(loadedTrack);
- // set filename if currently empty
- if (_trackInfo.getFileInfo().getNumFiles() == 0)
- {
- _trackInfo.getFileInfo().setFile(inFilename);
- }
- else
- {
- _trackInfo.getFileInfo().addFile();
- }
+ UndoLoad undo = new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints());
+ undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+ _undoStack.add(undo);
+ _track.combine(inLoadedTrack);
+ // set source information
+ inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
+ _trackInfo.getFileInfo().addSource(inSourceInfo);
}
else if (answer == JOptionPane.NO_OPTION)
{
// Don't append, replace data
PhotoList photos = null;
- if (_trackInfo.getPhotoList().hasCorrelatedPhotos())
- {
+ if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
photos = _trackInfo.getPhotoList().cloneList();
}
- _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos));
+ UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
+ undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+ _undoStack.add(undo);
_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();
- }
+ _trackInfo.getSelection().clearAll();
+ _track.load(inLoadedTrack);
+ inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
+ _trackInfo.getFileInfo().replaceSource(inSourceInfo);
+ _trackInfo.getPhotoList().removeCorrelatedPhotos();
+ _trackInfo.getAudioList().removeCorrelatedAudios();
}
}
else
{
- // currently no data held, so use received data
- _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null));
+ // Currently no data held, so transfer received data
+ UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
+ undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
+ _undoStack.add(undo);
_lastSavePosition = _undoStack.size();
- _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
- _trackInfo.getFileInfo().setFile(inFilename);
+ _trackInfo.getSelection().clearAll();
+ _track.load(inLoadedTrack);
+ inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
+ _trackInfo.getFileInfo().addSource(inSourceInfo);
}
- _broker.informSubscribers();
+ // Update config before subscribers are told
+ boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
+ Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
+ UpdateMessageBroker.informSubscribers();
+ // Update status bar
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
+ + " '" + inSourceInfo.getName() + "'");
// update menu
_menuManager.informFileLoaded();
+ // Remove busy lock
+ _busyLoading = false;
+ // load next file if there's a queue
+ loadNextFile();
}
-
/**
- * Accept a list of loaded photos
- * @param inPhotoList List of Photo objects
+ * Inform the app that NO data was loaded, eg cancel pressed
+ * Only needed if there's another file waiting in the queue
*/
- public void informPhotosLoaded(List inPhotoList)
+ public void informNoDataLoaded()
{
- if (inPhotoList != null && !inPhotoList.isEmpty())
- {
- int[] numsAdded = _trackInfo.addPhotos(inPhotoList);
- int numPhotosAdded = numsAdded[0];
- int numPointsAdded = numsAdded[1];
- if (numPhotosAdded > 0)
- {
- // 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 (numPhotosAdded == 1)
- {
- JOptionPane.showMessageDialog(_frame,
- "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
- I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
- }
- else
- {
- JOptionPane.showMessageDialog(_frame,
- "" + 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)
- _broker.informSubscribers();
- // update menu
- _menuManager.informFileLoaded();
- }
+ // Load next file if there's a queue
+ loadNextFile();
}
-
/**
- * Connect the current photo to the current point
+ * Load the next file in the waiting list, if any
*/
- public void connectPhotoToPoint()
+ private void loadNextFile()
{
- 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?)
+ _firstDataFile = false;
+ if (_dataFiles == null || _dataFiles.size() == 0) {
+ _dataFiles = null;
+ }
+ else {
+ new Thread(new Runnable() {
+ public void run() {
+ File f = _dataFiles.get(0);
+ _dataFiles.remove(0);
+ _fileLoader.openFile(f);
+ }
+ }).start();
}
}
/**
- * Remove the current photo, if any
+ * Accept a list of loaded photos
+ * @param inPhotoSet Set of Photo objects
*/
- public void deleteCurrentPhoto()
+ public void informPhotosLoaded(Set<Photo> inPhotoSet)
{
- // Delete the current photo, and optionally its point too, keeping undo information
- Photo currentPhoto = _trackInfo.getCurrentPhoto();
- if (currentPhoto != null)
+ if (inPhotoSet != null && !inPhotoSet.isEmpty())
{
- // Photo is selected, see if it has a point or not
- boolean photoDeleted = false;
- UndoDeletePhoto undoAction = null;
- if (currentPhoto.getDataPoint() == null)
+ int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
+ int numPhotosAdded = numsAdded[0];
+ int numPointsAdded = numsAdded[1];
+ if (numPhotosAdded > 0)
{
- // no point attached, so just delete photo
- undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
- null, -1);
- photoDeleted = _trackInfo.deleteCurrentPhoto(false);
+ // Save numbers so load can be undone
+ _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
}
- 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);
- }
+ if (numPhotosAdded == 1) {
+ UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
}
- // Add undo information to stack if necessary
- if (photoDeleted)
- {
- _undoStack.add(undoAction);
+ else {
+ UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
}
+ // MAYBE: Improve message when photo(s) fail to load (eg already added)
+ UpdateMessageBroker.informSubscribers();
+ // update menu
+ if (numPointsAdded > 0) _menuManager.informFileLoaded();
}
}
{
if (_undoStack.isEmpty())
{
+ // Nothing to undo
JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
}
_undoStack.clear();
_lastSavePosition = 0;
if (unsaved) _lastSavePosition = -1;
- _broker.informSubscribers();
+ UpdateMessageBroker.informSubscribers();
}
}
{
for (int i=0; i<inNumUndos; i++)
{
- ((UndoOperation) _undoStack.pop()).performUndo(_trackInfo);
+ _undoStack.pop().performUndo(_trackInfo);
}
- JOptionPane.showMessageDialog(_frame, "" + inNumUndos + " "
- + (inNumUndos==1?I18nManager.getText("dialog.confirmundo.single.text"):I18nManager.getText("dialog.confirmundo.multiple.text")),
- I18nManager.getText("dialog.confirmundo.title"),
- JOptionPane.INFORMATION_MESSAGE);
+ String message = "" + inNumUndos + " "
+ + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
+ UpdateMessageBroker.informSubscribers(message);
}
catch (UndoException ue)
{
- JOptionPane.showMessageDialog(_frame,
- I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage(),
- I18nManager.getText("error.undofailed.title"),
- JOptionPane.ERROR_MESSAGE);
+ showErrorMessageNoLookup("error.undofailed.title",
+ I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
_undoStack.clear();
- _broker.informSubscribers();
+ UpdateMessageBroker.informSubscribers();
}
catch (EmptyStackException empty) {}
}
}
return num;
}
+
+ /**
+ * Show a map url in an external browser
+ * @param inSourceIndex index of map source to use
+ */
+ public void showExternalMap(int inSourceIndex)
+ {
+ BrowserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
+ }
+
+ /**
+ * Display a standard error message
+ * @param inTitleKey key to lookup for window title
+ * @param inMessageKey key to lookup for error message
+ */
+ public void showErrorMessage(String inTitleKey, String inMessageKey)
+ {
+ JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
+ I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ * Display a standard error message
+ * @param inTitleKey key to lookup for window title
+ * @param inMessage error message
+ */
+ public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
+ {
+ JOptionPane.showMessageDialog(_frame, inMessage,
+ I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ * @param inViewport viewport object
+ */
+ public void setViewport(Viewport inViewport)
+ {
+ _viewport = inViewport;
+ }
+
+ /**
+ * @return current viewport object
+ */
+ public Viewport getViewport()
+ {
+ return _viewport;
+ }
+
+ /**
+ * Set the controller for the full screen mode
+ * @param inController controller object
+ */
+ public void setSidebarController(SidebarController inController)
+ {
+ _sidebarController = inController;
+ }
+
+ /**
+ * Toggle sidebars on and off
+ */
+ public void toggleSidebars()
+ {
+ _sidebarController.toggle();
+ }
+
+ /** @return true if App is currently busy with loading data */
+ public boolean isBusyLoading() {
+ return _busyLoading;
+ }
}