package tim.prune;
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.browser.BrowserLauncher;
+import tim.prune.browser.UrlGenerator;
+import tim.prune.correlate.PhotoCorrelator;
+import tim.prune.correlate.PointPair;
+import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
+import tim.prune.data.LatLonRectangle;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
import tim.prune.data.Photo;
import tim.prune.data.PhotoList;
import tim.prune.data.Track;
import tim.prune.edit.PointEditor;
import tim.prune.edit.PointNameEditor;
import tim.prune.gui.MenuManager;
+import tim.prune.gui.TimeOffsetDialog;
import tim.prune.gui.UndoManager;
import tim.prune.load.FileLoader;
+import tim.prune.load.GpsLoader;
import tim.prune.load.JpegLoader;
-import tim.prune.load.PhotoMeasurer;
import tim.prune.save.ExifSaver;
import tim.prune.save.FileSaver;
+import tim.prune.save.GpxExporter;
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.UndoAddTimeOffset;
import tim.prune.undo.UndoCompress;
import tim.prune.undo.UndoConnectPhoto;
+import tim.prune.undo.UndoConnectPhotoWithClone;
+import tim.prune.undo.UndoCorrelatePhotos;
+import tim.prune.undo.UndoCreatePoint;
+import tim.prune.undo.UndoCutAndMove;
import tim.prune.undo.UndoDeleteDuplicates;
import tim.prune.undo.UndoDeletePhoto;
import tim.prune.undo.UndoDeletePoint;
import tim.prune.undo.UndoDeleteRange;
+import tim.prune.undo.UndoDisconnectPhoto;
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.UndoMergeTrackSegments;
import tim.prune.undo.UndoOperation;
import tim.prune.undo.UndoRearrangeWaypoints;
import tim.prune.undo.UndoReverseSection;
private MenuManager _menuManager = null;
private FileLoader _fileLoader = null;
private JpegLoader _jpegLoader = null;
- private KmlExporter _exporter = null;
+ private GpsLoader _gpsLoader = null;
+ private FileSaver _fileSaver = null;
+ private KmlExporter _kmlExporter = null;
+ private GpxExporter _gpxExporter = null;
private PovExporter _povExporter = null;
+ private BrowserLauncher _browserLauncher = null;
private Stack _undoStack = null;
- private UpdateMessageBroker _broker = null;
- private boolean _reversePointsConfirmed = false;
+ private boolean _mangleTimestampsConfirmed = false;
// Constants
public static final int REARRANGE_TO_START = 0;
/**
* 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);
+ _track = new Track();
+ _trackInfo = new TrackInfo(_track);
}
/**
- * 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()));
}
+ /**
+ * Start a load from Gps
+ */
+ public void beginLoadFromGps()
+ {
+ if (_gpsLoader == null)
+ _gpsLoader = new GpsLoader(this, _frame);
+ _gpsLoader.openDialog();
+ }
/**
* Save the file in the selected format
}
else
{
- FileSaver saver = new FileSaver(this, _frame, _track);
- saver.showDialog(_fileLoader.getLastUsedDelimiter());
+ if (_fileSaver == null) {
+ _fileSaver = new FileSaver(this, _frame, _track);
+ }
+ char delim = ',';
+ if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
+ _fileSaver.showDialog(delim);
}
}
else
{
// Invoke the export
- if (_exporter == null)
+ if (_kmlExporter == null)
{
- _exporter = new KmlExporter(_frame, _trackInfo);
+ _kmlExporter = new KmlExporter(_frame, _trackInfo);
}
- _exporter.showDialog();
+ _kmlExporter.showDialog();
+ }
+ }
+
+
+ /**
+ * Export track data as Gpx
+ */
+ public void exportGpx()
+ {
+ if (_track == null)
+ {
+ JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
+ I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
+ }
+ else
+ {
+ // Invoke the export
+ if (_gpxExporter == null)
+ {
+ _gpxExporter = new GpxExporter(_frame, _trackInfo);
+ }
+ _gpxExporter.showDialog();
}
}
* @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
*/
private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap)
{
/**
* 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)
{
if (_track.editPoint(currentPoint, inEditList))
{
_undoStack.push(undo);
+ // 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)
{
- boolean deletePhoto = false;
- Photo currentPhoto = currentPoint.getPhoto();
+ // 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;}
+ }
+ // 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())
+ {
+ // 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);
}
}
+ // Confirm
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
}
}
}
}
}
// add information to undo stack
- UndoOperation undo = new UndoDeleteRange(_trackInfo);
+ UndoDeleteRange undo = new UndoDeleteRange(_trackInfo);
// delete requested photos
for (int i=0; i<numToDelete; i++)
{
if (_trackInfo.deleteRange())
{
_undoStack.push(undo);
+ // Confirm
+ UpdateMessageBroker.informSubscribers("" + numToDelete + " "
+ + I18nManager.getText("confirm.deletepoint.multi"));
}
}
}
String message = null;
if (numDeleted == 1)
{
- message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text");
+ message = "1 " + I18nManager.getText("confirm.deleteduplicates.single");
}
else
{
- message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text");
+ message = "" + numDeleted + " " + I18nManager.getText("confirm.deleteduplicates.multi");
}
- JOptionPane.showMessageDialog(_frame, message,
- I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
+ // Pass message to broker
+ UpdateMessageBroker.informSubscribers(message);
}
else
{
+ // No duplicates found to delete
JOptionPane.showMessageDialog(_frame,
I18nManager.getText("dialog.deleteduplicates.nonefound"),
I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
{
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
{
/**
- * 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"));
+ }
+ }
+ }
+
+ /**
+ * Trigger the dialog to add a time offset to the current selection
+ */
+ public void beginAddTimeOffset()
+ {
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)) {
+ JOptionPane.showMessageDialog(_frame,
+ I18nManager.getText("dialog.addtimeoffset.notimestamps"),
+ I18nManager.getText("dialog.addtimeoffset.title"), JOptionPane.ERROR_MESSAGE);
+ }
+ else {
+ TimeOffsetDialog timeDialog = new TimeOffsetDialog(this, _frame);
+ timeDialog.showDialog();
+ }
+ }
+
+ /**
+ * Complete the add time offset function with the specified offset
+ * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
+ */
+ public void finishAddTimeOffset(long inTimeOffset)
+ {
+ // Construct undo information
+ int selStart = _trackInfo.getSelection().getStart();
+ int selEnd = _trackInfo.getSelection().getEnd();
+ UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
+ if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset))
+ {
+ _undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
+ }
+ }
+
+
+ /**
+ * Merge the track segments within the current selection
+ */
+ 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 (_track.mergeTrackSegments(selStart, selEnd)) {
+ _undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
}
}
}
}
+ /**
+ * Create a new point at the given lat/long coordinates
+ * @param inLat latitude
+ * @param inLong longitude
+ */
+ public void createPoint(double inLat, double inLong)
+ {
+ // create undo object
+ UndoCreatePoint undo = new UndoCreatePoint();
+ // create point and add to track
+ DataPoint point = new DataPoint(new Latitude(inLat, Coordinate.FORMAT_NONE), new Longitude(inLong, Coordinate.FORMAT_NONE), null);
+ point.setSegmentStart(true);
+ _track.appendPoints(new DataPoint[] {point});
+ _trackInfo.getSelection().selectPoint(_trackInfo.getTrack().getNumPoints()-1);
+ // add undo object to stack
+ _undoStack.add(undo);
+ // update listeners
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
+ }
+
+
/**
* Rearrange the waypoints into track order
+ * @param inFunction nearest point, all to end or all to start
*/
public void rearrangeWaypoints(int inFunction)
{
if (success)
{
_undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.rearrangewaypoints"));
}
else
{
}
+ /**
+ * Cut the current selection and move it to before the currently selected point
+ */
+ public void cutAndMoveSelection()
+ {
+ int startIndex = _trackInfo.getSelection().getStart();
+ int endIndex = _trackInfo.getSelection().getEnd();
+ int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
+ // If timestamps would be mangled by cut/move, confirm
+ if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
+ || _mangleTimestampsConfirmed
+ || (JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.confirmcutandmove.text"),
+ I18nManager.getText("dialog.confirmcutandmove.title"),
+ JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
+ {
+ // Find points to set segment flags
+ DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
+ DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
+ DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
+ // Make undo object
+ UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
+ // Call track info to move track section
+ if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
+ {
+ // Set segment start flags (first track point, next track point, move to point)
+ if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
+ if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
+ if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
+
+ // Add undo object to stack, set confirm message
+ _undoStack.add(undo);
+ _trackInfo.getSelection().deselectRange();
+ UpdateMessageBroker.informSubscribers();
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
+ }
+ }
+ }
+
+
/**
* Open a new window with the 3d view
*/
* Receive loaded data and optionally merge with current Track
* @param inFieldArray array of fields
* @param inDataArray array of data
+ * @param inAltFormat altitude format
+ * @param inFilename filename used
*/
public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
+ {
+ informDataLoaded(inFieldArray, inDataArray, inAltFormat, inFilename, false);
+ }
+
+ /**
+ * Receive loaded data and optionally merge with current Track
+ * @param inFieldArray array of fields
+ * @param inDataArray array of data
+ * @param inAltFormat altitude format
+ * @param inFilename filename used
+ * @param inOverrideAppend true to override append question and always append
+ */
+ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat,
+ String inFilename, boolean inOverrideAppend)
{
// Check whether loaded array can be properly parsed into a Track
- Track loadedTrack = new Track(_broker);
+ Track loadedTrack = new Track();
loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
if (loadedTrack.getNumPoints() <= 0)
{
if (_track != null && _track.getNumPoints() > 0)
{
// ask whether to replace or append
- int answer = JOptionPane.showConfirmDialog(_frame,
- I18nManager.getText("dialog.openappend.text"),
- I18nManager.getText("dialog.openappend.title"),
- JOptionPane.YES_NO_CANCEL_OPTION);
+ int answer = JOptionPane.YES_OPTION;
+ if (!inOverrideAppend) {
+ answer = JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.openappend.text"),
+ I18nManager.getText("dialog.openappend.title"),
+ JOptionPane.YES_NO_CANCEL_OPTION);
+ }
if (answer == JOptionPane.YES_OPTION)
{
// append data to current Track
_undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos));
_lastSavePosition = _undoStack.size();
// TODO: Should be possible to reuse the Track object already loaded?
+ _trackInfo.selectPoint(null);
_trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
_trackInfo.getFileInfo().setFile(inFilename);
if (photos != null)
_trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
_trackInfo.getFileInfo().setFile(inFilename);
}
- _broker.informSubscribers();
+ UpdateMessageBroker.informSubscribers();
+ // Update status bar
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile") + " '" + inFilename + "'");
// update menu
_menuManager.informFileLoaded();
}
/**
* Accept a list of loaded photos
- * @param inPhotoList List of Photo objects
+ * @param inPhotoSet Set of Photo objects
*/
- public void informPhotosLoaded(List inPhotoList)
+ public void informPhotosLoaded(Set inPhotoSet)
{
- if (inPhotoList != null && !inPhotoList.isEmpty())
+ if (inPhotoSet != null && !inPhotoSet.isEmpty())
{
- int[] numsAdded = _trackInfo.addPhotos(inPhotoList);
+ int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
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);
+ UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
}
else
{
- JOptionPane.showMessageDialog(_frame,
- "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
- I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
+ UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
}
// TODO: Improve message when photo(s) fail to load (eg already added)
- _broker.informSubscribers();
+ UpdateMessageBroker.informSubscribers();
// update menu
_menuManager.informFileLoaded();
}
{
Photo photo = _trackInfo.getCurrentPhoto();
DataPoint point = _trackInfo.getCurrentPoint();
- if (photo != null && point != null && point.getPhoto() == null)
+ if (photo != null && point != null)
{
- // connect
- _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
- photo.setDataPoint(point);
- point.setPhoto(photo);
- //TODO: Confirm connect (maybe with status in photo panel?)
+ if (point.getPhoto() != null)
+ {
+ // point already has a photo, confirm cloning of new point
+ if (JOptionPane.showConfirmDialog(_frame,
+ I18nManager.getText("dialog.connectphoto.clonepoint"),
+ I18nManager.getText("dialog.connect.title"),
+ JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)
+ {
+ // Create undo, clone point and attach
+ int pointIndex = _trackInfo.getSelection().getCurrentPointIndex() + 1;
+ // insert new point after current one
+ point = point.clonePoint();
+ UndoConnectPhotoWithClone undo = new UndoConnectPhotoWithClone(
+ point, photo.getFile().getName(), pointIndex);
+ _track.insertPoint(point, pointIndex);
+ photo.setDataPoint(point);
+ point.setPhoto(photo);
+ _undoStack.add(undo);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
+ }
+ }
+ else
+ {
+ // point doesn't currently have a photo, so just connect it
+ _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
+ photo.setDataPoint(point);
+ point.setPhoto(photo);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
+ }
+ }
+ }
+
+
+ /**
+ * Disconnect the current photo from its point
+ */
+ public void disconnectPhotoFromPoint()
+ {
+ Photo photo = _trackInfo.getCurrentPhoto();
+ if (photo != null && photo.getDataPoint() != null)
+ {
+ DataPoint point = photo.getDataPoint();
+ _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName()));
+ // disconnect
+ photo.setDataPoint(null);
+ point.setPhoto(null);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.disconnect"));
}
}
}
+ /**
+ * Begin the photo correlation process by invoking dialog
+ */
+ public void beginCorrelatePhotos()
+ {
+ PhotoCorrelator correlator = new PhotoCorrelator(this, _frame);
+ // TODO: Do we need to keep a reference to this Photo Correlator object to reuse it later?
+ correlator.begin();
+ }
+
+
+ /**
+ * Finish the photo correlation process
+ * @param inPointPairs array of PointPair objects describing operation
+ */
+ public void finishCorrelatePhotos(PointPair[] inPointPairs)
+ {
+ // TODO: This method is too big for App, but where should it go?
+ if (inPointPairs != null && inPointPairs.length > 0)
+ {
+ // begin to construct undo information
+ UndoCorrelatePhotos undo = new UndoCorrelatePhotos(_trackInfo);
+ // loop over Photos
+ int arraySize = inPointPairs.length;
+ int i = 0, numPhotos = 0;
+ int numPointsToCreate = 0;
+ PointPair pair = null;
+ for (i=0; i<arraySize; i++)
+ {
+ pair = inPointPairs[i];
+ if (pair != null && pair.isValid())
+ {
+ if (pair.getMinSeconds() == 0L)
+ {
+ // exact match
+ Photo pointPhoto = pair.getPointBefore().getPhoto();
+ if (pointPhoto == null)
+ {
+ // photo coincides with photoless point so connect the two
+ pair.getPointBefore().setPhoto(pair.getPhoto());
+ pair.getPhoto().setDataPoint(pair.getPointBefore());
+ }
+ else if (pointPhoto.equals(pair.getPhoto()))
+ {
+ // photo is already connected, nothing to do
+ }
+ else
+ {
+ // point is already connected to a different photo, so need to clone point
+ numPointsToCreate++;
+ }
+ }
+ else
+ {
+ // photo time falls between two points, so need to interpolate new one
+ numPointsToCreate++;
+ }
+ numPhotos++;
+ }
+ }
+ // Second loop, to create points if necessary
+ if (numPointsToCreate > 0)
+ {
+ // make new array for added points
+ DataPoint[] addedPoints = new DataPoint[numPointsToCreate];
+ int pointNum = 0;
+ DataPoint pointToAdd = null;
+ for (i=0; i<arraySize; i++)
+ {
+ pair = inPointPairs[i];
+ if (pair != null && pair.isValid())
+ {
+ pointToAdd = null;
+ if (pair.getMinSeconds() == 0L && pair.getPointBefore().getPhoto() != null
+ && !pair.getPointBefore().getPhoto().equals(pair.getPhoto()))
+ {
+ // clone point
+ pointToAdd = pair.getPointBefore().clonePoint();
+ }
+ else if (pair.getMinSeconds() > 0L)
+ {
+ // interpolate point
+ pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction());
+ }
+ if (pointToAdd != null)
+ {
+ // link photo to point
+ pointToAdd.setPhoto(pair.getPhoto());
+ pair.getPhoto().setDataPoint(pointToAdd);
+ // set to start of segment so not joined in track
+ pointToAdd.setSegmentStart(true);
+ // add to point array
+ addedPoints[pointNum] = pointToAdd;
+ pointNum++;
+ }
+ }
+ }
+ // expand track
+ _track.appendPoints(addedPoints);
+ }
+ // add undo information to stack
+ undo.setNumPhotosCorrelated(numPhotos);
+ _undoStack.add(undo);
+ // confirm correlation
+ UpdateMessageBroker.informSubscribers("" + numPhotos + " "
+ + (numPhotos==1?I18nManager.getText("confirm.correlate.single"):I18nManager.getText("confirm.correlate.multi")));
+ // observers already informed by track update
+ }
+ }
+
+
/**
* Save the coordinates of photos in their exif data
*/
{
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();
}
}
{
((UndoOperation) _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)
{
I18nManager.getText("error.undofailed.title"),
JOptionPane.ERROR_MESSAGE);
_undoStack.clear();
- _broker.informSubscribers();
+ UpdateMessageBroker.informSubscribers();
}
catch (EmptyStackException empty) {}
}
}
return num;
}
+
+ /**
+ * Show a brief help message
+ */
+ public void showHelp()
+ {
+ // show the dialog and offer to open home page
+ Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")};
+ if (JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.help.help"),
+ I18nManager.getText("menu.help"), JOptionPane.YES_NO_OPTION,
+ JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1])
+ == JOptionPane.YES_OPTION)
+ {
+ // User selected to launch home page
+ if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();}
+ _browserLauncher.launchBrowser("http://activityworkshop.net/software/prune/index.html");
+ }
+ }
+
+ /**
+ * Show a map url in an external browser
+ */
+ public void showExternalMap(int inSourceIndex)
+ {
+ if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();}
+ _browserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
+ }
}