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.Checker;
14 import tim.prune.data.DataPoint;
15 import tim.prune.data.Field;
16 import tim.prune.data.LatLonRectangle;
17 import tim.prune.data.NumberUtils;
18 import tim.prune.data.Photo;
19 import tim.prune.data.PhotoList;
20 import tim.prune.data.PointCreateOptions;
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.data.Unit;
27 import tim.prune.function.AsyncMediaLoader;
28 import tim.prune.function.SaveConfig;
29 import tim.prune.function.SelectTracksFunction;
30 import tim.prune.function.browser.BrowserLauncher;
31 import tim.prune.function.browser.UrlGenerator;
32 import tim.prune.function.edit.FieldEditList;
33 import tim.prune.function.edit.PointEditor;
34 import tim.prune.gui.MenuManager;
35 import tim.prune.gui.SidebarController;
36 import tim.prune.gui.UndoManager;
37 import tim.prune.gui.Viewport;
38 import tim.prune.load.FileLoader;
39 import tim.prune.load.JpegLoader;
40 import tim.prune.load.MediaLinkInfo;
41 import tim.prune.load.TrackNameList;
42 import tim.prune.save.ExifSaver;
43 import tim.prune.save.FileSaver;
44 import tim.prune.tips.TipManager;
45 import tim.prune.undo.*;
49 * Main controller for the application
54 private JFrame _frame = null;
55 private Track _track = null;
56 private TrackInfo _trackInfo = null;
57 private int _lastSavePosition = 0;
58 private MenuManager _menuManager = null;
59 private SidebarController _sidebarController = null;
60 private FileLoader _fileLoader = null;
61 private JpegLoader _jpegLoader = null;
62 private FileSaver _fileSaver = null;
63 private Stack<UndoOperation> _undoStack = null;
64 private boolean _mangleTimestampsConfirmed = false;
65 private Viewport _viewport = null;
66 private ArrayList<File> _dataFiles = null;
67 private boolean _autoAppendNextFile = false;
68 private boolean _busyLoading = false;
69 private AppMode _appMode = AppMode.NORMAL;
71 /** Enum for the app mode - currently only two options but may expand later */
72 public enum AppMode {NORMAL, DRAWRECT};
77 * @param inFrame frame object for application
79 public App(JFrame inFrame)
82 _undoStack = new Stack<UndoOperation>();
84 _trackInfo = new TrackInfo(_track);
85 FunctionLibrary.initialise(this);
90 * @return the current TrackInfo
92 public TrackInfo getTrackInfo()
98 * @return the dialog frame
100 public JFrame getFrame()
106 * Check if the application has unsaved data
107 * @return true if data is unsaved, false otherwise
109 public boolean hasDataUnsaved()
111 return (_undoStack.size() > _lastSavePosition
112 && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().hasModifiedMedia()));
116 * @return the undo stack
118 public Stack<UndoOperation> getUndoStack()
125 * Show the specified tip if appropriate
126 * @param inTipNumber tip number from TipManager
128 public void showTip(int inTipNumber)
130 String key = TipManager.fireTipTrigger(inTipNumber);
131 if (key != null && !key.equals(""))
133 JOptionPane.showMessageDialog(_frame, I18nManager.getText(key),
134 I18nManager.getText("tip.title"), JOptionPane.INFORMATION_MESSAGE);
140 * Load the specified data files one by one
141 * @param inDataFiles arraylist containing File objects to load
143 public void loadDataFiles(ArrayList<File> inDataFiles)
145 if (inDataFiles == null || inDataFiles.size() == 0) {
150 _dataFiles = inDataFiles;
151 File f = _dataFiles.get(0);
152 _dataFiles.remove(0);
153 // Start load of specified file
154 if (_fileLoader == null)
155 _fileLoader = new FileLoader(this, _frame);
156 _autoAppendNextFile = false; // prompt for append
157 _fileLoader.openFile(f);
162 * Complete a function execution
163 * @param inUndo undo object to be added to stack
164 * @param inConfirmText confirmation text
166 public void completeFunction(UndoOperation inUndo, String inConfirmText)
168 _undoStack.add(inUndo);
169 UpdateMessageBroker.informSubscribers(inConfirmText);
170 setCurrentMode(AppMode.NORMAL);
174 * Set the MenuManager object to be informed about changes
175 * @param inManager MenuManager object
177 public void setMenuManager(MenuManager inManager)
179 _menuManager = inManager;
184 * Open a file containing track or waypoint data
186 public void openFile()
188 if (_fileLoader == null)
189 _fileLoader = new FileLoader(this, _frame);
190 _fileLoader.openFile();
195 * Add a photo or a directory of photos
197 public void addPhotos()
199 if (_jpegLoader == null)
200 _jpegLoader = new JpegLoader(this, _frame);
201 _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
205 * Save the file in the selected format
207 public void saveFile()
209 if (_track == null) {
210 showErrorMessage("error.save.dialogtitle", "error.save.nodata");
214 if (_fileSaver == null) {
215 _fileSaver = new FileSaver(this, _frame);
218 if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
219 _fileSaver.showDialog(delim);
225 * Exit the application if confirmed
231 _frame.requestFocus();
232 // check if ok to exit
233 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
234 if (!hasDataUnsaved()
235 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
236 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
237 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
238 == JOptionPane.YES_OPTION)
241 if (Config.getConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS)) {
242 new SaveConfig(this).silentSave();
250 * Edit the currently selected point
252 public void editCurrentPoint()
256 DataPoint currentPoint = _trackInfo.getCurrentPoint();
257 if (currentPoint != null)
259 // Open point dialog to display details
260 PointEditor editor = new PointEditor(this, _frame);
261 editor.showDialog(_track, currentPoint);
268 * Complete the point edit
269 * @param inEditList field values to edit
270 * @param inUndoList field values before edit
272 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
274 DataPoint currentPoint = _trackInfo.getCurrentPoint();
275 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
277 // add information to undo stack
278 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
279 // pass to track for completion
280 if (_track.editPoint(currentPoint, inEditList, false))
282 _undoStack.push(undo);
283 // Confirm point edit
284 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
291 * Delete the currently selected point
293 public void deleteCurrentPoint()
295 if (_track == null) {return;}
296 DataPoint currentPoint = _trackInfo.getCurrentPoint();
297 if (currentPoint != null)
300 boolean deletePhoto = false;
301 Photo currentPhoto = currentPoint.getPhoto();
302 if (currentPhoto != null)
304 // Confirm deletion of photo or decoupling
305 int response = JOptionPane.showConfirmDialog(_frame,
306 I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getName(),
307 I18nManager.getText("dialog.deletepoint.title"),
308 JOptionPane.YES_NO_CANCEL_OPTION);
309 if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
311 // cancel pressed- abort delete
314 if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
316 // store necessary information to undo it later
317 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
318 int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
319 int audioIndex = _trackInfo.getAudioList().getAudioIndex(currentPoint.getAudio());
320 DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
321 // Construct Undo object
322 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
323 audioIndex, nextTrackPoint != null && nextTrackPoint.getSegmentStart());
324 // call track to delete point
325 if (_trackInfo.deletePoint())
327 // Delete was successful so add undo info to stack
328 _undoStack.push(undo);
329 if (currentPhoto != null)
331 // delete photo if necessary
334 _trackInfo.getPhotoList().deletePhoto(photoIndex);
338 // decouple photo from point
339 currentPhoto.setDataPoint(null);
341 UpdateMessageBroker.informSubscribers(DataSubscriber.PHOTOS_MODIFIED);
343 // Delete audio object (without bothering to ask)
344 if (audioIndex > -1) {
345 _trackInfo.getAudioList().deleteAudio(audioIndex);
348 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
349 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_ADDED_OR_REMOVED);
356 * Finish the compression by deleting the marked points
358 public void finishCompressTrack()
360 UndoDeleteMarked undo = new UndoDeleteMarked(_track);
361 // call track to do compress
362 int numPointsDeleted = _trackInfo.deleteMarkedPoints();
363 // add to undo stack if successful
364 if (numPointsDeleted > 0)
366 undo.setNumPointsDeleted(numPointsDeleted);
367 _undoStack.add(undo);
368 UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " "
369 + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi")));
372 showErrorMessage("function.compress", "dialog.deletemarked.nonefound");
377 * Reverse the currently selected section of the track
379 public void reverseRange()
381 // check whether Timestamp field exists, and if so confirm reversal
382 int selStart = _trackInfo.getSelection().getStart();
383 int selEnd = _trackInfo.getSelection().getEnd();
384 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
385 || _mangleTimestampsConfirmed
386 || (JOptionPane.showConfirmDialog(_frame,
387 I18nManager.getText("dialog.confirmreversetrack.text"),
388 I18nManager.getText("dialog.confirmreversetrack.title"),
389 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
391 UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
392 // call track to reverse range
393 if (_track.reverseRange(selStart, selEnd))
395 _undoStack.add(undo);
397 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
403 * Complete the add time offset function with the specified offset
404 * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
406 public void finishAddTimeOffset(long inTimeOffset)
408 // Construct undo information
409 int selStart = _trackInfo.getSelection().getStart();
410 int selEnd = _trackInfo.getSelection().getEnd();
411 UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
412 if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
414 _undoStack.add(undo);
415 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
416 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
422 * Complete the add altitude offset function with the specified offset
423 * @param inOffset altitude offset to add as String
424 * @param inUnit altitude units of offset (eg Feet, Metres)
426 public void finishAddAltitudeOffset(String inOffset, Unit inUnit)
429 if (inOffset == null || inOffset.equals("") || inUnit == null) {
432 // Construct undo information
433 UndoAddAltitudeOffset undo = new UndoAddAltitudeOffset(_trackInfo);
434 int selStart = _trackInfo.getSelection().getStart();
435 int selEnd = _trackInfo.getSelection().getEnd();
436 // How many decimal places are given in the offset?
437 int numDecimals = NumberUtils.getDecimalPlaces(inOffset);
438 boolean success = false;
439 // Decimal offset given
441 double offsetd = Double.parseDouble(inOffset);
442 success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inUnit, numDecimals);
444 catch (NumberFormatException nfe) {}
447 _undoStack.add(undo);
448 _trackInfo.getSelection().markInvalid();
449 UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
450 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addaltitudeoffset"));
456 * Merge the track segments within the current selection
458 public void mergeTrackSegments()
460 if (_trackInfo.getSelection().hasRangeSelected())
462 // Maybe could check segment start flags to see if it's worth merging
463 // If first track point is already start and no other seg starts then do nothing
465 int selStart = _trackInfo.getSelection().getStart();
466 int selEnd = _trackInfo.getSelection().getEnd();
468 UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
469 // Call track to merge segments
470 if (_trackInfo.mergeTrackSegments(selStart, selEnd)) {
471 _undoStack.add(undo);
472 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
479 * Average the selected points
481 public void averageSelection()
483 // Find following track point
484 DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
485 boolean segFlag = false;
486 if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
487 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
488 // call track info object to do the averaging
489 if (_trackInfo.average())
491 _undoStack.add(undo);
497 * Create a new point at the end of the track
498 * @param inPoint point to add
500 public void createPoint(DataPoint inPoint)
502 // create undo object
503 UndoCreatePoint undo = new UndoCreatePoint();
504 _undoStack.add(undo);
505 // add point to track
506 inPoint.setSegmentStart(true);
507 _track.appendPoints(new DataPoint[] {inPoint});
508 // ensure track's field list contains point's fields
509 _track.extendFieldList(inPoint.getFieldList());
510 _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
512 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
517 * Create a new point before the given position
518 * @param inPoint point to add
519 * @param inIndex index of following point
521 public void createPoint(DataPoint inPoint, int inIndex)
523 // create undo object
524 UndoInsert undo = new UndoInsert(inIndex, 1);
525 _undoStack.add(undo);
526 // add point to track
527 _track.insertPoint(inPoint, inIndex);
528 // ensure track's field list contains point's fields
529 _track.extendFieldList(inPoint.getFieldList());
530 _trackInfo.selectPoint(inIndex);
531 final int selStart = _trackInfo.getSelection().getStart();
532 final int selEnd = _trackInfo.getSelection().getEnd();
533 if (selStart < inIndex && selEnd > inIndex)
535 // Extend end of selection by 1
536 _trackInfo.getSelection().selectRange(selStart, selEnd+1);
539 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
544 * Cut the current selection and move it to before the currently selected point
546 public void cutAndMoveSelection()
548 int startIndex = _trackInfo.getSelection().getStart();
549 int endIndex = _trackInfo.getSelection().getEnd();
550 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
551 // If timestamps would be mangled by cut/move, confirm
552 if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
553 || _mangleTimestampsConfirmed
554 || (JOptionPane.showConfirmDialog(_frame,
555 I18nManager.getText("dialog.confirmcutandmove.text"),
556 I18nManager.getText("dialog.confirmcutandmove.title"),
557 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
559 // Find points to set segment flags
560 DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
561 DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
562 DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
564 UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
565 // Call track info to move track section
566 if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
568 // Set segment start flags (first track point, next track point, move to point)
569 if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
570 if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
571 if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
573 // Add undo object to stack, set confirm message
574 _undoStack.add(undo);
575 _trackInfo.getSelection().selectRange(-1, -1);
576 UpdateMessageBroker.informSubscribers();
577 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
585 public void selectNone()
587 // deselect point, range and photo
588 _trackInfo.getSelection().clearAll();
589 _track.clearDeletionMarkers();
593 * Receive loaded data and determine whether to filter on tracks or not
594 * @param inFieldArray array of fields
595 * @param inDataArray array of data
596 * @param inSourceInfo information about the source of the data
597 * @param inTrackNameList information about the track names
599 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
600 SourceInfo inSourceInfo, TrackNameList inTrackNameList)
602 // no link array given
603 informDataLoaded(inFieldArray, inDataArray, null, inSourceInfo,
604 inTrackNameList, null);
608 * Receive loaded data and determine whether to filter on tracks or not
609 * @param inFieldArray array of fields
610 * @param inDataArray array of data
611 * @param inOptions creation options such as units
612 * @param inSourceInfo information about the source of the data
613 * @param inTrackNameList information about the track names
615 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
616 PointCreateOptions inOptions, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
618 // no link array given
619 informDataLoaded(inFieldArray, inDataArray, inOptions, inSourceInfo,
620 inTrackNameList, null);
624 * Receive loaded data and determine whether to filter on tracks or not
625 * @param inFieldArray array of fields
626 * @param inDataArray array of data
627 * @param inOptions creation options such as units
628 * @param inSourceInfo information about the source of the data
629 * @param inTrackNameList information about the track names
630 * @param inLinkInfo links to photo/audio clips
632 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, PointCreateOptions inOptions,
633 SourceInfo inSourceInfo, TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
635 // Check whether loaded array can be properly parsed into a Track
636 Track loadedTrack = new Track();
637 loadedTrack.load(inFieldArray, inDataArray, inOptions);
638 if (loadedTrack.getNumPoints() <= 0)
640 showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
641 // load next file if there's a queue
645 // Check for doubled track
646 if (Checker.isDoubledTrack(loadedTrack)) {
647 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
648 I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
652 // Attach photos and/or audio clips to points
653 if (inLinkInfo != null)
655 String[] linkArray = inLinkInfo.getLinkArray();
656 if (linkArray != null) {
657 new AsyncMediaLoader(this, inLinkInfo.getZipFile(), linkArray, loadedTrack, inSourceInfo.getFile()).begin();
660 // Look at TrackNameList, decide whether to filter or not
661 if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
663 // Launch a dialog to let the user choose which tracks to load, then continue
664 new SelectTracksFunction(this, loadedTrack, inSourceInfo, inTrackNameList).begin();
667 // go directly to load
668 informDataLoaded(loadedTrack, inSourceInfo);
670 setCurrentMode(AppMode.NORMAL);
675 * Receive loaded data and optionally merge with current Track
676 * @param inLoadedTrack loaded track
677 * @param inSourceInfo information about the source of the data
679 public void informDataLoaded(Track inLoadedTrack, SourceInfo inSourceInfo)
681 // Decide whether to load or append
682 if (_track.getNumPoints() > 0)
684 // ask whether to replace or append
686 if (_autoAppendNextFile) {
687 // Automatically append the next file
688 answer = JOptionPane.YES_OPTION;
691 // Ask whether to append or not
692 answer = JOptionPane.showConfirmDialog(_frame,
693 I18nManager.getText("dialog.openappend.text"),
694 I18nManager.getText("dialog.openappend.title"),
695 JOptionPane.YES_NO_CANCEL_OPTION);
697 _autoAppendNextFile = false; // reset flag to cancel autoappend
699 if (answer == JOptionPane.YES_OPTION)
701 // append data to current Track
702 UndoLoad undo = new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints());
703 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
704 _undoStack.add(undo);
705 _track.combine(inLoadedTrack);
706 // set source information
707 inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
708 _trackInfo.getFileInfo().addSource(inSourceInfo);
710 else if (answer == JOptionPane.NO_OPTION)
712 // Don't append, replace data
713 PhotoList photos = null;
714 if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
715 photos = _trackInfo.getPhotoList().cloneList();
717 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
718 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
719 _undoStack.add(undo);
720 _lastSavePosition = _undoStack.size();
721 _trackInfo.getSelection().clearAll();
722 _track.load(inLoadedTrack);
723 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
724 _trackInfo.getFileInfo().replaceSource(inSourceInfo);
725 _trackInfo.getPhotoList().removeCorrelatedPhotos();
726 _trackInfo.getAudioList().removeCorrelatedAudios();
731 // Currently no data held, so transfer received data
732 UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
733 undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
734 _undoStack.add(undo);
735 _lastSavePosition = _undoStack.size();
736 _trackInfo.getSelection().clearAll();
737 _track.load(inLoadedTrack);
738 inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
739 _trackInfo.getFileInfo().addSource(inSourceInfo);
741 // Update config before subscribers are told
742 boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
743 Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
744 UpdateMessageBroker.informSubscribers();
746 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
747 + " '" + inSourceInfo.getName() + "'");
749 _menuManager.informFileLoaded();
751 _busyLoading = false;
752 // load next file if there's a queue
757 * Inform the app that NO data was loaded, eg cancel pressed
758 * Only needed if there's another file waiting in the queue
760 public void informNoDataLoaded()
762 // Load next file if there's a queue
767 * External trigger to automatically append the next loaded file
768 * instead of prompting to replace or append
770 public void autoAppendNextFile()
772 _autoAppendNextFile = true;
776 * Load the next file in the waiting list, if any
778 private void loadNextFile()
780 if (_dataFiles == null || _dataFiles.size() == 0) {
784 new Thread(new Runnable() {
786 File f = _dataFiles.get(0);
787 _dataFiles.remove(0);
788 _autoAppendNextFile = true;
789 _fileLoader.openFile(f);
797 * Accept a list of loaded photos
798 * @param inPhotoSet Set of Photo objects
800 public void informPhotosLoaded(Set<Photo> inPhotoSet)
802 if (inPhotoSet != null && !inPhotoSet.isEmpty())
804 int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
805 int numPhotosAdded = numsAdded[0];
806 int numPointsAdded = numsAdded[1];
807 if (numPhotosAdded > 0)
809 // Save numbers so load can be undone
810 _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
812 if (numPhotosAdded == 1) {
813 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
816 UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
818 // MAYBE: Improve message when photo(s) fail to load (eg already added)
819 UpdateMessageBroker.informSubscribers();
821 if (numPointsAdded > 0) _menuManager.informFileLoaded();
827 * Save the coordinates of photos in their exif data
829 public void saveExif()
831 ExifSaver saver = new ExifSaver(_frame);
832 saver.saveExifInformation(_trackInfo.getPhotoList());
837 * Inform the app that the data has been saved
839 public void informDataSaved()
841 _lastSavePosition = _undoStack.size();
848 public void beginUndo()
850 if (_undoStack.isEmpty())
853 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
854 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
858 new UndoManager(this, _frame);
864 * Clear the undo stack (losing all undo information
866 public void clearUndo()
868 // Exit if nothing to undo
869 if (_undoStack == null || _undoStack.isEmpty())
871 // Has track got unsaved data?
872 boolean unsaved = hasDataUnsaved();
873 // Confirm operation with dialog
874 int answer = JOptionPane.showConfirmDialog(_frame,
875 I18nManager.getText("dialog.clearundo.text"),
876 I18nManager.getText("dialog.clearundo.title"),
877 JOptionPane.YES_NO_OPTION);
878 if (answer == JOptionPane.YES_OPTION)
881 _lastSavePosition = 0;
882 if (unsaved) _lastSavePosition = -1;
883 UpdateMessageBroker.informSubscribers();
889 * Undo the specified number of actions
890 * @param inNumUndos number of actions to undo
892 public void undoActions(int inNumUndos)
896 for (int i=0; i<inNumUndos; i++)
898 _undoStack.pop().performUndo(_trackInfo);
900 String message = "" + inNumUndos + " "
901 + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
902 UpdateMessageBroker.informSubscribers(message);
904 catch (UndoException ue)
906 showErrorMessageNoLookup("error.undofailed.title",
907 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
910 catch (EmptyStackException empty) {}
911 UpdateMessageBroker.informSubscribers();
916 * Show a map url in an external browser
917 * @param inSourceIndex index of map source to use
919 public void showExternalMap(int inSourceIndex)
921 BrowserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo));
925 * Display a standard error message
926 * @param inTitleKey key to lookup for window title
927 * @param inMessageKey key to lookup for error message
929 public void showErrorMessage(String inTitleKey, String inMessageKey)
931 JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
932 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
936 * Display a standard error message
937 * @param inTitleKey key to lookup for window title
938 * @param inMessage error message
940 public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
942 JOptionPane.showMessageDialog(_frame, inMessage,
943 I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
947 * @param inViewport viewport object
949 public void setViewport(Viewport inViewport)
951 _viewport = inViewport;
955 * @return current viewport object
957 public Viewport getViewport()
963 * Set the controller for the full screen mode
964 * @param inController controller object
966 public void setSidebarController(SidebarController inController)
968 _sidebarController = inController;
972 * Toggle sidebars on and off
974 public void toggleSidebars()
976 _sidebarController.toggle();
979 /** @return true if App is currently busy with loading data */
980 public boolean isBusyLoading() {
984 /** @return current app mode */
985 public AppMode getCurrentMode() {
989 /** @param inMode the current app mode */
990 public void setCurrentMode(AppMode inMode) {