+ /**
+ * Accept a list of loaded photos
+ * @param inPhotoSet Set of Photo objects
+ */
+ public void informPhotosLoaded(Set inPhotoSet)
+ {
+ if (inPhotoSet != null && !inPhotoSet.isEmpty())
+ {
+ 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));
+ }
+ 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();
+ }
+ }
+
+
+ /**
+ * 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);
+ _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+ }
+
+
+ /**
+ * 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);
+ _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+ }
+
+
+ /**
+ * 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);
+ }
+ }
+ }
+
+
+ /**
+ * 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 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);
+ // 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
+ JOptionPane.showMessageDialog(_frame, "" + numPhotos + " "
+ + (numPhotos==1?I18nManager.getText("dialog.correlate.confirmsingle.text"):I18nManager.getText("dialog.correlate.confirmmultiple.text")),
+ I18nManager.getText("dialog.correlate.title"),
+ JOptionPane.INFORMATION_MESSAGE);
+ // observers already informed by track update
+ }
+ }
+
+
+ /**
+ * Save the coordinates of photos in their exif data
+ */
+ public void saveExif()
+ {
+ ExifSaver saver = new ExifSaver(_frame);
+ saver.saveExifInformation(_trackInfo.getPhotoList());
+ }
+
+