4 import java.util.ArrayList;
5 import java.util.EmptyStackException;
7 import java.util.Stack;
9 import javax.swing.JFrame;
10 import javax.swing.JOptionPane;
12 import tim.prune.data.Altitude;
13 import tim.prune.data.AudioFile;
14 import tim.prune.data.Checker;
15 import tim.prune.data.DataPoint;
16 import tim.prune.data.Field;
17 import tim.prune.data.LatLonRectangle;
18 import tim.prune.data.MediaFile;
19 import tim.prune.data.NumberUtils;
20 import tim.prune.data.Photo;
21 import tim.prune.data.PhotoList;
22 import tim.prune.data.SourceInfo;
23 import tim.prune.data.Track;
24 import tim.prune.data.TrackInfo;
25 import tim.prune.function.SelectTracksFunction;
26 import tim.prune.function.browser.BrowserLauncher;
27 import tim.prune.function.browser.UrlGenerator;
28 import tim.prune.function.edit.FieldEditList;
29 import tim.prune.function.edit.PointEditor;
30 import tim.prune.gui.SidebarController;
31 import tim.prune.gui.MenuManager;
32 import tim.prune.gui.UndoManager;
33 import tim.prune.gui.Viewport;
34 import tim.prune.load.FileLoader;
35 import tim.prune.load.JpegLoader;
36 import tim.prune.load.MediaHelper;
37 import tim.prune.load.TrackNameList;
38 import tim.prune.save.ExifSaver;
39 import tim.prune.save.FileSaver;
40 import tim.prune.undo.*;
44 * Main controller for the application
49 private JFrame _frame = null;
50 private Track _track = null;
51 private TrackInfo _trackInfo = null;
52 private int _lastSavePosition = 0;
53 private MenuManager _menuManager = null;
54 private SidebarController _sidebarController = null;
55 private FileLoader _fileLoader = null;
56 private JpegLoader _jpegLoader = null;
57 private FileSaver _fileSaver = null;
58 private Stack<UndoOperation> _undoStack = null;
59 private boolean _mangleTimestampsConfirmed = false;
60 private Viewport _viewport = null;
61 private ArrayList<File> _dataFiles = null;
62 private boolean _firstDataFile = true;
67 * @param inFrame frame object for application
69 public App(JFrame inFrame)
72 _undoStack = new Stack<UndoOperation>();
74 _trackInfo = new TrackInfo(_track);
75 FunctionLibrary.initialise(this);
80 * @return the current TrackInfo
82 public TrackInfo getTrackInfo()
88 * @return the dialog frame
90 public JFrame getFrame()
96 * Check if the application has unsaved data
97 * @return true if data is unsaved, false otherwise
99 public boolean hasDataUnsaved()
101 return (_undoStack.size() > _lastSavePosition
102 && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
106 * @return the undo stack
108 public Stack<UndoOperation> getUndoStack()
114 * Load the specified data files one by one
115 * @param inDataFiles arraylist containing File objects to load
117 public void loadDataFiles(ArrayList<File> inDataFiles)
119 if (inDataFiles == null || inDataFiles.size() == 0) {
123 _dataFiles = inDataFiles;
124 File f = _dataFiles.get(0);
125 _dataFiles.remove(0);
126 // Start load of specified file
127 if (_fileLoader == null)
128 _fileLoader = new FileLoader(this, _frame);
129 _firstDataFile = true;
130 _fileLoader.openFile(f);
135 * Complete a function execution
136 * @param inUndo undo object to be added to stack
137 * @param inConfirmText confirmation text
139 public void completeFunction(UndoOperation inUndo, String inConfirmText)
141 _undoStack.add(inUndo);
142 UpdateMessageBroker.informSubscribers(inConfirmText);
146 * Set the MenuManager object to be informed about changes
147 * @param inManager MenuManager object
149 public void setMenuManager(MenuManager inManager)
151 _menuManager = inManager;
156 * Open a file containing track or waypoint data
158 public void openFile()
160 if (_fileLoader == null)
161 _fileLoader = new FileLoader(this, _frame);
162 _fileLoader.openFile();
167 * Add a photo or a directory of photos
169 public void addPhotos()
171 if (_jpegLoader == null)
172 _jpegLoader = new JpegLoader(this, _frame);
173 _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
177 * Save the file in the selected format
179 public void saveFile()
181 if (_track == null) {
182 showErrorMessage("error.save.dialogtitle", "error.save.nodata");
186 if (_fileSaver == null) {
187 _fileSaver = new FileSaver(this, _frame);
190 if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
191 _fileSaver.showDialog(delim);
197 * Exit the application if confirmed
203 _frame.requestFocus();
204 // check if ok to exit
205 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
206 if (!hasDataUnsaved()
207 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
208 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
209 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
210 == JOptionPane.YES_OPTION)
218 * Edit the currently selected point
220 public void editCurrentPoint()
224 DataPoint currentPoint = _trackInfo.getCurrentPoint();
225 if (currentPoint != null)
227 // Open point dialog to display details
228 PointEditor editor = new PointEditor(this, _frame);
229 editor.showDialog(_track, currentPoint);
236 * Complete the point edit
237 * @param inEditList field values to edit
238 * @param inUndoList field values before edit
240 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
242 DataPoint currentPoint = _trackInfo.getCurrentPoint();
243 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
245 // add information to undo stack
246 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
247 // pass to track for completion
248 if (_track.editPoint(currentPoint, inEditList, false))
250 _undoStack.push(undo);
251 // Confirm point edit
252 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
259 * Delete the currently selected point
261 public void deleteCurrentPoint()
263 if (_track == null) {return;}
264 DataPoint currentPoint = _trackInfo.getCurrentPoint();
265 if (currentPoint != null)
267 boolean deletePhoto = false;
268 Photo currentPhoto = currentPoint.getPhoto();
269 if (currentPhoto != null)
271 // Confirm deletion of photo or decoupling
272 int response = JOptionPane.showConfirmDialog(_frame,
273 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getFile().getName(),
274 I18nManager.getText("dialog.deletepoint.title"),
275 JOptionPane.YES_NO_CANCEL_OPTION);
276 if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
278 // cancel pressed- abort delete
281 if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
283 // store necessary information to undo it later
284 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
285 int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
286 DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
287 // Construct Undo object
288 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
289 nextTrackPoint != null && nextTrackPoint.getSegmentStart());
290 // call track to delete point
291 if (_trackInfo.deletePoint())
293 // Delete was successful so add undo info to stack
294 _undoStack.push(undo);
295 if (currentPhoto != null)
297 // delete photo if necessary
300 _trackInfo.getPhotoList().deletePhoto(photoIndex);
304 // decouple photo from point
305 currentPhoto.setDataPoint(null);
309 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
316 * Delete the currently selected range
318 public void deleteSelectedRange()
322 // Find out if photos should be deleted or not
323 int selStart = _trackInfo.getSelection().getStart();
324 int selEnd = _trackInfo.getSelection().getEnd();
325 if (selStart >= 0 && selEnd >= selStart)
327 int numToDelete = selEnd - selStart + 1;
328 boolean[] deletePhotos = new boolean[numToDelete];
329 Photo[] photosToDelete = new Photo[numToDelete];
330 boolean deleteAll = false;
331 boolean deleteNone = false;
332 String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
333 I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"),
334 I18nManager.getText("button.cancel")};
335 DataPoint point = null;
336 for (int i=0; i<numToDelete; i++)
338 point = _trackInfo.getTrack().getPoint(i + selStart);
339 if (point != null && point.getPhoto() != null)
343 deletePhotos[i] = true;
344 photosToDelete[i] = point.getPhoto();
346 else if (deleteNone) {deletePhotos[i] = false;}
349 int response = JOptionPane.showOptionDialog(_frame,
350 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getFile().getName(),
351 I18nManager.getText("dialog.deletepoint.title"),
352 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
353 questionOptions, questionOptions[1]);
354 // check for cancel or close
355 if (response == 4 || response == -1) {return;}
356 // check for yes or yes to all
357 if (response == 0 || response == 2)
359 deletePhotos[i] = true;
360 photosToDelete[i] = point.getPhoto();
361 if (response == 2) {deleteAll = true;}
363 // check for no to all
364 if (response == 3) {deleteNone = true;}
368 // add information to undo stack
369 UndoDeleteRange undo = new UndoDeleteRange(_trackInfo);
370 // delete requested photos
371 for (int i=0; i<numToDelete; i++)
373 point = _trackInfo.getTrack().getPoint(i + selStart);
374 if (point != null && point.getPhoto() != null)
378 // delete photo from list
379 _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
383 // decouple from point
384 point.getPhoto().setDataPoint(null);
388 // call track to delete range
389 if (_trackInfo.deleteRange())
391 _undoStack.push(undo);
393 UpdateMessageBroker.informSubscribers("" + numToDelete + " "
394 + I18nManager.getText("confirm.deletepoint.multi"));
402 * Finish the compression by deleting the marked points
404 public void finishCompressTrack()
406 UndoCompress undo = new UndoCompress(_track);
407 // call track to do compress
408 int numPointsDeleted = _trackInfo.deleteMarkedPoints();
409 // add to undo stack if successful
410 if (numPointsDeleted > 0)
412 undo.setNumPointsDeleted(numPointsDeleted);
413 _undoStack.add(undo);
414 UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " "
415 + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi")));
418 showErrorMessage("function.compress", "dialog.compress.nonefound");
423 * Reverse the currently selected section of the track
425 public void reverseRange()
427 // check whether Timestamp field exists, and if so confirm reversal
428 int selStart = _trackInfo.getSelection().getStart();
429 int selEnd = _trackInfo.getSelection().getEnd();
430 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
431 || _mangleTimestampsConfirmed
432 || (JOptionPane.showConfirmDialog(_frame,
433 I18nManager.getText("dialog.confirmreversetrack.text"),
434 I18nManager.getText("dialog.confirmreversetrack.title"),
435 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
437 UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
438 // call track to reverse range
439 if (_track.reverseRange(selStart, selEnd))
441 _undoStack.add(undo);
443 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
449 * Complete the add time offset function with the specified offset
450 * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
452 public void finishAddTimeOffset(long inTimeOffset)
454 // Construct undo information
455 int selStart = _trackInfo.getSelection().getStart();
456 int selEnd = _trackInfo.getSelection().getEnd();
457 UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
458 if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
460 _undoStack.add(undo);
461 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
462 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
468 * Complete the add altitude offset function with the specified offset
469 * @param inOffset altitude offset to add as String
470 * @param inFormat altitude format of offset (eg Feet, Metres)
472 public void finishAddAltitudeOffset(String inOffset, Altitude.Format inFormat)
475 if (inOffset == null || inOffset.equals("") || inFormat==Altitude.Format.NO_FORMAT) {
478 // Construct undo information
479 UndoAddAltitudeOffset undo = new UndoAddAltitudeOffset(_trackInfo);
480 int selStart = _trackInfo.getSelection().getStart();
481 int selEnd = _trackInfo.getSelection().getEnd();
482 // How many decimal places are given in the offset?
483 int numDecimals = NumberUtils.getDecimalPlaces(inOffset);
484 boolean success = false;
485 // Decimal offset given
487 double offsetd = Double.parseDouble(inOffset);
488 success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inFormat, numDecimals);
490 catch (NumberFormatException nfe) {}
493 _undoStack.add(undo);
494 _trackInfo.getSelection().markInvalid();
495 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
496 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addaltitudeoffset"));
502 * Merge the track segments within the current selection
504 public void mergeTrackSegments()
506 if (_trackInfo.getSelection().hasRangeSelected())
508 // Maybe could check segment start flags to see if it's worth merging
509 // If first track point is already start and no other seg starts then do nothing
511 int selStart = _trackInfo.getSelection().getStart();
512 int selEnd = _trackInfo.getSelection().getEnd();
514 UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
515 // Call track to merge segments
516 if (_trackInfo.mergeTrackSegments(selStart, selEnd)) {
517 _undoStack.add(undo);
518 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
525 * Interpolate the two selected points
527 public void interpolateSelection()
529 // Get number of points to add
530 Object numPointsStr = JOptionPane.showInputDialog(_frame,
531 I18nManager.getText("dialog.interpolate.parameter.text"),
532 I18nManager.getText("dialog.interpolate.title"),
533 JOptionPane.QUESTION_MESSAGE, null, null, "");
534 int numPoints = parseNumber(numPointsStr);
535 if (numPoints <= 0) return;
537 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
539 // call track to interpolate
540 if (_trackInfo.interpolate(numPoints))
542 _undoStack.add(undo);
548 * Average the selected points
550 public void averageSelection()
552 // Find following track point
553 DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
554 boolean segFlag = false;
555 if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
556 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
557 // call track info object to do the averaging
558 if (_trackInfo.average())
560 _undoStack.add(undo);
566 * Create a new point at the given position
567 * @param inPoint point to add
569 public void createPoint(DataPoint inPoint)
571 // create undo object
572 UndoCreatePoint undo = new UndoCreatePoint();
573 _undoStack.add(undo);
574 // add point to track
575 inPoint.setSegmentStart(true);
576 _track.appendPoints(new DataPoint[] {inPoint});
577 // ensure track's field list contains point's fields
578 _track.extendFieldList(inPoint.getFieldList());
579 _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
581 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
586 * Cut the current selection and move it to before the currently selected point
588 public void cutAndMoveSelection()
590 int startIndex = _trackInfo.getSelection().getStart();
591 int endIndex = _trackInfo.getSelection().getEnd();
592 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
593 // If timestamps would be mangled by cut/move, confirm
594 if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
595 || _mangleTimestampsConfirmed
596 || (JOptionPane.showConfirmDialog(_frame,
597 I18nManager.getText("dialog.confirmcutandmove.text"),
598 I18nManager.getText("dialog.confirmcutandmove.title"),
599 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
601 // Find points to set segment flags
602 DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
603 DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
604 DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
606 UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
607 // Call track info to move track section
608 if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
610 // Set segment start flags (first track point, next track point, move to point)
611 if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
612 if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
613 if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
615 // Add undo object to stack, set confirm message
616 _undoStack.add(undo);
617 _trackInfo.getSelection().selectRange(-1, -1);
618 UpdateMessageBroker.informSubscribers();
619 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
627 public void selectNone()
629 // deselect point, range and photo
630 _trackInfo.getSelection().clearAll();
631 _track.clearDeletionMarkers();
635 * Receive loaded data and start load
636 * @param inFieldArray array of fields
637 * @param inDataArray array of data
638 * @param inAltFormat altitude format
639 * @param inSourceInfo information about the source of the data
641 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
642 Altitude.Format inAltFormat, SourceInfo inSourceInfo)
644 informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo, null, null);
648 * Receive loaded data and determine whether to filter on tracks or not
649 * @param inFieldArray array of fields
650 * @param inDataArray array of data
651 * @param inAltFormat altitude format
652 * @param inSourceInfo information about the source of the data
653 * @param inTrackNameList information about the track names
655 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
656 Altitude.Format inAltFormat, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
658 // no link array given
659 informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo,
660 inTrackNameList, null);
664 * Receive loaded data and determine whether to filter on tracks or not
665 * @param inFieldArray array of fields
666 * @param inDataArray array of data
667 * @param inAltFormat altitude format
668 * @param inSourceInfo information about the source of the data
669 * @param inTrackNameList information about the track names
670 * @param inLinkArray array of links to photo/audio files
672 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
673 Altitude.Format inAltFormat, SourceInfo inSourceInfo,
674 TrackNameList inTrackNameList, String[] inLinkArray)
676 // Check whether loaded array can be properly parsed into a Track
677 Track loadedTrack = new Track();
678 loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
679 if (loadedTrack.getNumPoints() <= 0)
681 showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
682 // load next file if there's a queue
686 // Check for doubled track
687 if (Checker.isDoubledTrack(loadedTrack)) {
688 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
689 I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
691 // Attach photos and/or audio files to points
692 if (inLinkArray != null)
694 for (int i=0; i<inLinkArray.length; i++)
696 if (inLinkArray[i] != null)
698 MediaFile mf = MediaHelper.createMediaFile(inLinkArray[i]);
700 loadedTrack.getPoint(i).attachMedia(mf);
701 mf.setOriginalStatus(MediaFile.Status.TAGGED);
702 mf.setCurrentStatus(MediaFile.Status.TAGGED);
707 // Look at TrackNameList, decide whether to filter or not
708 if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
710 // Launch a dialog to let the user choose which tracks to load, then continue
711 new SelectTracksFunction(this, inFieldArray, inDataArray, inAltFormat, inSourceInfo,
712 inTrackNameList).begin();
715 // go directly to load
716 informDataLoaded(loadedTrack, inSourceInfo);
722 * Receive loaded data and optionally merge with current Track
723 * @param inLoadedTrack loaded track
724 * @param inSourceInfo information about the source of the data
726 public void informDataLoaded(Track inLoadedTrack, SourceInfo inSourceInfo)
728 // Decide whether to load or append
729 if (_track.getNumPoints() > 0)
731 // ask whether to replace or append
733 if (_dataFiles == null || _firstDataFile) {
734 answer = JOptionPane.showConfirmDialog(_frame,
735 I18nManager.getText("dialog.openappend.text"),
736 I18nManager.getText("dialog.openappend.title"),
737 JOptionPane.YES_NO_CANCEL_OPTION);
740 // Automatically append if there's a file load queue
741 answer = JOptionPane.YES_OPTION;
743 if (answer == JOptionPane.YES_OPTION)
745 // append data to current Track
746 UndoLoad undo = new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints());
747 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
748 _undoStack.add(undo);
749 _track.combine(inLoadedTrack);
750 // Add photos and audios (if any in loaded track) to list(s)
751 MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
752 MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
753 // set source information
754 inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
755 _trackInfo.getFileInfo().addSource(inSourceInfo);
757 else if (answer == JOptionPane.NO_OPTION)
759 // Don't append, replace data
760 PhotoList photos = null;
761 if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
762 photos = _trackInfo.getPhotoList().cloneList();
764 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
765 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
766 _undoStack.add(undo);
767 _lastSavePosition = _undoStack.size();
768 _trackInfo.getSelection().clearAll();
769 _track.load(inLoadedTrack);
770 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
771 _trackInfo.getFileInfo().replaceSource(inSourceInfo);
772 _trackInfo.getPhotoList().removeCorrelatedPhotos();
773 _trackInfo.getAudioList().removeCorrelatedAudios();
774 // Add photos and audios (if any in loaded track) to list(s)
775 MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
776 MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
781 // Currently no data held, so transfer received data
782 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
783 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
784 _undoStack.add(undo);
785 _lastSavePosition = _undoStack.size();
786 _trackInfo.getSelection().clearAll();
787 _track.load(inLoadedTrack);
788 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
789 _trackInfo.getFileInfo().addSource(inSourceInfo);
790 // Add photos and audios (if any in loaded track) to list(s)
791 MediaHelper.addMediaFromTrack(_track, _trackInfo.getPhotoList(), Photo.class);
792 MediaHelper.addMediaFromTrack(_track, _trackInfo.getAudioList(), AudioFile.class);
794 UpdateMessageBroker.informSubscribers();
796 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
797 + " '" + inSourceInfo.getName() + "'");
799 _menuManager.informFileLoaded();
800 // load next file if there's a queue
805 * Inform the app that NO data was loaded, eg cancel pressed
806 * Only needed if there's another file waiting in the queue
808 public void informNoDataLoaded()
810 // Load next file if there's a queue
815 * Load the next file in the waiting list, if any
817 private void loadNextFile()
819 _firstDataFile = false;
820 if (_dataFiles == null || _dataFiles.size() == 0) {
824 new Thread(new Runnable() {
826 File f = _dataFiles.get(0);
827 _dataFiles.remove(0);
828 _fileLoader.openFile(f);
836 * Accept a list of loaded photos
837 * @param inPhotoSet Set of Photo objects
839 public void informPhotosLoaded(Set<Photo> inPhotoSet)
841 if (inPhotoSet != null && !inPhotoSet.isEmpty())
843 int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
844 int numPhotosAdded = numsAdded[0];
845 int numPointsAdded = numsAdded[1];
846 if (numPhotosAdded > 0)
848 // Save numbers so load can be undone
849 _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
851 if (numPhotosAdded == 1) {
852 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
855 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
857 // MAYBE: Improve message when photo(s) fail to load (eg already added)
858 UpdateMessageBroker.informSubscribers();
860 if (numPointsAdded > 0) _menuManager.informFileLoaded();
866 * Save the coordinates of photos in their exif data
868 public void saveExif()
870 ExifSaver saver = new ExifSaver(_frame);
871 saver.saveExifInformation(_trackInfo.getPhotoList());
876 * Inform the app that the data has been saved
878 public void informDataSaved()
880 _lastSavePosition = _undoStack.size();
887 public void beginUndo()
889 if (_undoStack.isEmpty())
892 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
893 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
897 new UndoManager(this, _frame);
903 * Clear the undo stack (losing all undo information
905 public void clearUndo()
907 // Exit if nothing to undo
908 if (_undoStack == null || _undoStack.isEmpty())
910 // Has track got unsaved data?
911 boolean unsaved = hasDataUnsaved();
912 // Confirm operation with dialog
913 int answer = JOptionPane.showConfirmDialog(_frame,
914 I18nManager.getText("dialog.clearundo.text"),
915 I18nManager.getText("dialog.clearundo.title"),
916 JOptionPane.YES_NO_OPTION);
917 if (answer == JOptionPane.YES_OPTION)
920 _lastSavePosition = 0;
921 if (unsaved) _lastSavePosition = -1;
922 UpdateMessageBroker.informSubscribers();
928 * Undo the specified number of actions
929 * @param inNumUndos number of actions to undo
931 public void undoActions(int inNumUndos)
935 for (int i=0; i<inNumUndos; i++)
937 _undoStack.pop().performUndo(_trackInfo);
939 String message = "" + inNumUndos + " "
940 + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
941 UpdateMessageBroker.informSubscribers(message);
943 catch (UndoException ue)
945 showErrorMessageNoLookup("error.undofailed.title",
946 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
948 UpdateMessageBroker.informSubscribers();
950 catch (EmptyStackException empty) {}
955 * Helper method to parse an Object into an integer
956 * @param inObject object, eg from dialog
957 * @return int value given
959 private static int parseNumber(Object inObject)
962 if (inObject != null)
966 num = Integer.parseInt(inObject.toString());
968 catch (NumberFormatException nfe)
975 * Show a map url in an external browser
976 * @param inSourceIndex index of map source to use
978 public void showExternalMap(int inSourceIndex)
980 BrowserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
984 * Display a standard error message
985 * @param inTitleKey key to lookup for window title
986 * @param inMessageKey key to lookup for error message
988 public void showErrorMessage(String inTitleKey, String inMessageKey)
990 JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
991 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
995 * Display a standard error message
996 * @param inTitleKey key to lookup for window title
997 * @param inMessage error message
999 public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
1001 JOptionPane.showMessageDialog(_frame, inMessage,
1002 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
1006 * @param inViewport viewport object
1008 public void setViewport(Viewport inViewport)
1010 _viewport = inViewport;
1014 * @return current viewport object
1016 public Viewport getViewport()
1022 * Set the controller for the full screen mode
1023 * @param inController controller object
1025 public void setSidebarController(SidebarController inController)
1027 _sidebarController = inController;
1031 * Toggle sidebars on and off
1033 public void toggleSidebars()
1035 _sidebarController.toggle();