3 import java.util.EmptyStackException;
5 import java.util.Stack;
7 import javax.swing.JFrame;
8 import javax.swing.JOptionPane;
10 import tim.prune.data.Altitude;
11 import tim.prune.data.Coordinate;
12 import tim.prune.data.DataPoint;
13 import tim.prune.data.Field;
14 import tim.prune.data.LatLonRectangle;
15 import tim.prune.data.Latitude;
16 import tim.prune.data.Longitude;
17 import tim.prune.data.Photo;
18 import tim.prune.data.PhotoList;
19 import tim.prune.data.Track;
20 import tim.prune.data.TrackInfo;
21 import tim.prune.function.browser.BrowserLauncher;
22 import tim.prune.function.browser.UrlGenerator;
23 import tim.prune.function.edit.FieldEditList;
24 import tim.prune.function.edit.PointEditor;
25 import tim.prune.function.edit.PointNameEditor;
26 import tim.prune.gui.MenuManager;
27 import tim.prune.gui.UndoManager;
28 import tim.prune.load.FileLoader;
29 import tim.prune.load.JpegLoader;
30 import tim.prune.save.ExifSaver;
31 import tim.prune.save.FileSaver;
32 import tim.prune.undo.UndoAddTimeOffset;
33 import tim.prune.undo.UndoCompress;
34 import tim.prune.undo.UndoConnectPhoto;
35 import tim.prune.undo.UndoConnectPhotoWithClone;
36 import tim.prune.undo.UndoCreatePoint;
37 import tim.prune.undo.UndoCutAndMove;
38 import tim.prune.undo.UndoDeletePhoto;
39 import tim.prune.undo.UndoDeletePoint;
40 import tim.prune.undo.UndoDeleteRange;
41 import tim.prune.undo.UndoDisconnectPhoto;
42 import tim.prune.undo.UndoEditPoint;
43 import tim.prune.undo.UndoException;
44 import tim.prune.undo.UndoInsert;
45 import tim.prune.undo.UndoLoad;
46 import tim.prune.undo.UndoLoadPhotos;
47 import tim.prune.undo.UndoMergeTrackSegments;
48 import tim.prune.undo.UndoOperation;
49 import tim.prune.undo.UndoReverseSection;
53 * Main controller for the application
58 private JFrame _frame = null;
59 private Track _track = null;
60 private TrackInfo _trackInfo = null;
61 private int _lastSavePosition = 0;
62 private MenuManager _menuManager = null;
63 private FileLoader _fileLoader = null;
64 private JpegLoader _jpegLoader = null;
65 private FileSaver _fileSaver = null;
66 private Stack<UndoOperation> _undoStack = null;
67 private boolean _mangleTimestampsConfirmed = false;
71 * @param inFrame frame object for application
73 public App(JFrame inFrame)
76 _undoStack = new Stack<UndoOperation>();
78 _trackInfo = new TrackInfo(_track);
79 FunctionLibrary.initialise(this);
84 * @return the current TrackInfo
86 public TrackInfo getTrackInfo()
92 * @return the dialog frame
94 public JFrame getFrame()
100 * Check if the application has unsaved data
101 * @return true if data is unsaved, false otherwise
103 public boolean hasDataUnsaved()
105 return (_undoStack.size() > _lastSavePosition
106 && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
110 * @return the undo stack
112 public Stack<UndoOperation> getUndoStack()
118 * Complete a function execution
119 * @param inUndo undo object to be added to stack
120 * @param inConfirmText confirmation text
122 public void completeFunction(UndoOperation inUndo, String inConfirmText)
124 _undoStack.add(inUndo);
125 UpdateMessageBroker.informSubscribers(inConfirmText);
129 * Set the MenuManager object to be informed about changes
130 * @param inManager MenuManager object
132 public void setMenuManager(MenuManager inManager)
134 _menuManager = inManager;
139 * Open a file containing track or waypoint data
141 public void openFile()
143 if (_fileLoader == null)
144 _fileLoader = new FileLoader(this, _frame);
145 _fileLoader.openFile();
150 * Add a photo or a directory of photos
152 public void addPhotos()
154 if (_jpegLoader == null)
155 _jpegLoader = new JpegLoader(this, _frame);
156 _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
160 * Save the file in the selected format
162 public void saveFile()
164 if (_track == null) {
165 showErrorMessage("error.save.dialogtitle", "error.save.nodata");
169 if (_fileSaver == null) {
170 _fileSaver = new FileSaver(this, _frame, _track);
173 if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
174 _fileSaver.showDialog(delim);
180 * Exit the application if confirmed
186 _frame.requestFocus();
187 // check if ok to exit
188 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
189 if (!hasDataUnsaved()
190 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
191 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
192 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
193 == JOptionPane.YES_OPTION)
201 * Edit the currently selected point
203 public void editCurrentPoint()
207 DataPoint currentPoint = _trackInfo.getCurrentPoint();
208 if (currentPoint != null)
210 // Open point dialog to display details
211 PointEditor editor = new PointEditor(this, _frame);
212 editor.showDialog(_track, currentPoint);
219 * Complete the point edit
220 * @param inEditList field values to edit
221 * @param inUndoList field values before edit
223 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
225 DataPoint currentPoint = _trackInfo.getCurrentPoint();
226 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
228 // add information to undo stack
229 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
230 // pass to track for completion
231 if (_track.editPoint(currentPoint, inEditList))
233 _undoStack.push(undo);
234 // Confirm point edit
235 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
242 * Edit the name of the currently selected (way)point
244 public void editCurrentPointName()
248 DataPoint currentPoint = _trackInfo.getCurrentPoint();
249 if (currentPoint != null)
251 // Open point dialog to display details
252 PointNameEditor editor = new PointNameEditor(this, _frame);
253 editor.showDialog(currentPoint);
260 * Delete the currently selected point
262 public void deleteCurrentPoint()
264 if (_track == null) {return;}
265 DataPoint currentPoint = _trackInfo.getCurrentPoint();
266 if (currentPoint != null)
268 boolean deletePhoto = false;
269 Photo currentPhoto = currentPoint.getPhoto();
270 if (currentPhoto != null)
272 // Confirm deletion of photo or decoupling
273 int response = JOptionPane.showConfirmDialog(_frame,
274 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getFile().getName(),
275 I18nManager.getText("dialog.deletepoint.title"),
276 JOptionPane.YES_NO_CANCEL_OPTION);
277 if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
279 // cancel pressed- abort delete
282 if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
284 // store necessary information to undo it later
285 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
286 int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
287 DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
288 // Construct Undo object
289 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
290 nextTrackPoint != null && nextTrackPoint.getSegmentStart());
291 // call track to delete point
292 if (_trackInfo.deletePoint())
294 // Delete was successful so add undo info to stack
295 _undoStack.push(undo);
296 if (currentPhoto != null)
298 // delete photo if necessary
301 _trackInfo.getPhotoList().deletePhoto(photoIndex);
305 // decouple photo from point
306 currentPhoto.setDataPoint(null);
310 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
317 * Delete the currently selected range
319 public void deleteSelectedRange()
323 // Find out if photos should be deleted or not
324 int selStart = _trackInfo.getSelection().getStart();
325 int selEnd = _trackInfo.getSelection().getEnd();
326 if (selStart >= 0 && selEnd >= selStart)
328 int numToDelete = selEnd - selStart + 1;
329 boolean[] deletePhotos = new boolean[numToDelete];
330 Photo[] photosToDelete = new Photo[numToDelete];
331 boolean deleteAll = false;
332 boolean deleteNone = false;
333 String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
334 I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"),
335 I18nManager.getText("button.cancel")};
336 DataPoint point = null;
337 for (int i=0; i<numToDelete; i++)
339 point = _trackInfo.getTrack().getPoint(i + selStart);
340 if (point != null && point.getPhoto() != null)
344 deletePhotos[i] = true;
345 photosToDelete[i] = point.getPhoto();
347 else if (deleteNone) {deletePhotos[i] = false;}
350 int response = JOptionPane.showOptionDialog(_frame,
351 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getFile().getName(),
352 I18nManager.getText("dialog.deletepoint.title"),
353 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
354 questionOptions, questionOptions[1]);
355 // check for cancel or close
356 if (response == 4 || response == -1) {return;}
357 // check for yes or yes to all
358 if (response == 0 || response == 2)
360 deletePhotos[i] = true;
361 photosToDelete[i] = point.getPhoto();
362 if (response == 2) {deleteAll = true;}
364 // check for no to all
365 if (response == 3) {deleteNone = true;}
369 // add information to undo stack
370 UndoDeleteRange undo = new UndoDeleteRange(_trackInfo);
371 // delete requested photos
372 for (int i=0; i<numToDelete; i++)
374 point = _trackInfo.getTrack().getPoint(i + selStart);
375 if (point != null && point.getPhoto() != null)
379 // delete photo from list
380 _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
384 // decouple from point
385 point.getPhoto().setDataPoint(null);
389 // call track to delete range
390 if (_trackInfo.deleteRange())
392 _undoStack.push(undo);
394 UpdateMessageBroker.informSubscribers("" + numToDelete + " "
395 + I18nManager.getText("confirm.deletepoint.multi"));
403 * Finish the compression by deleting the marked points
405 public void finishCompressTrack()
407 UndoCompress undo = new UndoCompress(_track);
408 // call track to do compress
409 int numPointsDeleted = _trackInfo.deleteMarkedPoints();
410 // add to undo stack if successful
411 if (numPointsDeleted > 0)
413 undo.setNumPointsDeleted(numPointsDeleted);
414 _undoStack.add(undo);
415 UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " "
416 + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi")));
419 showErrorMessage("function.compress", "dialog.compress.nonefound");
424 * Reverse the currently selected section of the track
426 public void reverseRange()
428 // check whether Timestamp field exists, and if so confirm reversal
429 int selStart = _trackInfo.getSelection().getStart();
430 int selEnd = _trackInfo.getSelection().getEnd();
431 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
432 || _mangleTimestampsConfirmed
433 || (JOptionPane.showConfirmDialog(_frame,
434 I18nManager.getText("dialog.confirmreversetrack.text"),
435 I18nManager.getText("dialog.confirmreversetrack.title"),
436 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
438 UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
439 // call track to reverse range
440 if (_track.reverseRange(selStart, selEnd))
442 _undoStack.add(undo);
444 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
450 * Complete the add time offset function with the specified offset
451 * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
453 public void finishAddTimeOffset(long inTimeOffset)
455 // Construct undo information
456 int selStart = _trackInfo.getSelection().getStart();
457 int selEnd = _trackInfo.getSelection().getEnd();
458 UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
459 if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset))
461 _undoStack.add(undo);
462 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
463 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
469 * Merge the track segments within the current selection
471 public void mergeTrackSegments()
473 if (_trackInfo.getSelection().hasRangeSelected())
475 // Maybe could check segment start flags to see if it's worth merging
476 // If first track point is already start and no other seg starts then do nothing
478 int selStart = _trackInfo.getSelection().getStart();
479 int selEnd = _trackInfo.getSelection().getEnd();
481 UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
482 // Call track to merge segments
483 if (_track.mergeTrackSegments(selStart, selEnd)) {
484 _undoStack.add(undo);
485 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
492 * Interpolate the two selected points
494 public void interpolateSelection()
496 // Get number of points to add
497 Object numPointsStr = JOptionPane.showInputDialog(_frame,
498 I18nManager.getText("dialog.interpolate.parameter.text"),
499 I18nManager.getText("dialog.interpolate.title"),
500 JOptionPane.QUESTION_MESSAGE, null, null, "");
501 int numPoints = parseNumber(numPointsStr);
502 if (numPoints <= 0) return;
504 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
506 // call track to interpolate
507 if (_trackInfo.interpolate(numPoints))
509 _undoStack.add(undo);
515 * Average the selected points
517 public void averageSelection()
519 // Find following track point
520 DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
521 boolean segFlag = false;
522 if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
523 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
524 // call track info object to do the averaging
525 if (_trackInfo.average())
527 _undoStack.add(undo);
533 * Create a new point at the given lat/long coordinates
534 * @param inLat latitude
535 * @param inLong longitude
537 public void createPoint(double inLat, double inLong)
539 // create undo object
540 UndoCreatePoint undo = new UndoCreatePoint();
541 // create point and add to track
542 DataPoint point = new DataPoint(new Latitude(inLat, Coordinate.FORMAT_NONE), new Longitude(inLong, Coordinate.FORMAT_NONE), null);
543 point.setSegmentStart(true);
544 _track.appendPoints(new DataPoint[] {point});
545 _trackInfo.getSelection().selectPoint(_trackInfo.getTrack().getNumPoints()-1);
546 // add undo object to stack
547 _undoStack.add(undo);
549 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
554 * Cut the current selection and move it to before the currently selected point
556 public void cutAndMoveSelection()
558 int startIndex = _trackInfo.getSelection().getStart();
559 int endIndex = _trackInfo.getSelection().getEnd();
560 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
561 // If timestamps would be mangled by cut/move, confirm
562 if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
563 || _mangleTimestampsConfirmed
564 || (JOptionPane.showConfirmDialog(_frame,
565 I18nManager.getText("dialog.confirmcutandmove.text"),
566 I18nManager.getText("dialog.confirmcutandmove.title"),
567 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
569 // Find points to set segment flags
570 DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
571 DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
572 DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
574 UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
575 // Call track info to move track section
576 if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
578 // Set segment start flags (first track point, next track point, move to point)
579 if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
580 if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
581 if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
583 // Add undo object to stack, set confirm message
584 _undoStack.add(undo);
585 _trackInfo.getSelection().deselectRange();
586 UpdateMessageBroker.informSubscribers();
587 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
596 public void selectAll()
598 _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1);
604 public void selectNone()
606 // deselect point, range and photo
607 _trackInfo.getSelection().clearAll();
608 _track.clearDeletionMarkers();
613 * Receive loaded data and optionally merge with current Track
614 * @param inFieldArray array of fields
615 * @param inDataArray array of data
616 * @param inAltFormat altitude format
617 * @param inFilename filename used
619 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, Altitude.Format inAltFormat,
622 // Check whether loaded array can be properly parsed into a Track
623 Track loadedTrack = new Track();
624 loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
625 if (loadedTrack.getNumPoints() <= 0)
627 showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
630 // Decide whether to load or append
631 if (_track.getNumPoints() > 0)
633 // ask whether to replace or append
634 int answer = JOptionPane.showConfirmDialog(_frame,
635 I18nManager.getText("dialog.openappend.text"),
636 I18nManager.getText("dialog.openappend.title"),
637 JOptionPane.YES_NO_CANCEL_OPTION);
638 if (answer == JOptionPane.YES_OPTION)
640 // append data to current Track
641 _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints()));
642 _track.combine(loadedTrack);
643 // set filename if currently empty
644 if (_trackInfo.getFileInfo().getNumFiles() == 0)
646 _trackInfo.getFileInfo().setFile(inFilename);
650 _trackInfo.getFileInfo().addFile();
653 else if (answer == JOptionPane.NO_OPTION)
655 // Don't append, replace data
656 PhotoList photos = null;
657 if (_trackInfo.getPhotoList().hasCorrelatedPhotos())
659 photos = _trackInfo.getPhotoList().cloneList();
661 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos));
662 _lastSavePosition = _undoStack.size();
663 _trackInfo.getSelection().clearAll();
664 _track.load(loadedTrack);
665 _trackInfo.getFileInfo().setFile(inFilename);
668 _trackInfo.getPhotoList().removeCorrelatedPhotos();
674 // Currently no data held, so transfer received data
675 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null));
676 _lastSavePosition = _undoStack.size();
677 _trackInfo.getSelection().clearAll();
678 _track.load(loadedTrack);
679 _trackInfo.getFileInfo().setFile(inFilename);
681 UpdateMessageBroker.informSubscribers();
683 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile") + " '" + inFilename + "'");
685 _menuManager.informFileLoaded();
690 * Accept a list of loaded photos
691 * @param inPhotoSet Set of Photo objects
693 public void informPhotosLoaded(Set<Photo> inPhotoSet)
695 if (inPhotoSet != null && !inPhotoSet.isEmpty())
697 int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
698 int numPhotosAdded = numsAdded[0];
699 int numPointsAdded = numsAdded[1];
700 if (numPhotosAdded > 0)
702 // Save numbers so load can be undone
703 _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
705 if (numPhotosAdded == 1)
707 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
711 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
713 // TODO: Improve message when photo(s) fail to load (eg already added)
714 UpdateMessageBroker.informSubscribers();
716 _menuManager.informFileLoaded();
722 * Connect the current photo to the current point
724 public void connectPhotoToPoint()
726 Photo photo = _trackInfo.getCurrentPhoto();
727 DataPoint point = _trackInfo.getCurrentPoint();
728 if (photo != null && point != null)
730 if (point.getPhoto() != null)
732 // point already has a photo, confirm cloning of new point
733 if (JOptionPane.showConfirmDialog(_frame,
734 I18nManager.getText("dialog.connectphoto.clonepoint"),
735 I18nManager.getText("dialog.connect.title"),
736 JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)
738 // Create undo, clone point and attach
739 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex() + 1;
740 // insert new point after current one
741 point = point.clonePoint();
742 UndoConnectPhotoWithClone undo = new UndoConnectPhotoWithClone(
743 point, photo.getFile().getName(), pointIndex);
744 _track.insertPoint(point, pointIndex);
745 photo.setDataPoint(point);
746 point.setPhoto(photo);
747 _undoStack.add(undo);
748 UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
749 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
754 // point doesn't currently have a photo, so just connect it
755 _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName()));
756 photo.setDataPoint(point);
757 point.setPhoto(photo);
758 UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
759 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect"));
766 * Disconnect the current photo from its point
768 public void disconnectPhotoFromPoint()
770 Photo photo = _trackInfo.getCurrentPhoto();
771 if (photo != null && photo.getDataPoint() != null)
773 DataPoint point = photo.getDataPoint();
774 _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName()));
776 photo.setDataPoint(null);
777 point.setPhoto(null);
778 UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
779 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.disconnect"));
785 * Remove the current photo, if any
787 public void deleteCurrentPhoto()
789 // Delete the current photo, and optionally its point too, keeping undo information
790 Photo currentPhoto = _trackInfo.getCurrentPhoto();
791 if (currentPhoto != null)
793 // Photo is selected, see if it has a point or not
794 boolean photoDeleted = false;
795 UndoDeletePhoto undoAction = null;
796 if (currentPhoto.getDataPoint() == null)
798 // no point attached, so just delete photo
799 undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
801 photoDeleted = _trackInfo.deleteCurrentPhoto(false);
805 // point is attached, so need to confirm point deletion
806 undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(),
807 currentPhoto.getDataPoint(), _trackInfo.getTrack().getPointIndex(currentPhoto.getDataPoint()));
808 int response = JOptionPane.showConfirmDialog(_frame,
809 I18nManager.getText("dialog.deletephoto.deletepoint"),
810 I18nManager.getText("dialog.deletephoto.title"),
811 JOptionPane.YES_NO_CANCEL_OPTION);
812 boolean deletePointToo = (response == JOptionPane.YES_OPTION);
813 // Cancel delete if cancel pressed or dialog closed
814 if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION)
816 photoDeleted = _trackInfo.deleteCurrentPhoto(deletePointToo);
819 // Add undo information to stack if necessary
822 _undoStack.add(undoAction);
829 * Save the coordinates of photos in their exif data
831 public void saveExif()
833 ExifSaver saver = new ExifSaver(_frame);
834 saver.saveExifInformation(_trackInfo.getPhotoList());
839 * Inform the app that the data has been saved
841 public void informDataSaved()
843 _lastSavePosition = _undoStack.size();
850 public void beginUndo()
852 if (_undoStack.isEmpty())
855 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
856 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
860 new UndoManager(this, _frame);
866 * Clear the undo stack (losing all undo information
868 public void clearUndo()
870 // Exit if nothing to undo
871 if (_undoStack == null || _undoStack.isEmpty())
873 // Has track got unsaved data?
874 boolean unsaved = hasDataUnsaved();
875 // Confirm operation with dialog
876 int answer = JOptionPane.showConfirmDialog(_frame,
877 I18nManager.getText("dialog.clearundo.text"),
878 I18nManager.getText("dialog.clearundo.title"),
879 JOptionPane.YES_NO_OPTION);
880 if (answer == JOptionPane.YES_OPTION)
883 _lastSavePosition = 0;
884 if (unsaved) _lastSavePosition = -1;
885 UpdateMessageBroker.informSubscribers();
891 * Undo the specified number of actions
892 * @param inNumUndos number of actions to undo
894 public void undoActions(int inNumUndos)
898 for (int i=0; i<inNumUndos; i++)
900 _undoStack.pop().performUndo(_trackInfo);
902 String message = "" + inNumUndos + " "
903 + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
904 UpdateMessageBroker.informSubscribers(message);
906 catch (UndoException ue)
908 showErrorMessageNoLookup("error.undofailed.title",
909 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
911 UpdateMessageBroker.informSubscribers();
913 catch (EmptyStackException empty) {}
918 * Helper method to parse an Object into an integer
919 * @param inObject object, eg from dialog
920 * @return int value given
922 private static int parseNumber(Object inObject)
925 if (inObject != null)
929 num = Integer.parseInt(inObject.toString());
931 catch (NumberFormatException nfe)
938 * Show a map url in an external browser
939 * @param inSourceIndex index of map source to use
941 public void showExternalMap(int inSourceIndex)
943 BrowserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
947 * Display a standard error message
948 * @param inTitleKey key to lookup for window title
949 * @param inMessageKey key to lookup for error message
951 public void showErrorMessage(String inTitleKey, String inMessageKey)
953 JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
954 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
958 * Display a standard error message
959 * @param inTitleKey key to lookup for window title
960 * @param inMessage error message
962 public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
964 JOptionPane.showMessageDialog(_frame, inMessage,
965 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);