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.config.Config;
13 import tim.prune.data.Altitude;
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.NumberUtils;
19 import tim.prune.data.Photo;
20 import tim.prune.data.PhotoList;
21 import tim.prune.data.RecentFile;
22 import tim.prune.data.SourceInfo;
23 import tim.prune.data.Track;
24 import tim.prune.data.TrackInfo;
25 import tim.prune.data.SourceInfo.FILE_TYPE;
26 import tim.prune.function.AsyncMediaLoader;
27 import tim.prune.function.SaveConfig;
28 import tim.prune.function.SelectTracksFunction;
29 import tim.prune.function.browser.BrowserLauncher;
30 import tim.prune.function.browser.UrlGenerator;
31 import tim.prune.function.edit.FieldEditList;
32 import tim.prune.function.edit.PointEditor;
33 import tim.prune.gui.MenuManager;
34 import tim.prune.gui.SidebarController;
35 import tim.prune.gui.UndoManager;
36 import tim.prune.gui.Viewport;
37 import tim.prune.load.FileLoader;
38 import tim.prune.load.JpegLoader;
39 import tim.prune.load.MediaLinkInfo;
40 import tim.prune.load.TrackNameList;
41 import tim.prune.save.ExifSaver;
42 import tim.prune.save.FileSaver;
43 import tim.prune.undo.*;
47 * Main controller for the application
52 private JFrame _frame = null;
53 private Track _track = null;
54 private TrackInfo _trackInfo = null;
55 private int _lastSavePosition = 0;
56 private MenuManager _menuManager = null;
57 private SidebarController _sidebarController = null;
58 private FileLoader _fileLoader = null;
59 private JpegLoader _jpegLoader = null;
60 private FileSaver _fileSaver = null;
61 private Stack<UndoOperation> _undoStack = null;
62 private boolean _mangleTimestampsConfirmed = false;
63 private Viewport _viewport = null;
64 private ArrayList<File> _dataFiles = null;
65 private boolean _firstDataFile = true;
66 private boolean _busyLoading = 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 * Load the specified data files one by one
119 * @param inDataFiles arraylist containing File objects to load
121 public void loadDataFiles(ArrayList<File> inDataFiles)
123 if (inDataFiles == null || inDataFiles.size() == 0) {
127 _dataFiles = inDataFiles;
128 File f = _dataFiles.get(0);
129 _dataFiles.remove(0);
130 // Start load of specified file
131 if (_fileLoader == null)
132 _fileLoader = new FileLoader(this, _frame);
133 _firstDataFile = true;
134 _fileLoader.openFile(f);
139 * Complete a function execution
140 * @param inUndo undo object to be added to stack
141 * @param inConfirmText confirmation text
143 public void completeFunction(UndoOperation inUndo, String inConfirmText)
145 _undoStack.add(inUndo);
146 UpdateMessageBroker.informSubscribers(inConfirmText);
150 * Set the MenuManager object to be informed about changes
151 * @param inManager MenuManager object
153 public void setMenuManager(MenuManager inManager)
155 _menuManager = inManager;
160 * Open a file containing track or waypoint data
162 public void openFile()
164 if (_fileLoader == null)
165 _fileLoader = new FileLoader(this, _frame);
166 _fileLoader.openFile();
171 * Add a photo or a directory of photos
173 public void addPhotos()
175 if (_jpegLoader == null)
176 _jpegLoader = new JpegLoader(this, _frame);
177 _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
181 * Save the file in the selected format
183 public void saveFile()
185 if (_track == null) {
186 showErrorMessage("error.save.dialogtitle", "error.save.nodata");
190 if (_fileSaver == null) {
191 _fileSaver = new FileSaver(this, _frame);
194 if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
195 _fileSaver.showDialog(delim);
201 * Exit the application if confirmed
207 _frame.requestFocus();
208 // check if ok to exit
209 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
210 if (!hasDataUnsaved()
211 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
212 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
213 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
214 == JOptionPane.YES_OPTION)
217 if (Config.getConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS)) {
218 new SaveConfig(this).silentSave();
226 * Edit the currently selected point
228 public void editCurrentPoint()
232 DataPoint currentPoint = _trackInfo.getCurrentPoint();
233 if (currentPoint != null)
235 // Open point dialog to display details
236 PointEditor editor = new PointEditor(this, _frame);
237 editor.showDialog(_track, currentPoint);
244 * Complete the point edit
245 * @param inEditList field values to edit
246 * @param inUndoList field values before edit
248 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
250 DataPoint currentPoint = _trackInfo.getCurrentPoint();
251 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
253 // add information to undo stack
254 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
255 // pass to track for completion
256 if (_track.editPoint(currentPoint, inEditList, false))
258 _undoStack.push(undo);
259 // Confirm point edit
260 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
267 * Delete the currently selected point
269 public void deleteCurrentPoint()
271 if (_track == null) {return;}
272 DataPoint currentPoint = _trackInfo.getCurrentPoint();
273 if (currentPoint != null)
275 boolean deletePhoto = false;
276 Photo currentPhoto = currentPoint.getPhoto();
277 if (currentPhoto != null)
279 // Confirm deletion of photo or decoupling
280 int response = JOptionPane.showConfirmDialog(_frame,
281 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getName(),
282 I18nManager.getText("dialog.deletepoint.title"),
283 JOptionPane.YES_NO_CANCEL_OPTION);
284 if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
286 // cancel pressed- abort delete
289 if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
291 // store necessary information to undo it later
292 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
293 int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
294 DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
295 // Construct Undo object
296 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
297 nextTrackPoint != null && nextTrackPoint.getSegmentStart());
298 // call track to delete point
299 if (_trackInfo.deletePoint())
301 // Delete was successful so add undo info to stack
302 _undoStack.push(undo);
303 if (currentPhoto != null)
305 // delete photo if necessary
308 _trackInfo.getPhotoList().deletePhoto(photoIndex);
312 // decouple photo from point
313 currentPhoto.setDataPoint(null);
315 UpdateMessageBroker.informSubscribers();
318 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
325 * Delete the currently selected range
327 public void deleteSelectedRange()
329 if (_track == null) return;
330 // Find out if photos should be deleted or not
331 int selStart = _trackInfo.getSelection().getStart();
332 int selEnd = _trackInfo.getSelection().getEnd();
333 if (selStart >= 0 && selEnd >= selStart)
335 int numToDelete = selEnd - selStart + 1;
336 boolean[] deletePhotos = new boolean[numToDelete];
337 Photo[] photosToDelete = new Photo[numToDelete];
338 boolean deleteAll = false;
339 boolean deleteNone = false;
340 String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
341 I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"),
342 I18nManager.getText("button.cancel")};
343 DataPoint point = null;
344 for (int i=0; i<numToDelete; i++)
346 point = _trackInfo.getTrack().getPoint(i + selStart);
347 if (point != null && point.getPhoto() != null)
351 deletePhotos[i] = true;
352 photosToDelete[i] = point.getPhoto();
354 else if (deleteNone) {deletePhotos[i] = false;}
357 int response = JOptionPane.showOptionDialog(_frame,
358 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getName(),
359 I18nManager.getText("dialog.deletepoint.title"),
360 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
361 questionOptions, questionOptions[1]);
362 // check for cancel or close
363 if (response == 4 || response == -1) {return;}
364 // check for yes or yes to all
365 if (response == 0 || response == 2)
367 deletePhotos[i] = true;
368 photosToDelete[i] = point.getPhoto();
369 if (response == 2) {deleteAll = true;}
371 // check for no to all
372 if (response == 3) {deleteNone = true;}
376 // add information to undo stack
377 UndoDeleteRange undo = new UndoDeleteRange(_trackInfo);
378 // delete requested photos
379 for (int i=0; i<numToDelete; i++)
381 point = _trackInfo.getTrack().getPoint(i + selStart);
382 if (point != null && point.getPhoto() != null)
386 // delete photo from list
387 _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
391 // decouple from point
392 point.getPhoto().setDataPoint(null);
396 // call track to delete range
397 if (_trackInfo.deleteRange())
399 _undoStack.push(undo);
401 UpdateMessageBroker.informSubscribers("" + numToDelete + " "
402 + I18nManager.getText("confirm.deletepoint.multi"));
409 * Finish the compression by deleting the marked points
411 public void finishCompressTrack()
413 UndoCompress undo = new UndoCompress(_track);
414 // call track to do compress
415 int numPointsDeleted = _trackInfo.deleteMarkedPoints();
416 // add to undo stack if successful
417 if (numPointsDeleted > 0)
419 undo.setNumPointsDeleted(numPointsDeleted);
420 _undoStack.add(undo);
421 UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " "
422 + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi")));
425 showErrorMessage("function.compress", "dialog.compress.nonefound");
430 * Reverse the currently selected section of the track
432 public void reverseRange()
434 // check whether Timestamp field exists, and if so confirm reversal
435 int selStart = _trackInfo.getSelection().getStart();
436 int selEnd = _trackInfo.getSelection().getEnd();
437 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
438 || _mangleTimestampsConfirmed
439 || (JOptionPane.showConfirmDialog(_frame,
440 I18nManager.getText("dialog.confirmreversetrack.text"),
441 I18nManager.getText("dialog.confirmreversetrack.title"),
442 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
444 UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
445 // call track to reverse range
446 if (_track.reverseRange(selStart, selEnd))
448 _undoStack.add(undo);
450 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
456 * Complete the add time offset function with the specified offset
457 * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
459 public void finishAddTimeOffset(long inTimeOffset)
461 // Construct undo information
462 int selStart = _trackInfo.getSelection().getStart();
463 int selEnd = _trackInfo.getSelection().getEnd();
464 UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
465 if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
467 _undoStack.add(undo);
468 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
469 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
475 * Complete the add altitude offset function with the specified offset
476 * @param inOffset altitude offset to add as String
477 * @param inFormat altitude format of offset (eg Feet, Metres)
479 public void finishAddAltitudeOffset(String inOffset, Altitude.Format inFormat)
482 if (inOffset == null || inOffset.equals("") || inFormat==Altitude.Format.NO_FORMAT) {
485 // Construct undo information
486 UndoAddAltitudeOffset undo = new UndoAddAltitudeOffset(_trackInfo);
487 int selStart = _trackInfo.getSelection().getStart();
488 int selEnd = _trackInfo.getSelection().getEnd();
489 // How many decimal places are given in the offset?
490 int numDecimals = NumberUtils.getDecimalPlaces(inOffset);
491 boolean success = false;
492 // Decimal offset given
494 double offsetd = Double.parseDouble(inOffset);
495 success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inFormat, numDecimals);
497 catch (NumberFormatException nfe) {}
500 _undoStack.add(undo);
501 _trackInfo.getSelection().markInvalid();
502 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
503 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addaltitudeoffset"));
509 * Merge the track segments within the current selection
511 public void mergeTrackSegments()
513 if (_trackInfo.getSelection().hasRangeSelected())
515 // Maybe could check segment start flags to see if it's worth merging
516 // If first track point is already start and no other seg starts then do nothing
518 int selStart = _trackInfo.getSelection().getStart();
519 int selEnd = _trackInfo.getSelection().getEnd();
521 UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
522 // Call track to merge segments
523 if (_trackInfo.mergeTrackSegments(selStart, selEnd)) {
524 _undoStack.add(undo);
525 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
532 * Interpolate the two selected points
534 public void interpolateSelection()
536 // Get number of points to add
537 Object numPointsStr = JOptionPane.showInputDialog(_frame,
538 I18nManager.getText("dialog.interpolate.parameter.text"),
539 I18nManager.getText("dialog.interpolate.title"),
540 JOptionPane.QUESTION_MESSAGE, null, null, "");
541 int numPoints = parseNumber(numPointsStr);
542 if (numPoints <= 0) return;
544 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
546 // call track to interpolate
547 if (_trackInfo.interpolate(numPoints))
549 _undoStack.add(undo);
555 * Average the selected points
557 public void averageSelection()
559 // Find following track point
560 DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
561 boolean segFlag = false;
562 if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
563 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
564 // call track info object to do the averaging
565 if (_trackInfo.average())
567 _undoStack.add(undo);
573 * Create a new point at the given position
574 * @param inPoint point to add
576 public void createPoint(DataPoint inPoint)
578 // create undo object
579 UndoCreatePoint undo = new UndoCreatePoint();
580 _undoStack.add(undo);
581 // add point to track
582 inPoint.setSegmentStart(true);
583 _track.appendPoints(new DataPoint[] {inPoint});
584 // ensure track's field list contains point's fields
585 _track.extendFieldList(inPoint.getFieldList());
586 _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
588 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
593 * Cut the current selection and move it to before the currently selected point
595 public void cutAndMoveSelection()
597 int startIndex = _trackInfo.getSelection().getStart();
598 int endIndex = _trackInfo.getSelection().getEnd();
599 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
600 // If timestamps would be mangled by cut/move, confirm
601 if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
602 || _mangleTimestampsConfirmed
603 || (JOptionPane.showConfirmDialog(_frame,
604 I18nManager.getText("dialog.confirmcutandmove.text"),
605 I18nManager.getText("dialog.confirmcutandmove.title"),
606 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
608 // Find points to set segment flags
609 DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
610 DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
611 DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
613 UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
614 // Call track info to move track section
615 if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
617 // Set segment start flags (first track point, next track point, move to point)
618 if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
619 if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
620 if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
622 // Add undo object to stack, set confirm message
623 _undoStack.add(undo);
624 _trackInfo.getSelection().selectRange(-1, -1);
625 UpdateMessageBroker.informSubscribers();
626 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
634 public void selectNone()
636 // deselect point, range and photo
637 _trackInfo.getSelection().clearAll();
638 _track.clearDeletionMarkers();
642 * Receive loaded data and start load
643 * @param inFieldArray array of fields
644 * @param inDataArray array of data
645 * @param inAltFormat altitude format
646 * @param inSourceInfo information about the source of the data
648 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
649 Altitude.Format inAltFormat, SourceInfo inSourceInfo)
651 informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo, null, null);
655 * Receive loaded data and determine whether to filter on tracks or not
656 * @param inFieldArray array of fields
657 * @param inDataArray array of data
658 * @param inAltFormat altitude format
659 * @param inSourceInfo information about the source of the data
660 * @param inTrackNameList information about the track names
662 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
663 Altitude.Format inAltFormat, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
665 // no link array given
666 informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo,
667 inTrackNameList, null);
671 * Receive loaded data and determine whether to filter on tracks or not
672 * @param inFieldArray array of fields
673 * @param inDataArray array of data
674 * @param inAltFormat altitude format
675 * @param inSourceInfo information about the source of the data
676 * @param inTrackNameList information about the track names
677 * @param inLinkInfo links to photo/audio clips
679 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
680 Altitude.Format inAltFormat, SourceInfo inSourceInfo,
681 TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
683 // Check whether loaded array can be properly parsed into a Track
684 Track loadedTrack = new Track();
685 loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
686 if (loadedTrack.getNumPoints() <= 0)
688 showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
689 // load next file if there's a queue
693 // Check for doubled track
694 if (Checker.isDoubledTrack(loadedTrack)) {
695 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
696 I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
700 // Attach photos and/or audio clips to points
701 if (inLinkInfo != null)
703 String[] linkArray = inLinkInfo.getLinkArray();
704 if (linkArray != null) {
705 new AsyncMediaLoader(this, inLinkInfo.getZipFile(), linkArray, loadedTrack, inSourceInfo.getFile()).begin();
708 // Look at TrackNameList, decide whether to filter or not
709 if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
711 // Launch a dialog to let the user choose which tracks to load, then continue
712 new SelectTracksFunction(this, loadedTrack, inSourceInfo, 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 // set source information
751 inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
752 _trackInfo.getFileInfo().addSource(inSourceInfo);
754 else if (answer == JOptionPane.NO_OPTION)
756 // Don't append, replace data
757 PhotoList photos = null;
758 if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
759 photos = _trackInfo.getPhotoList().cloneList();
761 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
762 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
763 _undoStack.add(undo);
764 _lastSavePosition = _undoStack.size();
765 _trackInfo.getSelection().clearAll();
766 _track.load(inLoadedTrack);
767 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
768 _trackInfo.getFileInfo().replaceSource(inSourceInfo);
769 _trackInfo.getPhotoList().removeCorrelatedPhotos();
770 _trackInfo.getAudioList().removeCorrelatedAudios();
775 // Currently no data held, so transfer received data
776 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
777 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
778 _undoStack.add(undo);
779 _lastSavePosition = _undoStack.size();
780 _trackInfo.getSelection().clearAll();
781 _track.load(inLoadedTrack);
782 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
783 _trackInfo.getFileInfo().addSource(inSourceInfo);
785 // Update config before subscribers are told
786 boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
787 Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
788 UpdateMessageBroker.informSubscribers();
790 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
791 + " '" + inSourceInfo.getName() + "'");
793 _menuManager.informFileLoaded();
795 _busyLoading = false;
796 // load next file if there's a queue
801 * Inform the app that NO data was loaded, eg cancel pressed
802 * Only needed if there's another file waiting in the queue
804 public void informNoDataLoaded()
806 // Load next file if there's a queue
811 * Load the next file in the waiting list, if any
813 private void loadNextFile()
815 _firstDataFile = false;
816 if (_dataFiles == null || _dataFiles.size() == 0) {
820 new Thread(new Runnable() {
822 File f = _dataFiles.get(0);
823 _dataFiles.remove(0);
824 _fileLoader.openFile(f);
832 * Accept a list of loaded photos
833 * @param inPhotoSet Set of Photo objects
835 public void informPhotosLoaded(Set<Photo> inPhotoSet)
837 if (inPhotoSet != null && !inPhotoSet.isEmpty())
839 int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
840 int numPhotosAdded = numsAdded[0];
841 int numPointsAdded = numsAdded[1];
842 if (numPhotosAdded > 0)
844 // Save numbers so load can be undone
845 _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
847 if (numPhotosAdded == 1) {
848 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
851 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
853 // MAYBE: Improve message when photo(s) fail to load (eg already added)
854 UpdateMessageBroker.informSubscribers();
856 if (numPointsAdded > 0) _menuManager.informFileLoaded();
862 * Save the coordinates of photos in their exif data
864 public void saveExif()
866 ExifSaver saver = new ExifSaver(_frame);
867 saver.saveExifInformation(_trackInfo.getPhotoList());
872 * Inform the app that the data has been saved
874 public void informDataSaved()
876 _lastSavePosition = _undoStack.size();
883 public void beginUndo()
885 if (_undoStack.isEmpty())
888 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
889 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
893 new UndoManager(this, _frame);
899 * Clear the undo stack (losing all undo information
901 public void clearUndo()
903 // Exit if nothing to undo
904 if (_undoStack == null || _undoStack.isEmpty())
906 // Has track got unsaved data?
907 boolean unsaved = hasDataUnsaved();
908 // Confirm operation with dialog
909 int answer = JOptionPane.showConfirmDialog(_frame,
910 I18nManager.getText("dialog.clearundo.text"),
911 I18nManager.getText("dialog.clearundo.title"),
912 JOptionPane.YES_NO_OPTION);
913 if (answer == JOptionPane.YES_OPTION)
916 _lastSavePosition = 0;
917 if (unsaved) _lastSavePosition = -1;
918 UpdateMessageBroker.informSubscribers();
924 * Undo the specified number of actions
925 * @param inNumUndos number of actions to undo
927 public void undoActions(int inNumUndos)
931 for (int i=0; i<inNumUndos; i++)
933 _undoStack.pop().performUndo(_trackInfo);
935 String message = "" + inNumUndos + " "
936 + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
937 UpdateMessageBroker.informSubscribers(message);
939 catch (UndoException ue)
941 showErrorMessageNoLookup("error.undofailed.title",
942 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
944 UpdateMessageBroker.informSubscribers();
946 catch (EmptyStackException empty) {}
951 * Helper method to parse an Object into an integer
952 * @param inObject object, eg from dialog
953 * @return int value given
955 private static int parseNumber(Object inObject)
958 if (inObject != null)
962 num = Integer.parseInt(inObject.toString());
964 catch (NumberFormatException nfe)
971 * Show a map url in an external browser
972 * @param inSourceIndex index of map source to use
974 public void showExternalMap(int inSourceIndex)
976 BrowserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
980 * Display a standard error message
981 * @param inTitleKey key to lookup for window title
982 * @param inMessageKey key to lookup for error message
984 public void showErrorMessage(String inTitleKey, String inMessageKey)
986 JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
987 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
991 * Display a standard error message
992 * @param inTitleKey key to lookup for window title
993 * @param inMessage error message
995 public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
997 JOptionPane.showMessageDialog(_frame, inMessage,
998 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
1002 * @param inViewport viewport object
1004 public void setViewport(Viewport inViewport)
1006 _viewport = inViewport;
1010 * @return current viewport object
1012 public Viewport getViewport()
1018 * Set the controller for the full screen mode
1019 * @param inController controller object
1021 public void setSidebarController(SidebarController inController)
1023 _sidebarController = inController;
1027 * Toggle sidebars on and off
1029 public void toggleSidebars()
1031 _sidebarController.toggle();
1034 /** @return true if App is currently busy with loading data */
1035 public boolean isBusyLoading() {
1036 return _busyLoading;