X-Git-Url: https://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=tim%2Fprune%2FApp.java;h=686b3dee485d4b3ffcd58203f2a3e5445be9bb17;hp=18a2e79a60fa737cde270045cb0d3b6263dc278f;hb=8c8868ae29b3252f02e094c02307384cf61ba667;hpb=da0b1f449260a0b4a94318006382a9039726ef3e diff --git a/tim/prune/App.java b/tim/prune/App.java index 18a2e79..686b3de 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -1,5 +1,7 @@ package tim.prune; +import java.io.File; +import java.util.ArrayList; import java.util.EmptyStackException; import java.util.Set; import java.util.Stack; @@ -7,45 +9,39 @@ import java.util.Stack; import javax.swing.JFrame; import javax.swing.JOptionPane; -import tim.prune.correlate.PhotoCorrelator; -import tim.prune.correlate.PointPair; +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.edit.FieldEditList; -import tim.prune.edit.PointEditor; -import tim.prune.edit.PointNameEditor; +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.browser.BrowserLauncher; +import tim.prune.function.browser.UrlGenerator; +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.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.save.GpxExporter; -import tim.prune.save.KmlExporter; -import tim.prune.save.PovExporter; -import tim.prune.threedee.ThreeDException; -import tim.prune.threedee.ThreeDWindow; -import tim.prune.threedee.WindowFactory; -import tim.prune.undo.UndoCompress; -import tim.prune.undo.UndoConnectPhoto; -import tim.prune.undo.UndoCorrelatePhotos; -import tim.prune.undo.UndoDeleteDuplicates; -import tim.prune.undo.UndoDeletePhoto; -import tim.prune.undo.UndoDeletePoint; -import tim.prune.undo.UndoDeleteRange; -import tim.prune.undo.UndoDisconnectPhoto; -import tim.prune.undo.UndoEditPoint; -import tim.prune.undo.UndoException; -import tim.prune.undo.UndoInsert; -import tim.prune.undo.UndoLoad; -import tim.prune.undo.UndoLoadPhotos; -import tim.prune.undo.UndoOperation; -import tim.prune.undo.UndoRearrangeWaypoints; -import tim.prune.undo.UndoReverseSection; +import tim.prune.undo.*; /** @@ -59,34 +55,33 @@ public class App 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 KmlExporter _kmlExporter = null; - private GpxExporter _gpxExporter = null; - private PovExporter _povExporter = null; - private Stack _undoStack = null; - private UpdateMessageBroker _broker = null; - private boolean _reversePointsConfirmed = false; + private Stack _undoStack = null; + private boolean _mangleTimestampsConfirmed = false; + private Viewport _viewport = null; + private ArrayList _dataFiles = null; + private boolean _autoAppendNextFile = false; + private boolean _busyLoading = false; + private AppMode _appMode = AppMode.NORMAL; - // Constants - public static final int REARRANGE_TO_START = 0; - public static final int REARRANGE_TO_END = 1; - public static final int REARRANGE_TO_NEAREST = 2; + /** 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 - * @param inBroker message broker */ - public App(JFrame inFrame, UpdateMessageBroker inBroker) + public App(JFrame inFrame) { _frame = inFrame; - _undoStack = new Stack(); - _broker = inBroker; - _track = new Track(_broker); - _trackInfo = new TrackInfo(_track, _broker); + _undoStack = new Stack(); + _track = new Track(); + _trackInfo = new TrackInfo(_track); + FunctionLibrary.initialise(this); } @@ -98,6 +93,14 @@ public class App 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 @@ -105,17 +108,51 @@ public class App public boolean hasDataUnsaved() { return (_undoStack.size() > _lastSavePosition - && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0)); + && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().hasModifiedMedia())); } /** * @return the undo stack */ - public Stack getUndoStack() + public Stack getUndoStack() { return _undoStack; } + /** + * Load the specified data files one by one + * @param inDataFiles arraylist containing File objects to load + */ + public void loadDataFiles(ArrayList 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 @@ -138,131 +175,31 @@ public class App /** - * Add a photo or a directory of photos which are already correlated + * Add a photo or a directory of photos */ public void addPhotos() { if (_jpegLoader == null) _jpegLoader = new JpegLoader(this, _frame); - _jpegLoader.openFile(); + _jpegLoader.openDialog(new LatLonRectangle(_track.getLatRange(), _track.getLonRange())); } - /** * Save the file in the selected format */ public void saveFile() { - if (_track == null) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"), - I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); + if (_track == null) { + showErrorMessage("error.save.dialogtitle", "error.save.nodata"); } else { if (_fileSaver == null) { - _fileSaver = new FileSaver(this, _frame, _track); - } - _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter()); - } - } - - - /** - * Export track data as Kml - */ - public void exportKml() - { - if (_track == null) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"), - I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); - } - else - { - // Invoke the export - if (_kmlExporter == null) - { - _kmlExporter = new KmlExporter(_frame, _trackInfo); - } - _kmlExporter.showDialog(); - } - } - - - /** - * Export track data as Gpx - */ - public void exportGpx() - { - if (_track == null) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"), - I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); - } - else - { - // Invoke the export - if (_gpxExporter == null) - { - _gpxExporter = new GpxExporter(_frame, _trackInfo); - } - _gpxExporter.showDialog(); - } - } - - - /** - * Export track data as Pov without specifying settings - */ - public void exportPov() - { - exportPov(false, 0.0, 0.0, 0.0, 0); - } - - /** - * Export track data as Pov and also specify settings - * @param inX X component of unit vector - * @param inY Y component of unit vector - * @param inZ Z component of unit vector - * @param inAltitudeCap altitude cap - */ - public void exportPov(double inX, double inY, double inZ, int inAltitudeCap) - { - exportPov(true, inX, inY, inZ, inAltitudeCap); - } - - /** - * Export track data as Pov with optional angle specification - * @param inDefineAngles true to define angles, false to ignore - * @param inX X component of unit vector - * @param inY Y component of unit vector - * @param inZ Z component of unit vector - * @param inAltitudeCap altitude cap - */ - private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap) - { - // Check track has data to export - if (_track == null || _track.getNumPoints() <= 0) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"), - I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); - } - else - { - // Make new exporter if necessary - if (_povExporter == null) - { - _povExporter = new PovExporter(_frame, _track); - } - // Specify angles if necessary - if (inDefineSettings) - { - _povExporter.setCameraCoordinates(inX, inY, inZ); - _povExporter.setAltitudeCap(inAltitudeCap); + _fileSaver = new FileSaver(this, _frame); } - // Initiate export - _povExporter.showDialog(); + char delim = ','; + if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();} + _fileSaver.showDialog(delim); } } @@ -283,6 +220,10 @@ public class App 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); } } @@ -319,27 +260,11 @@ public class App // add information to undo stack UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList); // pass to track for completion - if (_track.editPoint(currentPoint, inEditList)) + if (_track.editPoint(currentPoint, inEditList, false)) { _undoStack.push(undo); - } - } - } - - - /** - * Edit the name of the currently selected (way)point - */ - public void editCurrentPointName() - { - if (_track != null) - { - DataPoint currentPoint = _trackInfo.getCurrentPoint(); - if (currentPoint != null) - { - // Open point dialog to display details - PointNameEditor editor = new PointNameEditor(this, _frame); - editor.showDialog(currentPoint); + // Confirm point edit + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit")); } } } @@ -350,210 +275,89 @@ public class App */ public void deleteCurrentPoint() { - if (_track != null) + if (_track == null) {return;} + DataPoint currentPoint = _trackInfo.getCurrentPoint(); + if (currentPoint != null) { - DataPoint currentPoint = _trackInfo.getCurrentPoint(); - if (currentPoint != null) + // Check for photo + boolean deletePhoto = false; + Photo currentPhoto = currentPoint.getPhoto(); + if (currentPhoto != null) { - 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.getFile().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;} - } - // add information to undo stack - int pointIndex = _trackInfo.getSelection().getCurrentPointIndex(); - int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto); - // Undo object needs to know index of photo in list (if any) to restore - UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex); - // call track to delete point - if (_trackInfo.deletePoint()) + // 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) { - _undoStack.push(undo); - if (currentPhoto != null) - { - // delete photo if necessary - if (deletePhoto) - { - _trackInfo.getPhotoList().deletePhoto(photoIndex); - } - else - { - // decouple photo from point - currentPhoto.setDataPoint(null); - } - } + // cancel pressed- abort delete + return; } + if (response == JOptionPane.YES_OPTION) {deletePhoto = true;} } - } - } - - - /** - * Delete the currently selected range - */ - public void deleteSelectedRange() - { - if (_track != null) - { - // Find out if photos should be deleted or not - int selStart = _trackInfo.getSelection().getStart(); - int selEnd = _trackInfo.getSelection().getEnd(); - if (selStart >= 0 && selEnd >= selStart) + // 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 + UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex, + audioIndex, nextTrackPoint != null && nextTrackPoint.getSegmentStart()); + // call track to delete point + if (_trackInfo.deletePoint()) { - int numToDelete = selEnd - selStart + 1; - boolean[] deletePhotos = new boolean[numToDelete]; - Photo[] photosToDelete = new Photo[numToDelete]; - boolean deleteAll = false; - boolean deleteNone = false; - String[] questionOptions = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"), - I18nManager.getText("button.yestoall"), I18nManager.getText("button.notoall"), - I18nManager.getText("button.cancel")}; - DataPoint point = null; - for (int i=0; i -1) { + _trackInfo.getAudioList().deleteAudio(audioIndex); } + // Confirm + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single")); + UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_ADDED_OR_REMOVED); } } } /** - * Delete all the duplicate points in the track + * Finish the compression by deleting the marked points */ - public void deleteDuplicates() + public void finishCompressTrack() { - if (_track != null) - { - // Save undo information - UndoOperation undo = new UndoDeleteDuplicates(_track); - // tell track to do it - int numDeleted = _trackInfo.deleteDuplicates(); - if (numDeleted > 0) - { - _undoStack.add(undo); - String message = null; - if (numDeleted == 1) - { - message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text"); - } - else - { - message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text"); - } - JOptionPane.showMessageDialog(_frame, message, - I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE); - } - else - { - JOptionPane.showMessageDialog(_frame, - I18nManager.getText("dialog.deleteduplicates.nonefound"), - I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE); - } - } - } - - - /** - * Compress the track - */ - public void compressTrack() - { - UndoCompress undo = new UndoCompress(_track); - // Get compression parameter - Object compParam = JOptionPane.showInputDialog(_frame, - I18nManager.getText("dialog.compresstrack.parameter.text"), - I18nManager.getText("dialog.compresstrack.title"), - JOptionPane.QUESTION_MESSAGE, null, null, "100"); - int compNumber = parseNumber(compParam); - if (compNumber <= 0) return; + UndoDeleteMarked undo = new UndoDeleteMarked(_track); // call track to do compress - int numPointsDeleted = _trackInfo.compress(compNumber); + int numPointsDeleted = _trackInfo.deleteMarkedPoints(); // add to undo stack if successful if (numPointsDeleted > 0) { undo.setNumPointsDeleted(numPointsDeleted); _undoStack.add(undo); - JOptionPane.showMessageDialog(_frame, - I18nManager.getText("dialog.compresstrack.text") + " - " - + numPointsDeleted + " " - + (numPointsDeleted==1?I18nManager.getText("dialog.compresstrack.single.text"):I18nManager.getText("dialog.compresstrack.multi.text")), - I18nManager.getText("dialog.compresstrack.title"), JOptionPane.INFORMATION_MESSAGE); + UpdateMessageBroker.informSubscribers("" + numPointsDeleted + " " + + (numPointsDeleted==1?I18nManager.getText("confirm.deletepoint.single"):I18nManager.getText("confirm.deletepoint.multi"))); } - else - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.compresstrack.nonefound"), - I18nManager.getText("dialog.compresstrack.title"), JOptionPane.WARNING_MESSAGE); + else { + showErrorMessage("function.compress", "dialog.deletemarked.nonefound"); } } - /** - * Reverse a section of the track + * Reverse the currently selected section of the track */ public void reverseRange() { @@ -561,109 +365,201 @@ public class App int selStart = _trackInfo.getSelection().getStart(); int selEnd = _trackInfo.getSelection().getEnd(); if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd) - || _reversePointsConfirmed + || _mangleTimestampsConfirmed || (JOptionPane.showConfirmDialog(_frame, I18nManager.getText("dialog.confirmreversetrack.text"), I18nManager.getText("dialog.confirmreversetrack.title"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true))) + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_mangleTimestampsConfirmed = true))) { - UndoReverseSection undo = new UndoReverseSection(selStart, selEnd); + 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")); } } } - /** - * Interpolate the two selected points + * Complete the add time offset function with the specified offset + * @param inTimeOffset time offset to add (+ve for add, -ve for subtract) */ - public void interpolateSelection() + public void finishAddTimeOffset(long inTimeOffset) { - // Get number of points to add - Object numPointsStr = JOptionPane.showInputDialog(_frame, - I18nManager.getText("dialog.interpolate.parameter.text"), - I18nManager.getText("dialog.interpolate.title"), - JOptionPane.QUESTION_MESSAGE, null, null, ""); - int numPoints = parseNumber(numPointsStr); - if (numPoints <= 0) return; - - UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1, - numPoints); - // call track to interpolate - if (_trackInfo.interpolate(numPoints)) + // 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")); } } /** - * Rearrange the waypoints into track order - * @param inFunction nearest point, all to end or all to start + * 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 rearrangeWaypoints(int inFunction) + public void finishAddAltitudeOffset(String inOffset, Unit inUnit) { - UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(_track); - boolean success = false; - if (inFunction == REARRANGE_TO_START || inFunction == REARRANGE_TO_END) - { - // Collect the waypoints to the start or end of the track - success = _track.collectWaypoints(inFunction == REARRANGE_TO_START); + // Sanity check + if (inOffset == null || inOffset.equals("") || inUnit == null) { + return; } - else - { - // Interleave the waypoints into track order - success = _track.interleaveWaypoints(); + // 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")); } - else + } + + + /** + * Merge the track segments within the current selection + */ + public void mergeTrackSegments() + { + if (_trackInfo.getSelection().hasRangeSelected()) { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"), - I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE); + // 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")); + } } } /** - * Open a new window with the 3d view + * Average the selected points */ - public void show3dWindow() + public void averageSelection() { - ThreeDWindow window = WindowFactory.getWindow(this, _frame); - if (window == null) + // 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()) { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"), - I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE); + _undoStack.add(undo); } - else + } + + + /** + * Create a new point at the end of the track + * @param inPoint point to add + */ + public void createPoint(DataPoint inPoint) + { + // create undo object + UndoCreatePoint undo = new UndoCreatePoint(); + _undoStack.add(undo); + // add point to track + inPoint.setSegmentStart(true); + _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) { - try - { - // Pass the track object and show the window - window.setTrack(_track); - window.show(); - } - catch (ThreeDException e) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(), - I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE); - } + // Extend end of selection by 1 + _trackInfo.getSelection().selectRange(selStart, selEnd+1); } + // update listeners + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.createpoint")); } /** - * Select all points + * Cut the current selection and move it to before the currently selected point */ - public void selectAll() + public void cutAndMoveSelection() { - _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1); + 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")); + } + } } /** @@ -673,309 +569,239 @@ public class App { // 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 optionally merge with current Track + * Receive loaded data and determine whether to filter on tracks or not * @param inFieldArray array of fields * @param inDataArray array of data - * @param inAltFormat altitude format - * @param inFilename filename used + * @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, int inAltFormat, String inFilename) + 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(_broker); - loadedTrack.load(inFieldArray, inDataArray, inAltFormat); + Track loadedTrack = new Track(); + loadedTrack.load(inFieldArray, inDataArray, inOptions); if (loadedTrack.getNumPoints() <= 0) { - JOptionPane.showMessageDialog(_frame, - I18nManager.getText("error.load.nopoints"), - I18nManager.getText("error.load.dialogtitle"), - JOptionPane.ERROR_MESSAGE); + 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 != null && _track.getNumPoints() > 0) + if (_track.getNumPoints() > 0) { // ask whether to replace or append - int answer = JOptionPane.showConfirmDialog(_frame, - I18nManager.getText("dialog.openappend.text"), - I18nManager.getText("dialog.openappend.title"), - JOptionPane.YES_NO_CANCEL_OPTION); + 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 - _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints())); - _track.combine(loadedTrack); - // set filename if currently empty - if (_trackInfo.getFileInfo().getNumFiles() == 0) - { - _trackInfo.getFileInfo().setFile(inFilename); - } - else - { - _trackInfo.getFileInfo().addFile(); - } + 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()) - { + if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) { photos = _trackInfo.getPhotoList().cloneList(); } - _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos)); + UndoLoad undo = new UndoLoad(_trackInfo, inLoadedTrack.getNumPoints(), photos); + undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios()); + _undoStack.add(undo); _lastSavePosition = _undoStack.size(); - // TODO: Should be possible to reuse the Track object already loaded? - _trackInfo.selectPoint(null); - _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); - _trackInfo.getFileInfo().setFile(inFilename); - if (photos != null) - { - _trackInfo.getPhotoList().removeCorrelatedPhotos(); - } + _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 use received data - _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null)); + // 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.loadTrack(inFieldArray, inDataArray, inAltFormat); - _trackInfo.getFileInfo().setFile(inFilename); - } - _broker.informSubscribers(); + _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(); } - /** - * Accept a list of loaded photos - * @param inPhotoSet Set of Photo objects + * Inform the app that NO data was loaded, eg cancel pressed + * Only needed if there's another file waiting in the queue */ - public void informPhotosLoaded(Set inPhotoSet) + public void informNoDataLoaded() { - 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) - { - JOptionPane.showMessageDialog(_frame, - "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"), - I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE); - } - else - { - JOptionPane.showMessageDialog(_frame, - "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"), - I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE); - } - // TODO: Improve message when photo(s) fail to load (eg already added) - _broker.informSubscribers(); - // update menu - _menuManager.informFileLoaded(); - } + // Load next file if there's a queue + loadNextFile(); } - /** - * Connect the current photo to the current point + * External trigger to automatically append the next loaded file + * instead of prompting to replace or append */ - public void connectPhotoToPoint() + public void autoAppendNextFile() { - Photo photo = _trackInfo.getCurrentPhoto(); - DataPoint point = _trackInfo.getCurrentPoint(); - if (photo != null && point != null && point.getPhoto() == null) - { - // connect - _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); - photo.setDataPoint(point); - point.setPhoto(photo); - _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED); - } + _autoAppendNextFile = true; } - /** - * Disconnect the current photo from its point + * Load the next file in the waiting list, if any */ - public void disconnectPhotoFromPoint() + private void loadNextFile() { - Photo photo = _trackInfo.getCurrentPhoto(); - if (photo != null && photo.getDataPoint() != null) - { - DataPoint point = photo.getDataPoint(); - _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName())); - // disconnect - photo.setDataPoint(null); - point.setPhoto(null); - _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + if (_dataFiles == null || _dataFiles.size() == 0) { + _dataFiles = null; } - } - - - /** - * Remove the current photo, if any - */ - public void deleteCurrentPhoto() - { - // Delete the current photo, and optionally its point too, keeping undo information - Photo currentPhoto = _trackInfo.getCurrentPhoto(); - if (currentPhoto != null) - { - // Photo is selected, see if it has a point or not - boolean photoDeleted = false; - UndoDeletePhoto undoAction = null; - if (currentPhoto.getDataPoint() == null) - { - // no point attached, so just delete photo - undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(), - null, -1); - photoDeleted = _trackInfo.deleteCurrentPhoto(false); - } - else - { - // point is attached, so need to confirm point deletion - undoAction = new UndoDeletePhoto(currentPhoto, _trackInfo.getSelection().getCurrentPhotoIndex(), - currentPhoto.getDataPoint(), _trackInfo.getTrack().getPointIndex(currentPhoto.getDataPoint())); - int response = JOptionPane.showConfirmDialog(_frame, - I18nManager.getText("dialog.deletephoto.deletepoint"), - I18nManager.getText("dialog.deletephoto.title"), - JOptionPane.YES_NO_CANCEL_OPTION); - boolean deletePointToo = (response == JOptionPane.YES_OPTION); - // Cancel delete if cancel pressed or dialog closed - if (response == JOptionPane.YES_OPTION || response == JOptionPane.NO_OPTION) - { - photoDeleted = _trackInfo.deleteCurrentPhoto(deletePointToo); + else { + new Thread(new Runnable() { + public void run() { + File f = _dataFiles.get(0); + _dataFiles.remove(0); + _autoAppendNextFile = true; + _fileLoader.openFile(f); } - } - // Add undo information to stack if necessary - if (photoDeleted) - { - _undoStack.add(undoAction); - } + }).start(); } } /** - * Begin the photo correlation process by invoking dialog - */ - public void beginCorrelatePhotos() - { - PhotoCorrelator correlator = new PhotoCorrelator(this, _frame); - // TODO: Do we need to keep a reference to this object to reuse it later? - correlator.begin(); - } - - - /** - * Finish the photo correlation process - * @param inPointPairs array of PointPair objects describing operation + * Accept a list of loaded photos + * @param inPhotoSet Set of Photo objects */ - public void finishCorrelatePhotos(PointPair[] inPointPairs) + public void informPhotosLoaded(Set inPhotoSet) { - // TODO: This method is too big for App, but where should it go? - if (inPointPairs != null && inPointPairs.length > 0) + if (inPhotoSet != null && !inPhotoSet.isEmpty()) { - // begin to construct undo information - UndoCorrelatePhotos undo = new UndoCorrelatePhotos(_trackInfo); - // loop over Photos - int arraySize = inPointPairs.length; - int i = 0, numPhotos = 0; - int numPointsToCreate = 0; - PointPair pair = null; - for (i=0; i 0) { - pair = inPointPairs[i]; - if (pair != null && pair.isValid()) - { - if (pair.getMinSeconds() == 0L) - { - // exact match - Photo pointPhoto = pair.getPointBefore().getPhoto(); - if (pointPhoto == null) - { - // photo coincides with photoless point so connect the two - pair.getPointBefore().setPhoto(pair.getPhoto()); - pair.getPhoto().setDataPoint(pair.getPointBefore()); - } - else if (pointPhoto.equals(pair.getPhoto())) - { - // photo is already connected, nothing to do - } - else - { - // point is already connected to a different photo, so need to clone point - numPointsToCreate++; - } - } - else - { - // photo time falls between two points, so need to interpolate new one - numPointsToCreate++; - } - numPhotos++; - } + // Save numbers so load can be undone + _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded)); } - // Second loop, to create points if necessary - if (numPointsToCreate > 0) - { - // make new array for added points - DataPoint[] addedPoints = new DataPoint[numPointsToCreate]; - int pointNum = 0; - DataPoint pointToAdd = null; - for (i=0; i 0L) - { - // interpolate point - pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction()); - } - if (pointToAdd != null) - { - // link photo to point - pointToAdd.setPhoto(pair.getPhoto()); - pair.getPhoto().setDataPoint(pointToAdd); - // add to point array - addedPoints[pointNum] = pointToAdd; - pointNum++; - } - } - } - // expand track - _track.appendPoints(addedPoints); + if (numPhotosAdded == 1) { + UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single")); } - // add undo information to stack - undo.setNumPhotosCorrelated(numPhotos); - _undoStack.add(undo); - // confirm correlation - JOptionPane.showMessageDialog(_frame, "" + numPhotos + " " - + (numPhotos==1?I18nManager.getText("dialog.correlate.confirmsingle.text"):I18nManager.getText("dialog.correlate.confirmmultiple.text")), - I18nManager.getText("dialog.correlate.title"), - JOptionPane.INFORMATION_MESSAGE); - // observers already informed by track update + 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(); } } @@ -1006,6 +832,7 @@ public class App { if (_undoStack.isEmpty()) { + // Nothing to undo JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"), I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE); } @@ -1036,7 +863,7 @@ public class App _undoStack.clear(); _lastSavePosition = 0; if (unsaved) _lastSavePosition = -1; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } } @@ -1051,53 +878,99 @@ public class App { for (int i=0; i