package tim.prune;
import java.io.File;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import tim.prune.config.Config;
import tim.prune.data.Checker;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.LatLonRectangle;
import tim.prune.data.NumberUtils;
import tim.prune.data.Photo;
import tim.prune.data.PhotoList;
import tim.prune.data.PointCreateOptions;
import tim.prune.data.RecentFile;
import tim.prune.data.SourceInfo;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
import tim.prune.data.SourceInfo.FILE_TYPE;
import tim.prune.data.Unit;
import tim.prune.function.AsyncMediaLoader;
import tim.prune.function.SaveConfig;
import tim.prune.function.SelectTracksFunction;
import tim.prune.function.edit.FieldEditList;
import tim.prune.function.edit.PointEditor;
import tim.prune.gui.MenuManager;
import tim.prune.gui.SidebarController;
import tim.prune.gui.UndoManager;
import tim.prune.gui.Viewport;
import tim.prune.gui.colour.ColourerCaretaker;
import tim.prune.gui.colour.PointColourer;
import tim.prune.load.FileLoader;
import tim.prune.load.JpegLoader;
import tim.prune.load.MediaLinkInfo;
import tim.prune.load.TrackNameList;
import tim.prune.save.ExifSaver;
import tim.prune.save.FileSaver;
import tim.prune.tips.TipManager;
import tim.prune.undo.*;
/**
* Main controller for the application
*/
public class App
{
// Instance variables
private JFrame _frame = null;
private Track _track = null;
private TrackInfo _trackInfo = null;
private int _lastSavePosition = 0;
private MenuManager _menuManager = null;
private SidebarController _sidebarController = null;
private FileLoader _fileLoader = null;
private JpegLoader _jpegLoader = null;
private FileSaver _fileSaver = null;
private UndoStack _undoStack = null;
private ColourerCaretaker _colCaretaker = null;
private boolean _mangleTimestampsConfirmed = false;
private Viewport _viewport = null;
private ArrayList<File> _dataFiles = null;
private boolean _autoAppendNextFile = false;
private boolean _busyLoading = false;
private AppMode _appMode = AppMode.NORMAL;
/** Enum for the app mode - currently only two options but may expand later */
public enum AppMode {NORMAL, DRAWRECT};
/**
* Constructor
* @param inFrame frame object for application
*/
public App(JFrame inFrame)
{
_frame = inFrame;
_undoStack = new UndoStack();
_track = new Track();
_trackInfo = new TrackInfo(_track);
FunctionLibrary.initialise(this);
_colCaretaker = new ColourerCaretaker(this);
UpdateMessageBroker.addSubscriber(_colCaretaker);
_colCaretaker.setColourer(Config.getPointColourer());
}
/**
* @return the current TrackInfo
*/
public TrackInfo getTrackInfo()
{
return _trackInfo;
}
/**
* @return the dialog frame
*/
public JFrame getFrame()
{
return _frame;
}
/**
* Check if the application has unsaved data
* @return true if data is unsaved, false otherwise
*/
public boolean hasDataUnsaved()
{
return (_undoStack.size() > _lastSavePosition
&& (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().hasModifiedMedia()));
}
/**
* @return the undo stack
*/
public UndoStack getUndoStack()
{
return _undoStack;
}
/**
* Update the system's point colourer using the one in the Config
*/
public void updatePointColourer()
{
if (_colCaretaker != null) {
_colCaretaker.setColourer(Config.getPointColourer());
}
}
/**
* @return colourer object, or null
*/
public PointColourer getPointColourer()
{
if (_colCaretaker == null) {return null;}
return _colCaretaker.getColourer();
}
/**
* Show the specified tip if appropriate
* @param inTipNumber tip number from TipManager
*/
public void showTip(int inTipNumber)
{
String key = TipManager.fireTipTrigger(inTipNumber);
if (key != null && !key.equals(""))
{
JOptionPane.showMessageDialog(_frame, I18nManager.getText(key),
I18nManager.getText("tip.title"), JOptionPane.INFORMATION_MESSAGE);
}
}
/**
* Load the specified data files one by one
* @param inDataFiles arraylist containing File objects to load
*/
public void loadDataFiles(ArrayList<File> inDataFiles)
{
if (inDataFiles == null || inDataFiles.size() == 0) {
_dataFiles = null;
}
else
{
_dataFiles = inDataFiles;
File f = _dataFiles.get(0);
_dataFiles.remove(0);
// Start load of specified file
if (_fileLoader == null)
_fileLoader = new FileLoader(this, _frame);
_autoAppendNextFile = false; // prompt for append
_fileLoader.openFile(f);
}
}
/**
* Complete a function execution
* @param inUndo undo object to be added to stack
* @param inConfirmText confirmation text
*/
public void completeFunction(UndoOperation inUndo, String inConfirmText)
{
_undoStack.add(inUndo);
UpdateMessageBroker.informSubscribers(inConfirmText);
setCurrentMode(AppMode.NORMAL);
}
/**
* Set the MenuManager object to be informed about changes
* @param inManager MenuManager object
*/
public void setMenuManager(MenuManager inManager)
{
_menuManager = inManager;
}
/**
* Open a file containing track or waypoint data
*/
public void openFile()
{
if (_fileLoader == null)
_fileLoader = new FileLoader(this, _frame);
_fileLoader.openFile();
}
/**
* Add a photo or a directory of photos
*/
public void addPhotos()
{
if (_jpegLoader == null)
_jpegLoader = new JpegLoader(this, _frame);
_jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange()));
}
/**
* Save the file in the selected format
*/
public void saveFile()
{
if (_track == null) {
showErrorMessage("error.save.dialogtitle", "error.save.nodata");
}
else
{
if (_fileSaver == null) {
_fileSaver = new FileSaver(this, _frame);
}
char delim = ',';
if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();}
_fileSaver.showDialog(delim);
}
}
/**
* Exit the application if confirmed
*/
public void exit()
{
// grab focus
_frame.toFront();
_frame.requestFocus();
// check if ok to exit
Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
if (!hasDataUnsaved()
|| JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.YES_OPTION)
{
// save settings
if (Config.getConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS)) {
new SaveConfig(this).silentSave();
}
System.exit(0);
}
}
/**
* Edit the currently selected point
*/
public void editCurrentPoint()
{
if (_track != null)
{
DataPoint currentPoint = _trackInfo.getCurrentPoint();
if (currentPoint != null)
{
// Open point dialog to display details
PointEditor editor = new PointEditor(this, _frame);
editor.showDialog(_track, currentPoint);
}
}
}
/**
* Complete the point edit
* @param inEditList field values to edit
* @param inUndoList field values before edit
*/
public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
{
DataPoint currentPoint = _trackInfo.getCurrentPoint();
if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
{
// add information to undo stack
UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
// pass to track for completion
if (_track.editPoint(currentPoint, inEditList, false))
{
_undoStack.add(undo);
// Confirm point edit
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit"));
}
}
}
/**
* Delete the currently selected point
*/
public void deleteCurrentPoint()
{
if (_track == null) {return;}
DataPoint currentPoint = _trackInfo.getCurrentPoint();
if (currentPoint != null)
{
// Check for photo
boolean deletePhoto = false;
Photo currentPhoto = currentPoint.getPhoto();
if (currentPhoto != null)
{
// Confirm deletion of photo or decoupling
int response = JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.deletepoint.deletephoto") + " " + currentPhoto.getName(),
I18nManager.getText("dialog.deletepoint.title"),
JOptionPane.YES_NO_CANCEL_OPTION);
if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION)
{
// cancel pressed- abort delete
return;
}
if (response == JOptionPane.YES_OPTION) {deletePhoto = true;}
}
// store necessary information to undo it later
int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto);
int audioIndex = _trackInfo.getAudioList().getAudioIndex(currentPoint.getAudio());
DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
// Construct Undo object
UndoDeletePoint undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
audioIndex, nextTrackPoint != null && nextTrackPoint.getSegmentStart());
undo.setAtBoundaryOfSelectedRange(pointIndex == _trackInfo.getSelection().getStart() ||
pointIndex == _trackInfo.getSelection().getEnd());
// call track to delete point
if (_trackInfo.deletePoint())
{
// Delete was successful so add undo info to stack
_undoStack.add(undo);
if (currentPhoto != null)
{
// delete photo if necessary
if (deletePhoto)
{
_trackInfo.getPhotoList().deletePhoto(photoIndex);
}
else
{
// decouple photo from point
currentPhoto.setDataPoint(null);
}
UpdateMessageBroker.informSubscribers(DataSubscriber.PHOTOS_MODIFIED);
}
// Delete audio object (without bothering to ask)
if (audioIndex > -1) {
_trackInfo.getAudioList().deleteAudio(audioIndex);
}
// Confirm
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single"));
UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_ADDED_OR_REMOVED);
}
}
}
/**
* Reverse the currently selected section of the track
*/
public void reverseRange()
{
// check whether Timestamp field exists, and if so confirm reversal
int selStart = _trackInfo.getSelection().getStart();
int selEnd = _trackInfo.getSelection().getEnd();
if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
|| _mangleTimestampsConfirmed
|| (JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.confirmreversetrack.text"),
I18nManager.getText("dialog.confirmreversetrack.title"),
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
{
UndoReverseSection undo = new UndoReverseSection(_track, selStart, selEnd);
// call track to reverse range
if (_track.reverseRange(selStart, selEnd))
{
_undoStack.add(undo);
// Confirm
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.reverserange"));
}
}
}
/**
* Complete the add time offset function with the specified offset
* @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
*/
public void finishAddTimeOffset(long inTimeOffset)
{
// Construct undo information
int selStart = _trackInfo.getSelection().getStart();
int selEnd = _trackInfo.getSelection().getEnd();
UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
{
_undoStack.add(undo);
UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset"));
}
}
/**
* Complete the add altitude offset function with the specified offset
* @param inOffset altitude offset to add as String
* @param inUnit altitude units of offset (eg Feet, Metres)
*/
public void finishAddAltitudeOffset(String inOffset, Unit inUnit)
{
// Sanity check
if (inOffset == null || inOffset.equals("") || inUnit == null) {
return;
}
// Construct undo information
UndoAddAltitudeOffset undo = new UndoAddAltitudeOffset(_trackInfo);
int selStart = _trackInfo.getSelection().getStart();
int selEnd = _trackInfo.getSelection().getEnd();
// How many decimal places are given in the offset?
int numDecimals = NumberUtils.getDecimalPlaces(inOffset);
boolean success = false;
// Decimal offset given
try {
double offsetd = Double.parseDouble(inOffset);
success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inUnit, numDecimals);
}
catch (NumberFormatException nfe) {}
if (success)
{
_undoStack.add(undo);
_trackInfo.getSelection().markInvalid();
UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addaltitudeoffset"));
}
}
/**
* Merge the track segments within the current selection
*/
public void mergeTrackSegments()
{
if (_trackInfo.getSelection().hasRangeSelected())
{
// Maybe could check segment start flags to see if it's worth merging
// If first track point is already start and no other seg starts then do nothing
int selStart = _trackInfo.getSelection().getStart();
int selEnd = _trackInfo.getSelection().getEnd();
// Make undo object
UndoMergeTrackSegments undo = new UndoMergeTrackSegments(_track, selStart, selEnd);
// Call track to merge segments
if (_trackInfo.mergeTrackSegments(selStart, selEnd)) {
_undoStack.add(undo);
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments"));
}
}
}
/**
* Average the selected points
*/
public void averageSelection()
{
// Find following track point
DataPoint nextPoint = _track.getNextTrackPoint(_trackInfo.getSelection().getEnd() + 1);
boolean segFlag = false;
if (nextPoint != null) {segFlag = nextPoint.getSegmentStart();}
UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getEnd() + 1, 1, nextPoint != null, segFlag);
// call track info object to do the averaging
if (_trackInfo.average())
{
_undoStack.add(undo);
}
}
/**
* Create a new point at the end of the track
* @param inPoint point to add
*/
public void createPoint(DataPoint inPoint)
{
createPoint(inPoint, true);
}
/**
* Create a new point at the end of the track
* @param inPoint point to add
* @param inNewSegment true for a single point, false for a continuation
*/
public void createPoint(DataPoint inPoint, boolean inNewSegment)
{
// create undo object
UndoCreatePoint undo = new UndoCreatePoint();
_undoStack.add(undo);
// add point to track
inPoint.setSegmentStart(inNewSegment);
_track.appendPoints(new DataPoint[] {inPoint});
// ensure track's field list contains point's fields
_track.extendFieldList(inPoint.getFieldList());
_trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
// update listeners
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
}
/**
* Create a new point before the given position
* @param inPoint point to add
* @param inIndex index of following point
*/
public void createPoint(DataPoint inPoint, int inIndex)
{
// create undo object
UndoInsert undo = new UndoInsert(inIndex, 1);
_undoStack.add(undo);
// add point to track
_track.insertPoint(inPoint, inIndex);
// ensure track's field list contains point's fields
_track.extendFieldList(inPoint.getFieldList());
_trackInfo.selectPoint(inIndex);
final int selStart = _trackInfo.getSelection().getStart();
final int selEnd = _trackInfo.getSelection().getEnd();
if (selStart < inIndex && selEnd >= inIndex)
{
// Extend end of selection by 1
_trackInfo.getSelection().selectRange(selStart, selEnd+1);
}
// update listeners
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint"));
}
/**
* Cut the current selection and move it to before the currently selected point
*/
public void cutAndMoveSelection()
{
int startIndex = _trackInfo.getSelection().getStart();
int endIndex = _trackInfo.getSelection().getEnd();
int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
// If timestamps would be mangled by cut/move, confirm
if (!_track.hasData(Field.TIMESTAMP, startIndex, endIndex)
|| _mangleTimestampsConfirmed
|| (JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.confirmcutandmove.text"),
I18nManager.getText("dialog.confirmcutandmove.title"),
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true)))
{
// Find points to set segment flags
DataPoint firstTrackPoint = _track.getNextTrackPoint(startIndex, endIndex);
DataPoint nextTrackPoint = _track.getNextTrackPoint(endIndex+1);
DataPoint moveToTrackPoint = _track.getNextTrackPoint(pointIndex);
// Make undo object
UndoCutAndMove undo = new UndoCutAndMove(_track, startIndex, endIndex, pointIndex);
// Call track info to move track section
if (_track.cutAndMoveSection(startIndex, endIndex, pointIndex))
{
// Set segment start flags (first track point, next track point, move to point)
if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
if (moveToTrackPoint != null) {moveToTrackPoint.setSegmentStart(true);}
// Add undo object to stack, set confirm message
_undoStack.add(undo);
_trackInfo.getSelection().selectRange(-1, -1);
UpdateMessageBroker.informSubscribers();
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove"));
}
}
}
/**
* Select nothing
*/
public void selectNone()
{
// deselect point, range and photo
_trackInfo.getSelection().clearAll();
_track.clearDeletionMarkers();
}
/**
* Receive loaded data and determine whether to filter on tracks or not
* @param inFieldArray array of fields
* @param inDataArray array of data
* @param inSourceInfo information about the source of the data
* @param inTrackNameList information about the track names
*/
public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
SourceInfo inSourceInfo, TrackNameList inTrackNameList)
{
// no link array given
informDataLoaded(inFieldArray, inDataArray, null, inSourceInfo,
inTrackNameList, null);
}
/**
* Receive loaded data and determine whether to filter on tracks or not
* @param inFieldArray array of fields
* @param inDataArray array of data
* @param inOptions creation options such as units
* @param inSourceInfo information about the source of the data
* @param inTrackNameList information about the track names
*/
public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
PointCreateOptions inOptions, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
{
// no link array given
informDataLoaded(inFieldArray, inDataArray, inOptions, inSourceInfo,
inTrackNameList, null);
}
/**
* Receive loaded data and determine whether to filter on tracks or not
* @param inFieldArray array of fields
* @param inDataArray array of data
* @param inOptions creation options such as units
* @param inSourceInfo information about the source of the data
* @param inTrackNameList information about the track names
* @param inLinkInfo links to photo/audio clips
*/
public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, PointCreateOptions inOptions,
SourceInfo inSourceInfo, TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
{
// Check whether loaded array can be properly parsed into a Track
Track loadedTrack = new Track();
loadedTrack.load(inFieldArray, inDataArray, inOptions);
if (loadedTrack.getNumPoints() <= 0)
{
showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
// load next file if there's a queue
loadNextFile();
return;
}
// Check for doubled track
if (Checker.isDoubledTrack(loadedTrack)) {
JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.open.contentsdoubled"),
I18nManager.getText("function.open"), JOptionPane.WARNING_MESSAGE);
}
_busyLoading = true;
// Attach photos and/or audio clips to points
if (inLinkInfo != null)
{
String[] linkArray = inLinkInfo.getLinkArray();
if (linkArray != null) {
new AsyncMediaLoader(this, inLinkInfo.getZipFile(), linkArray, loadedTrack, inSourceInfo.getFile()).begin();
}
}
// Look at TrackNameList, decide whether to filter or not
if (inTrackNameList != null && inTrackNameList.getNumTracks() > 1)
{
// Launch a dialog to let the user choose which tracks to load, then continue
new SelectTracksFunction(this, loadedTrack, inSourceInfo, inTrackNameList).begin();
}
else {
// go directly to load
informDataLoaded(loadedTrack, inSourceInfo);
}
setCurrentMode(AppMode.NORMAL);
}
/**
* Receive loaded data and optionally merge with current Track
* @param inLoadedTrack loaded track
* @param inSourceInfo information about the source of the data
*/
public void informDataLoaded(Track inLoadedTrack, SourceInfo inSourceInfo)
{
// Decide whether to load or append
if (_track.getNumPoints() > 0)
{
// ask whether to replace or append
int answer = 0;
if (_autoAppendNextFile) {
// Automatically append the next file
answer = JOptionPane.YES_OPTION;
}
else {
// Ask whether to append or not
answer = JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.openappend.text"),
I18nManager.getText("dialog.openappend.title"),
JOptionPane.YES_NO_CANCEL_OPTION);
}
_autoAppendNextFile = false; // reset flag to cancel autoappend
if (answer == JOptionPane.YES_OPTION)
{
// append data to current Track
UndoLoad undo = new UndoLoad(_track.getNumPoints(), inLoadedTrack.getNumPoints());
undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
_undoStack.add(undo);
_track.combine(inLoadedTrack);
// set source information
inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
_trackInfo.getFileInfo().addSource(inSourceInfo);
}
else if (answer == JOptionPane.NO_OPTION)
{
// Don't append, replace data
PhotoList photos = null;
if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) {
photos = _trackInfo.getPhotoList().cloneList();
}
UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos);
undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
_undoStack.add(undo);
_lastSavePosition = _undoStack.size();
_trackInfo.getSelection().clearAll();
_track.load(inLoadedTrack);
inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
_trackInfo.getFileInfo().replaceSource(inSourceInfo);
_trackInfo.getPhotoList().removeCorrelatedPhotos();
_trackInfo.getAudioList().removeCorrelatedAudios();
}
}
else
{
// Currently no data held, so transfer received data
UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), null);
undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
_undoStack.add(undo);
_lastSavePosition = _undoStack.size();
_trackInfo.getSelection().clearAll();
_track.load(inLoadedTrack);
inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
_trackInfo.getFileInfo().addSource(inSourceInfo);
}
// Update config before subscribers are told
boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
UpdateMessageBroker.informSubscribers();
// Update status bar
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
+ " '" + inSourceInfo.getName() + "'");
// update menu
_menuManager.informFileLoaded();
// Remove busy lock
_busyLoading = false;
// load next file if there's a queue
loadNextFile();
}
/**
* Inform the app that NO data was loaded, eg cancel pressed
* Only needed if there's another file waiting in the queue
*/
public void informNoDataLoaded()
{
// Load next file if there's a queue
loadNextFile();
}
/**
* External trigger to automatically append the next loaded file
* instead of prompting to replace or append
*/
public void autoAppendNextFile()
{
_autoAppendNextFile = true;
}
/**
* Load the next file in the waiting list, if any
*/
private void loadNextFile()
{
if (_dataFiles == null || _dataFiles.size() == 0) {
_dataFiles = null;
}
else {
new Thread(new Runnable() {
public void run() {
File f = _dataFiles.get(0);
_dataFiles.remove(0);
_autoAppendNextFile = true;
_fileLoader.openFile(f);
}
}).start();
}
}
/**
* Accept a list of loaded photos
* @param inPhotoSet Set of Photo objects
*/
public void informPhotosLoaded(Set<Photo> inPhotoSet)
{
if (inPhotoSet != null && !inPhotoSet.isEmpty())
{
int[] numsAdded = _trackInfo.addPhotos(inPhotoSet);
int numPhotosAdded = numsAdded[0];
int numPointsAdded = numsAdded[1];
if (numPhotosAdded > 0)
{
// Save numbers so load can be undone
_undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded));
}
if (numPhotosAdded == 1) {
UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single"));
}
else {
UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi"));
}
// MAYBE: Improve message when photo(s) fail to load (eg already added)
UpdateMessageBroker.informSubscribers();
// update menu
if (numPointsAdded > 0) _menuManager.informFileLoaded();
}
}
/**
* Save the coordinates of photos in their exif data
*/
public void saveExif()
{
ExifSaver saver = new ExifSaver(_frame);
saver.saveExifInformation(_trackInfo.getPhotoList());
}
/**
* Inform the app that the data has been saved
*/
public void informDataSaved()
{
_lastSavePosition = _undoStack.size();
}
/**
* Begin undo process
*/
public void beginUndo()
{
if (_undoStack.isEmpty())
{
// Nothing to undo
JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
}
else
{
new UndoManager(this, _frame).show();
}
}
/**
* Clear the undo stack (losing all undo information
*/
public void clearUndo()
{
// Exit if nothing to undo
if (_undoStack == null || _undoStack.isEmpty())
return;
// Has track got unsaved data?
boolean unsaved = hasDataUnsaved();
// Confirm operation with dialog
int answer = JOptionPane.showConfirmDialog(_frame,
I18nManager.getText("dialog.clearundo.text"),
I18nManager.getText("dialog.clearundo.title"),
JOptionPane.YES_NO_OPTION);
if (answer == JOptionPane.YES_OPTION)
{
_undoStack.clear();
_lastSavePosition = 0;
if (unsaved) _lastSavePosition = -1;
UpdateMessageBroker.informSubscribers();
}
}
/**
* Undo the specified number of actions
* @param inNumUndos number of actions to undo
*/
public void undoActions(int inNumUndos)
{
try
{
for (int i=0; i<inNumUndos; i++)
{
_undoStack.popOperation().performUndo(_trackInfo);
}
String message = "" + inNumUndos + " "
+ (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi"));
UpdateMessageBroker.informSubscribers(message);
}
catch (UndoException ue)
{
showErrorMessageNoLookup("error.undofailed.title",
I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage());
_undoStack.clear();
}
catch (EmptyStackException empty) {}
UpdateMessageBroker.informSubscribers();
}
/**
* @return the current data status, used for later comparison
*/
public DataStatus getCurrentDataStatus()
{
return new DataStatus(_undoStack.size(), _undoStack.getNumUndos());
}
/**
* Display a standard error message
* @param inTitleKey key to lookup for window title
* @param inMessageKey key to lookup for error message
*/
public void showErrorMessage(String inTitleKey, String inMessageKey)
{
JOptionPane.showMessageDialog(_frame, I18nManager.getText(inMessageKey),
I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
}
/**
* Display a standard error message
* @param inTitleKey key to lookup for window title
* @param inMessage error message
*/
public void showErrorMessageNoLookup(String inTitleKey, String inMessage)
{
JOptionPane.showMessageDialog(_frame, inMessage,
I18nManager.getText(inTitleKey), JOptionPane.ERROR_MESSAGE);
}
/**
* @param inViewport viewport object
*/
public void setViewport(Viewport inViewport)
{
_viewport = inViewport;
}
/**
* @return current viewport object
*/
public Viewport getViewport()
{
return _viewport;
}
/**
* Set the controller for the full screen mode
* @param inController controller object
*/
public void setSidebarController(SidebarController inController)
{
_sidebarController = inController;
}
/**
* Toggle sidebars on and off
*/
public void toggleSidebars()
{
_sidebarController.toggle();
}
/** @return true if App is currently busy with loading data */
public boolean isBusyLoading() {
return _busyLoading;
}
/** @return current app mode */
public AppMode getCurrentMode() {
return _appMode;
}
/** @param inMode the current app mode */
public void setCurrentMode(AppMode inMode) {
_appMode = inMode;
}
}