import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
-import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import tim.prune.App;
+import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Distance;
import tim.prune.data.Timestamp;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
+import tim.prune.undo.UndoCorrelatePhotos;
/**
* Class to manage the automatic correlation of photos to points
* including the GUI stuff to control the correlation options
*/
-public class PhotoCorrelator
+public class PhotoCorrelator extends GenericFunction
{
- private App _app;
- private JFrame _parentFrame;
private JDialog _dialog;
private JButton _nextButton = null, _backButton = null;
private JButton _okButton = null;
/**
* Constructor
* @param inApp App object to report actions to
- * @param inFrame parent frame for dialogs
*/
- public PhotoCorrelator(App inApp, JFrame inFrame)
+ public PhotoCorrelator(App inApp)
{
- _app = inApp;
- _parentFrame = inFrame;
- _dialog = new JDialog(inFrame, I18nManager.getText("dialog.correlate.title"), true);
- _dialog.setLocationRelativeTo(inFrame);
- _dialog.getContentPane().add(makeDialogContents());
- _dialog.pack();
+ super(inApp);
}
+ /** Get the name key */
+ public String getNameKey() {
+ return "function.correlatephotos";
+ }
+
/**
* Reset dialog and show it
*/
public void begin()
{
+ // First create dialog if necessary
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.getContentPane().add(makeDialogContents());
+ _dialog.pack();
+ }
// Check whether track has timestamps, exit if not
if (!_app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP))
{
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.correlate.notimestamps"),
- I18nManager.getText("dialog.correlate.title"), JOptionPane.INFORMATION_MESSAGE);
+ I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
return;
}
// Check for any non-correlated photos, show warning continue/cancel
{
Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
if (JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.correlate.nouncorrelatedphotos"),
- I18nManager.getText("dialog.correlate.title"), JOptionPane.YES_NO_OPTION,
+ I18nManager.getText(getNameKey()), JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.NO_OPTION)
{
_tipLabel.setVisible(true);
setupSecondCard(null);
}
- _dialog.show();
+ _dialog.setVisible(true);
}
JPanel card2Top = new JPanel();
card2Top.setLayout(new BoxLayout(card2Top, BoxLayout.Y_AXIS));
_tipLabel = new JLabel(I18nManager.getText("dialog.correlate.options.tip"));
+ _tipLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
card2Top.add(_tipLabel);
- card2Top.add(new JLabel(I18nManager.getText("dialog.correlate.options.intro")));
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.correlate.options.intro"));
+ introLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
+ card2Top.add(introLabel);
// time offset section
JPanel offsetPanel = new JPanel();
offsetPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.offsetpanel")));
card2Top.add(previewButton);
card2.add(card2Top, BorderLayout.NORTH);
// preview
- _previewTable = new JTable();
+ _previewTable = new JTable(new PhotoPreviewTableModel());
JScrollPane previewScrollPane = new JScrollPane(_previewTable);
previewScrollPane.setPreferredSize(new Dimension(300, 100));
card2.add(previewScrollPane, BorderLayout.CENTER);
{
public void actionPerformed(ActionEvent e)
{
- _app.finishCorrelatePhotos(getPointPairs());
+ finishCorrelation();
_dialog.dispose();
}
});
for (int i=0; i<numPhotos; i++)
{
Photo photo = inTrackInfo.getPhotoList().getPhoto(i);
- if (photo.getDataPoint() != null && photo.getDataPoint().hasTimestamp())
+ // For working out time differences, can't use photos which already had point information
+ if (photo.getDataPoint() != null && photo.getDataPoint().hasTimestamp()
+ && photo.getOriginalStatus() == Photo.Status.NOT_CONNECTED)
{
// Calculate time difference, add to table model
long timeDiff = photo.getTimestamp().getSecondsSince(photo.getDataPoint().getTimestamp());
_pointLaterOption.setSelected(!inTimeDiff.getIsPositive());
createPreview(inTimeDiff, true);
CardLayout cl = (CardLayout) _cards.getLayout();
- cl.next(_cards);
+ cl.last(_cards);
_backButton.setEnabled(hasTimeDiff);
_nextButton.setEnabled(false);
// enable ok button if any photos have been selected
PhotoPreviewTableRow row = new PhotoPreviewTableRow(pair);
// Don't try to correlate photos which don't have points either side
boolean correlatePhoto = pair.isValid();
+ // Don't select photos which already have a point
+ if (photo.getCurrentStatus() != Photo.Status.NOT_CONNECTED) {correlatePhoto = false;}
// Check time limits, distance limits
if (timeLimit != null && correlatePhoto) {
long numSecs = pair.getMinSeconds();
correlatePhoto = (angDistPhoto < angDistLimit);
}
// Don't select photos which are already correlated to the same point
- if (pair.getSecondsBefore() == 0L && pair.getPointBefore().getPhoto() != null
- && pair.getPointBefore().getPhoto().equals(photo)) {
+ if (pair.getSecondsBefore() == 0L && pair.getPointBefore().isDuplicate(photo.getDataPoint())) {
correlatePhoto = false;
}
row.setCorrelateFlag(correlatePhoto);
if (inShowWarning && !model.hasPhotosSelected())
{
JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.correlate.alloutsiderange"),
- I18nManager.getText("dialog.correlate.title"), JOptionPane.ERROR_MESSAGE);
+ I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
}
}
/**
* @return the selected distance units from the dropdown
*/
- private int getSelectedDistanceUnits()
+ private Distance.Units getSelectedDistanceUnits()
{
- final int[] distUnits = {Distance.UNITS_KILOMETRES, Distance.UNITS_METRES, Distance.UNITS_MILES};
+ final Distance.Units[] distUnits = {Distance.Units.KILOMETRES, Distance.Units.METRES, Distance.Units.MILES};
return distUnits[_distUnitsDropdown.getSelectedIndex()];
}
private static PointPair getPointPairForPhoto(Track inTrack, Photo inPhoto, TimeDifference inOffset)
{
PointPair pair = new PointPair(inPhoto);
- // Add offet to photo timestamp
- Timestamp photoStamp = inPhoto.getTimestamp().subtractOffset(inOffset);
+ // Add/subtract offet to photo timestamp
+ Timestamp photoStamp = inPhoto.getTimestamp().createMinusOffset(inOffset);
int numPoints = inTrack.getNumPoints();
for (int i=0; i<numPoints; i++)
{
DataPoint point = inTrack.getPoint(i);
- Timestamp pointStamp = point.getTimestamp();
- if (pointStamp != null && pointStamp.isValid())
+ if (point.getPhoto() == null || point.getPhoto().getCurrentStatus() != Photo.Status.TAGGED)
{
- long numSeconds = pointStamp.getSecondsSince(photoStamp);
- pair.addPoint(point, numSeconds);
+ Timestamp pointStamp = point.getTimestamp();
+ if (pointStamp != null && pointStamp.isValid())
+ {
+ long numSeconds = pointStamp.getSecondsSince(photoStamp);
+ pair.addPoint(point, numSeconds);
+ }
}
}
return pair;
private static int getMedianIndex(PhotoSelectionTableModel inModel)
{
// make sortable list
- TreeSet set = new TreeSet();
+ TreeSet<TimeIndexPair> set = new TreeSet<TimeIndexPair>();
// loop through rows of table adding to list
int numRows = inModel.getRowCount();
int i;
}
// pull out middle entry and return index
TimeIndexPair pair = null;
- Iterator iterator = set.iterator();
+ Iterator<TimeIndexPair> iterator = set.iterator();
for (i=0; i<(numRows+1)/2; i++)
{
- pair = (TimeIndexPair) iterator.next();
+ pair = iterator.next();
}
return pair.getIndex();
}
// no uncorrelated photos found
return false;
}
+
+ /**
+ * Finish the correlation by modifying the track
+ * and passing the Undo information back to the App
+ */
+ private void finishCorrelation()
+ {
+ PointPair[] pointPairs = getPointPairs();
+ if (pointPairs == null || pointPairs.length <= 0) {return;}
+
+ // begin to construct undo information
+ UndoCorrelatePhotos undo = new UndoCorrelatePhotos(_app.getTrackInfo());
+ // loop over Photos
+ int arraySize = pointPairs.length;
+ int i = 0, numPhotos = 0;
+ int numPointsToCreate = 0;
+ PointPair pair = null;
+ for (i=0; i<arraySize; i++)
+ {
+ pair = pointPairs[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 = pointPairs[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
+ _app.getTrackInfo().getTrack().appendPoints(addedPoints);
+ }
+
+ // send undo information back to controlling app
+ undo.setNumPhotosCorrelated(numPhotos);
+ _app.completeFunction(undo, ("" + numPhotos + " "
+ + (numPhotos==1?I18nManager.getText("confirm.correlate.single"):I18nManager.getText("confirm.correlate.multi"))));
+ // observers already informed by track update
+ }
}