3 import java.util.EmptyStackException;
5 import java.util.Stack;
7 import javax.swing.JFrame;
8 import javax.swing.JOptionPane;
10 import tim.prune.correlate.PhotoCorrelator;
11 import tim.prune.correlate.PointPair;
12 import tim.prune.data.DataPoint;
13 import tim.prune.data.Field;
14 import tim.prune.data.Photo;
15 import tim.prune.data.PhotoList;
16 import tim.prune.data.Track;
17 import tim.prune.data.TrackInfo;
18 import tim.prune.edit.FieldEditList;
19 import tim.prune.edit.PointEditor;
20 import tim.prune.edit.PointNameEditor;
21 import tim.prune.gui.MenuManager;
22 import tim.prune.gui.UndoManager;
23 import tim.prune.load.FileLoader;
24 import tim.prune.load.JpegLoader;
25 import tim.prune.save.ExifSaver;
26 import tim.prune.save.FileSaver;
27 import tim.prune.save.GpxExporter;
28 import tim.prune.save.KmlExporter;
29 import tim.prune.save.PovExporter;
30 import tim.prune.threedee.ThreeDException;
31 import tim.prune.threedee.ThreeDWindow;
32 import tim.prune.threedee.WindowFactory;
33 import tim.prune.undo.UndoCompress;
34 import tim.prune.undo.UndoConnectPhoto;
35 import tim.prune.undo.UndoCorrelatePhotos;
36 import tim.prune.undo.UndoDeleteDuplicates;
37 import tim.prune.undo.UndoDeletePhoto;
38 import tim.prune.undo.UndoDeletePoint;
39 import tim.prune.undo.UndoDeleteRange;
40 import tim.prune.undo.UndoDisconnectPhoto;
41 import tim.prune.undo.UndoEditPoint;
42 import tim.prune.undo.UndoException;
43 import tim.prune.undo.UndoInsert;
44 import tim.prune.undo.UndoLoad;
45 import tim.prune.undo.UndoLoadPhotos;
46 import tim.prune.undo.UndoOperation;
47 import tim.prune.undo.UndoRearrangeWaypoints;
48 import tim.prune.undo.UndoReverseSection;
52 * Main controller for the application
57 private JFrame _frame = null;
58 private Track _track = null;
59 private TrackInfo _trackInfo = null;
60 private int _lastSavePosition = 0;
61 private MenuManager _menuManager = null;
62 private FileLoader _fileLoader = null;
63 private JpegLoader _jpegLoader = null;
64 private FileSaver _fileSaver = null;
65 private KmlExporter _kmlExporter = null;
66 private GpxExporter _gpxExporter = null;
67 private PovExporter _povExporter = null;
68 private Stack _undoStack = null;
69 private UpdateMessageBroker _broker = null;
70 private boolean _reversePointsConfirmed = false;
73 public static final int REARRANGE_TO_START = 0;
74 public static final int REARRANGE_TO_END = 1;
75 public static final int REARRANGE_TO_NEAREST = 2;
80 * @param inFrame frame object for application
81 * @param inBroker message broker
83 public App(JFrame inFrame, UpdateMessageBroker inBroker)
86 _undoStack = new Stack();
88 _track = new Track(_broker);
89 _trackInfo = new TrackInfo(_track, _broker);
94 * @return the current TrackInfo
96 public TrackInfo getTrackInfo()
102 * Check if the application has unsaved data
103 * @return true if data is unsaved, false otherwise
105 public boolean hasDataUnsaved()
107 return (_undoStack.size() > _lastSavePosition
108 && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
112 * @return the undo stack
114 public Stack getUndoStack()
120 * Set the MenuManager object to be informed about changes
121 * @param inManager MenuManager object
123 public void setMenuManager(MenuManager inManager)
125 _menuManager = inManager;
130 * Open a file containing track or waypoint data
132 public void openFile()
134 if (_fileLoader == null)
135 _fileLoader = new FileLoader(this, _frame);
136 _fileLoader.openFile();
141 * Add a photo or a directory of photos which are already correlated
143 public void addPhotos()
145 if (_jpegLoader == null)
146 _jpegLoader = new JpegLoader(this, _frame);
147 _jpegLoader.openFile();
152 * Save the file in the selected format
154 public void saveFile()
158 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
159 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
163 if (_fileSaver == null) {
164 _fileSaver = new FileSaver(this, _frame, _track);
166 _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter());
172 * Export track data as Kml
174 public void exportKml()
178 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
179 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
184 if (_kmlExporter == null)
186 _kmlExporter = new KmlExporter(_frame, _trackInfo);
188 _kmlExporter.showDialog();
194 * Export track data as Gpx
196 public void exportGpx()
200 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
201 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
206 if (_gpxExporter == null)
208 _gpxExporter = new GpxExporter(_frame, _trackInfo);
210 _gpxExporter.showDialog();
216 * Export track data as Pov without specifying settings
218 public void exportPov()
220 exportPov(false, 0.0, 0.0, 0.0, 0);
224 * Export track data as Pov and also specify settings
225 * @param inX X component of unit vector
226 * @param inY Y component of unit vector
227 * @param inZ Z component of unit vector
228 * @param inAltitudeCap altitude cap
230 public void exportPov(double inX, double inY, double inZ, int inAltitudeCap)
232 exportPov(true, inX, inY, inZ, inAltitudeCap);
236 * Export track data as Pov with optional angle specification
237 * @param inDefineAngles true to define angles, false to ignore
238 * @param inX X component of unit vector
239 * @param inY Y component of unit vector
240 * @param inZ Z component of unit vector
241 * @param inAltitudeCap altitude cap
243 private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap)
245 // Check track has data to export
246 if (_track == null || _track.getNumPoints() <= 0)
248 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
249 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
253 // Make new exporter if necessary
254 if (_povExporter == null)
256 _povExporter = new PovExporter(_frame, _track);
258 // Specify angles if necessary
259 if (inDefineSettings)
261 _povExporter.setCameraCoordinates(inX, inY, inZ);
262 _povExporter.setAltitudeCap(inAltitudeCap);
265 _povExporter.showDialog();
271 * Exit the application if confirmed
277 _frame.requestFocus();
278 // check if ok to exit
279 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
280 if (!hasDataUnsaved()
281 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
282 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
283 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
284 == JOptionPane.YES_OPTION)
292 * Edit the currently selected point
294 public void editCurrentPoint()
298 DataPoint currentPoint = _trackInfo.getCurrentPoint();
299 if (currentPoint != null)
301 // Open point dialog to display details
302 PointEditor editor = new PointEditor(this, _frame);
303 editor.showDialog(_track, currentPoint);
310 * Complete the point edit
311 * @param inEditList field values to edit
312 * @param inUndoList field values before edit
314 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
316 DataPoint currentPoint = _trackInfo.getCurrentPoint();
317 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
319 // add information to undo stack
320 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
321 // pass to track for completion
322 if (_track.editPoint(currentPoint, inEditList))
324 _undoStack.push(undo);
331 * Edit the name of the currently selected (way)point
333 public void editCurrentPointName()
337 DataPoint currentPoint = _trackInfo.getCurrentPoint();
338 if (currentPoint != null)
340 // Open point dialog to display details
341 PointNameEditor editor = new PointNameEditor(this, _frame);
342 editor.showDialog(currentPoint);
349 * Delete the currently selected point
351 public void deleteCurrentPoint()
355 DataPoint currentPoint = _trackInfo.getCurrentPoint();
356 if (currentPoint != null)
358 boolean deletePhoto = false;
359 Photo currentPhoto = currentPoint.getPhoto();
360 if (currentPhoto != null)
362 // Confirm deletion of photo or decoupling
363 int response = JOptionPane.showConfirmDialog(_frame,
364 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getFile().getName(),
365 I18nManager.getText("dialog.deletepoint.title"),
366 JOptionPane.YES_NO_CANCEL_OPTION);
367 if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
369 // cancel pressed- abort delete
372 if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
374 // add information to undo stack
375 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
376 int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
377 // Undo object needs to know index of photo in list (if any) to restore
378 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex);
379 // call track to delete point
380 if (_trackInfo.deletePoint())
382 _undoStack.push(undo);
383 if (currentPhoto != null)
385 // delete photo if necessary
388 _trackInfo.getPhotoList().deletePhoto(photoIndex);
392 // decouple photo from point
393 currentPhoto.setDataPoint(null);
403 * Delete the currently selected range
405 public void deleteSelectedRange()
409 // Find out if photos should be deleted or not
410 int selStart = _trackInfo.getSelection().getStart();
411 int selEnd = _trackInfo.getSelection().getEnd();
412 if (selStart >= 0 && selEnd >= selStart)
414 int numToDelete = selEnd - selStart + 1;
415 boolean[] deletePhotos = new boolean[numToDelete];
416 Photo[] photosToDelete = new Photo[numToDelete];
417 boolean deleteAll = false;
418 boolean deleteNone = false;
419 String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
420 I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"),
421 I18nManager.getText("button.cancel")};
422 DataPoint point = null;
423 for (int i=0; i<numToDelete; i++)
425 point = _trackInfo.getTrack().getPoint(i + selStart);
426 if (point != null && point.getPhoto() != null)
430 deletePhotos[i] = true;
431 photosToDelete[i] = point.getPhoto();
433 else if (deleteNone) {deletePhotos[i] = false;}
436 int response = JOptionPane.showOptionDialog(_frame,
437 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getFile().getName(),
438 I18nManager.getText("dialog.deletepoint.title"),
439 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
440 questionOptions, questionOptions[1]);
441 // check for cancel or close
442 if (response == 4 || response == -1) {return;}
443 // check for yes or yes to all
444 if (response == 0 || response == 2)
446 deletePhotos[i] = true;
447 photosToDelete[i] = point.getPhoto();
448 if (response == 2) {deleteAll = true;}
450 // check for no to all
451 if (response == 3) {deleteNone = true;}
455 // add information to undo stack
456 UndoOperation undo = new UndoDeleteRange(_trackInfo);
457 // delete requested photos
458 for (int i=0; i<numToDelete; i++)
460 point = _trackInfo.getTrack().getPoint(i + selStart);
461 if (point != null && point.getPhoto() != null)
465 // delete photo from list
466 _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
470 // decouple from point
471 point.getPhoto().setDataPoint(null);
475 // call track to delete range
476 if (_trackInfo.deleteRange())
478 _undoStack.push(undo);
486 * Delete all the duplicate points in the track
488 public void deleteDuplicates()
492 // Save undo information
493 UndoOperation undo = new UndoDeleteDuplicates(_track);
494 // tell track to do it
495 int numDeleted = _trackInfo.deleteDuplicates();
498 _undoStack.add(undo);
499 String message = null;
502 message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text");
506 message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text");
508 JOptionPane.showMessageDialog(_frame, message,
509 I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
513 JOptionPane.showMessageDialog(_frame,
514 I18nManager.getText("dialog.deleteduplicates.nonefound"),
515 I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
524 public void compressTrack()
526 UndoCompress undo = new UndoCompress(_track);
527 // Get compression parameter
528 Object compParam = JOptionPane.showInputDialog(_frame,
529 I18nManager.getText("dialog.compresstrack.parameter.text"),
530 I18nManager.getText("dialog.compresstrack.title"),
531 JOptionPane.QUESTION_MESSAGE, null, null, "100");
532 int compNumber = parseNumber(compParam);
533 if (compNumber <= 0) return;
534 // call track to do compress
535 int numPointsDeleted = _trackInfo.compress(compNumber);
536 // add to undo stack if successful
537 if (numPointsDeleted > 0)
539 undo.setNumPointsDeleted(numPointsDeleted);
540 _undoStack.add(undo);
541 JOptionPane.showMessageDialog(_frame,
542 I18nManager.getText("dialog.compresstrack.text") + " - "
543 + numPointsDeleted + " "
544 + (numPointsDeleted==1?I18nManager.getText("dialog.compresstrack.single.text"):I18nManager.getText("dialog.compresstrack.multi.text")),
545 I18nManager.getText("dialog.compresstrack.title"), JOptionPane.INFORMATION_MESSAGE);
549 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.compresstrack.nonefound"),
550 I18nManager.getText("dialog.compresstrack.title"), JOptionPane.WARNING_MESSAGE);
556 * Reverse a section of the track
558 public void reverseRange()
560 // check whether Timestamp field exists, and if so confirm reversal
561 int selStart = _trackInfo.getSelection().getStart();
562 int selEnd = _trackInfo.getSelection().getEnd();
563 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
564 || _reversePointsConfirmed
565 || (JOptionPane.showConfirmDialog(_frame,
566 I18nManager.getText("dialog.confirmreversetrack.text"),
567 I18nManager.getText("dialog.confirmreversetrack.title"),
568 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true)))
570 UndoReverseSection undo = new UndoReverseSection(selStart, selEnd);
571 // call track to reverse range
572 if (_track.reverseRange(selStart, selEnd))
574 _undoStack.add(undo);
581 * Interpolate the two selected points
583 public void interpolateSelection()
585 // Get number of points to add
586 Object numPointsStr = JOptionPane.showInputDialog(_frame,
587 I18nManager.getText("dialog.interpolate.parameter.text"),
588 I18nManager.getText("dialog.interpolate.title"),
589 JOptionPane.QUESTION_MESSAGE, null, null, "");
590 int numPoints = parseNumber(numPointsStr);
591 if (numPoints <= 0) return;
593 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
595 // call track to interpolate
596 if (_trackInfo.interpolate(numPoints))
598 _undoStack.add(undo);
604 * Rearrange the waypoints into track order
605 * @param inFunction nearest point, all to end or all to start
607 public void rearrangeWaypoints(int inFunction)
609 UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(_track);
610 boolean success = false;
611 if (inFunction == REARRANGE_TO_START || inFunction == REARRANGE_TO_END)
613 // Collect the waypoints to the start or end of the track
614 success = _track.collectWaypoints(inFunction == REARRANGE_TO_START);
618 // Interleave the waypoints into track order
619 success = _track.interleaveWaypoints();
623 _undoStack.add(undo);
627 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"),
628 I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);
634 * Open a new window with the 3d view
636 public void show3dWindow()
638 ThreeDWindow window = WindowFactory.getWindow(this, _frame);
641 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"),
642 I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE);
648 // Pass the track object and show the window
649 window.setTrack(_track);
652 catch (ThreeDException e)
654 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(),
655 I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE);
664 public void selectAll()
666 _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1);
672 public void selectNone()
674 // deselect point, range and photo
675 _trackInfo.getSelection().clearAll();
680 * Receive loaded data and optionally merge with current Track
681 * @param inFieldArray array of fields
682 * @param inDataArray array of data
683 * @param inAltFormat altitude format
684 * @param inFilename filename used
686 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
688 // Check whether loaded array can be properly parsed into a Track
689 Track loadedTrack = new Track(_broker);
690 loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
691 if (loadedTrack.getNumPoints() <= 0)
693 JOptionPane.showMessageDialog(_frame,
694 I18nManager.getText("error.load.nopoints"),
695 I18nManager.getText("error.load.dialogtitle"),
696 JOptionPane.ERROR_MESSAGE);
699 // Decide whether to load or append
700 if (_track != null && _track.getNumPoints() > 0)
702 // ask whether to replace or append
703 int answer = JOptionPane.showConfirmDialog(_frame,
704 I18nManager.getText("dialog.openappend.text"),
705 I18nManager.getText("dialog.openappend.title"),
706 JOptionPane.YES_NO_CANCEL_OPTION);
707 if (answer == JOptionPane.YES_OPTION)
709 // append data to current Track
710 _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints()));
711 _track.combine(loadedTrack);
712 // set filename if currently empty
713 if (_trackInfo.getFileInfo().getNumFiles() == 0)
715 _trackInfo.getFileInfo().setFile(inFilename);
719 _trackInfo.getFileInfo().addFile();
722 else if (answer == JOptionPane.NO_OPTION)
724 // Don't append, replace data
725 PhotoList photos = null;
726 if (_trackInfo.getPhotoList().hasCorrelatedPhotos())
728 photos = _trackInfo.getPhotoList().cloneList();
730 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos));
731 _lastSavePosition = _undoStack.size();
732 // TODO: Should be possible to reuse the Track object already loaded?
733 _trackInfo.selectPoint(null);
734 _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
735 _trackInfo.getFileInfo().setFile(inFilename);
738 _trackInfo.getPhotoList().removeCorrelatedPhotos();
744 // currently no data held, so use received data
745 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null));
746 _lastSavePosition = _undoStack.size();
747 _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
748 _trackInfo.getFileInfo().setFile(inFilename);
750 _broker.informSubscribers();
752 _menuManager.informFileLoaded();
757 * Accept a list of loaded photos
758 * @param inPhotoSet Set of Photo objects
760 public void informPhotosLoaded(Set inPhotoSet)
762 if (inPhotoSet != null && !inPhotoSet.isEmpty())
764 int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
765 int numPhotosAdded = numsAdded[0];
766 int numPointsAdded = numsAdded[1];
767 if (numPhotosAdded > 0)
769 // Save numbers so load can be undone
770 _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
772 if (numPhotosAdded == 1)
774 JOptionPane.showMessageDialog(_frame,
775 "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
776 I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
780 JOptionPane.showMessageDialog(_frame,
781 "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
782 I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
784 // TODO: Improve message when photo(s) fail to load (eg already added)
785 _broker.informSubscribers();
787 _menuManager.informFileLoaded();
793 * Connect the current photo to the current point
795 public void connectPhotoToPoint()
797 Photo photo = _trackInfo.getCurrentPhoto();
798 DataPoint point = _trackInfo.getCurrentPoint();
799 if (photo != null && point != null && point.getPhoto() == null)
802 _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
803 photo.setDataPoint(point);
804 point.setPhoto(photo);
805 _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
811 * Disconnect the current photo from its point
813 public void disconnectPhotoFromPoint()
815 Photo photo = _trackInfo.getCurrentPhoto();
816 if (photo != null && photo.getDataPoint() != null)
818 DataPoint point = photo.getDataPoint();
819 _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName()));
821 photo.setDataPoint(null);
822 point.setPhoto(null);
823 _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
829 * Remove the current photo, if any
831 public void deleteCurrentPhoto()
833 // Delete the current photo, and optionally its point too, keeping undo information
834 Photo currentPhoto = _trackInfo.getCurrentPhoto();
835 if (currentPhoto != null)
837 // Photo is selected, see if it has a point or not
838 boolean photoDeleted = false;
839 UndoDeletePhoto undoAction = null;
840 if (currentPhoto.getDataPoint() == null)
842 // no point attached, so just delete photo
843 undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
845 photoDeleted = _trackInfo.deleteCurrentPhoto(false);
849 // point is attached, so need to confirm point deletion
850 undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
851 currentPhoto.getDataPoint(), _trackInfo.getTrack().getPointIndex(currentPhoto.getDataPoint()));
852 int response = JOptionPane.showConfirmDialog(_frame,
853 I18nManager.getText("dialog.deletephoto.deletepoint"),
854 I18nManager.getText("dialog.deletephoto.title"),
855 JOptionPane.YES_NO_CANCEL_OPTION);
856 boolean deletePointToo = (response == JOptionPane.YES_OPTION);
857 // Cancel delete if cancel pressed or dialog closed
858 if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION)
860 photoDeleted = _trackInfo.deleteCurrentPhoto(deletePointToo);
863 // Add undo information to stack if necessary
866 _undoStack.add(undoAction);
873 * Begin the photo correlation process by invoking dialog
875 public void beginCorrelatePhotos()
877 PhotoCorrelator correlator = new PhotoCorrelator(this, _frame);
878 // TODO: Do we need to keep a reference to this object to reuse it later?
884 * Finish the photo correlation process
885 * @param inPointPairs array of PointPair objects describing operation
887 public void finishCorrelatePhotos(PointPair[] inPointPairs)
889 // TODO: This method is too big for App, but where should it go?
890 if (inPointPairs != null && inPointPairs.length > 0)
892 // begin to construct undo information
893 UndoCorrelatePhotos undo = new UndoCorrelatePhotos(_trackInfo);
895 int arraySize = inPointPairs.length;
896 int i = 0, numPhotos = 0;
897 int numPointsToCreate = 0;
898 PointPair pair = null;
899 for (i=0; i<arraySize; i++)
901 pair = inPointPairs[i];
902 if (pair != null && pair.isValid())
904 if (pair.getMinSeconds() == 0L)
907 Photo pointPhoto = pair.getPointBefore().getPhoto();
908 if (pointPhoto == null)
910 // photo coincides with photoless point so connect the two
911 pair.getPointBefore().setPhoto(pair.getPhoto());
912 pair.getPhoto().setDataPoint(pair.getPointBefore());
914 else if (pointPhoto.equals(pair.getPhoto()))
916 // photo is already connected, nothing to do
920 // point is already connected to a different photo, so need to clone point
926 // photo time falls between two points, so need to interpolate new one
932 // Second loop, to create points if necessary
933 if (numPointsToCreate > 0)
935 // make new array for added points
936 DataPoint[] addedPoints = new DataPoint[numPointsToCreate];
938 DataPoint pointToAdd = null;
939 for (i=0; i<arraySize; i++)
941 pair = inPointPairs[i];
942 if (pair != null && pair.isValid())
945 if (pair.getMinSeconds() == 0L && pair.getPointBefore().getPhoto() != null
946 && !pair.getPointBefore().getPhoto().equals(pair.getPhoto()))
949 pointToAdd = pair.getPointBefore().clonePoint();
951 else if (pair.getMinSeconds() > 0L)
954 pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction());
956 if (pointToAdd != null)
958 // link photo to point
959 pointToAdd.setPhoto(pair.getPhoto());
960 pair.getPhoto().setDataPoint(pointToAdd);
961 // add to point array
962 addedPoints[pointNum] = pointToAdd;
968 _track.appendPoints(addedPoints);
970 // add undo information to stack
971 undo.setNumPhotosCorrelated(numPhotos);
972 _undoStack.add(undo);
973 // confirm correlation
974 JOptionPane.showMessageDialog(_frame, "" + numPhotos + " "
975 + (numPhotos==1?I18nManager.getText("dialog.correlate.confirmsingle.text"):I18nManager.getText("dialog.correlate.confirmmultiple.text")),
976 I18nManager.getText("dialog.correlate.title"),
977 JOptionPane.INFORMATION_MESSAGE);
978 // observers already informed by track update
984 * Save the coordinates of photos in their exif data
986 public void saveExif()
988 ExifSaver saver = new ExifSaver(_frame);
989 saver.saveExifInformation(_trackInfo.getPhotoList());
994 * Inform the app that the data has been saved
996 public void informDataSaved()
998 _lastSavePosition = _undoStack.size();
1003 * Begin undo process
1005 public void beginUndo()
1007 if (_undoStack.isEmpty())
1009 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
1010 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
1014 new UndoManager(this, _frame);
1020 * Clear the undo stack (losing all undo information
1022 public void clearUndo()
1024 // Exit if nothing to undo
1025 if (_undoStack == null || _undoStack.isEmpty())
1027 // Has track got unsaved data?
1028 boolean unsaved = hasDataUnsaved();
1029 // Confirm operation with dialog
1030 int answer = JOptionPane.showConfirmDialog(_frame,
1031 I18nManager.getText("dialog.clearundo.text"),
1032 I18nManager.getText("dialog.clearundo.title"),
1033 JOptionPane.YES_NO_OPTION);
1034 if (answer == JOptionPane.YES_OPTION)
1037 _lastSavePosition = 0;
1038 if (unsaved) _lastSavePosition = -1;
1039 _broker.informSubscribers();
1045 * Undo the specified number of actions
1046 * @param inNumUndos number of actions to undo
1048 public void undoActions(int inNumUndos)
1052 for (int i=0; i<inNumUndos; i++)
1054 ((UndoOperation) _undoStack.pop()).performUndo(_trackInfo);
1056 JOptionPane.showMessageDialog(_frame, "" + inNumUndos + " "
1057 + (inNumUndos==1?I18nManager.getText("dialog.confirmundo.single.text"):I18nManager.getText("dialog.confirmundo.multiple.text")),
1058 I18nManager.getText("dialog.confirmundo.title"),
1059 JOptionPane.INFORMATION_MESSAGE);
1061 catch (UndoException ue)
1063 JOptionPane.showMessageDialog(_frame,
1064 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage(),
1065 I18nManager.getText("error.undofailed.title"),
1066 JOptionPane.ERROR_MESSAGE);
1068 _broker.informSubscribers();
1070 catch (EmptyStackException empty) {}
1075 * Helper method to parse an Object into an integer
1076 * @param inObject object, eg from dialog
1077 * @return int value given
1079 private static int parseNumber(Object inObject)
1082 if (inObject != null)
1086 num = Integer.parseInt(inObject.toString());
1088 catch (NumberFormatException nfe)
1095 * Show a brief help message
1097 public void showHelp()
1099 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.help.help"),
1100 I18nManager.getText("menu.help"),
1101 JOptionPane.INFORMATION_MESSAGE);