X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2FApp.java;h=ecde5481a1d269ed23028cb48208d4adcf6abb94;hb=52bf9e8686c916be37a26a0b75340393d4478b05;hp=e403722217d51bbe889168d7e66b03b3496899f8;hpb=5625a1abadb5f2ca5f017fe7dbda1d5141cb637b;p=GpsPrune.git diff --git a/tim/prune/App.java b/tim/prune/App.java index e403722..ecde548 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -1,14 +1,22 @@ package tim.prune; import java.util.EmptyStackException; -import java.util.List; +import java.util.Set; import java.util.Stack; import javax.swing.JFrame; import javax.swing.JOptionPane; +import tim.prune.browser.BrowserLauncher; +import tim.prune.browser.UrlGenerator; +import tim.prune.correlate.PhotoCorrelator; +import tim.prune.correlate.PointPair; +import tim.prune.data.Coordinate; import tim.prune.data.DataPoint; import tim.prune.data.Field; +import tim.prune.data.LatLonRectangle; +import tim.prune.data.Latitude; +import tim.prune.data.Longitude; import tim.prune.data.Photo; import tim.prune.data.PhotoList; import tim.prune.data.Track; @@ -17,28 +25,37 @@ import tim.prune.edit.FieldEditList; import tim.prune.edit.PointEditor; import tim.prune.edit.PointNameEditor; import tim.prune.gui.MenuManager; +import tim.prune.gui.TimeOffsetDialog; import tim.prune.gui.UndoManager; import tim.prune.load.FileLoader; +import tim.prune.load.GpsLoader; import tim.prune.load.JpegLoader; -import tim.prune.load.PhotoMeasurer; 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.UndoAddTimeOffset; import tim.prune.undo.UndoCompress; import tim.prune.undo.UndoConnectPhoto; +import tim.prune.undo.UndoConnectPhotoWithClone; +import tim.prune.undo.UndoCorrelatePhotos; +import tim.prune.undo.UndoCreatePoint; +import tim.prune.undo.UndoCutAndMove; 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.UndoMergeTrackSegments; import tim.prune.undo.UndoOperation; import tim.prune.undo.UndoRearrangeWaypoints; import tim.prune.undo.UndoReverseSection; @@ -57,11 +74,14 @@ public class App private MenuManager _menuManager = null; private FileLoader _fileLoader = null; private JpegLoader _jpegLoader = null; - private KmlExporter _exporter = null; + private GpsLoader _gpsLoader = null; + private FileSaver _fileSaver = null; + private KmlExporter _kmlExporter = null; + private GpxExporter _gpxExporter = null; private PovExporter _povExporter = null; + private BrowserLauncher _browserLauncher = null; private Stack _undoStack = null; - private UpdateMessageBroker _broker = null; - private boolean _reversePointsConfirmed = false; + private boolean _mangleTimestampsConfirmed = false; // Constants public static final int REARRANGE_TO_START = 0; @@ -72,15 +92,13 @@ public class App /** * 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); + _track = new Track(); + _trackInfo = new TrackInfo(_track); } @@ -132,15 +150,24 @@ 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())); } + /** + * Start a load from Gps + */ + public void beginLoadFromGps() + { + if (_gpsLoader == null) + _gpsLoader = new GpsLoader(this, _frame); + _gpsLoader.openDialog(); + } /** * Save the file in the selected format @@ -154,8 +181,12 @@ public class App } else { - FileSaver saver = new FileSaver(this, _frame, _track); - saver.showDialog(_fileLoader.getLastUsedDelimiter()); + if (_fileSaver == null) { + _fileSaver = new FileSaver(this, _frame, _track); + } + char delim = ','; + if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();} + _fileSaver.showDialog(delim); } } @@ -173,11 +204,33 @@ public class App else { // Invoke the export - if (_exporter == null) + if (_kmlExporter == null) { - _exporter = new KmlExporter(_frame, _trackInfo); + _kmlExporter = new KmlExporter(_frame, _trackInfo); } - _exporter.showDialog(); + _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(); } } @@ -208,6 +261,7 @@ public class App * @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) { @@ -277,7 +331,8 @@ public class App /** * Complete the point edit - * @param inEditList list of edits + * @param inEditList field values to edit + * @param inUndoList field values before edit */ public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList) { @@ -290,6 +345,8 @@ public class App if (_track.editPoint(currentPoint, inEditList)) { _undoStack.push(undo); + // Confirm point edit + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit")); } } } @@ -318,50 +375,53 @@ 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) + boolean deletePhoto = false; + Photo currentPhoto = currentPoint.getPhoto(); + if (currentPhoto != null) { - boolean deletePhoto = false; - Photo currentPhoto = currentPoint.getPhoto(); + // 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;} + } + // store necessary information to undo it later + int pointIndex = _trackInfo.getSelection().getCurrentPointIndex(); + int photoIndex = _trackInfo.getPhotoList().getPhotoIndex(currentPhoto); + DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1); + // Construct Undo object + UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex, + nextTrackPoint != null && nextTrackPoint.getSegmentStart()); + // call track to delete point + if (_trackInfo.deletePoint()) + { + // Delete was successful so add undo info to stack + _undoStack.push(undo); 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) + // delete photo if necessary + if (deletePhoto) { - // cancel pressed- abort delete - return; + _trackInfo.getPhotoList().deletePhoto(photoIndex); } - 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()) - { - _undoStack.push(undo); - if (currentPhoto != null) + else { - // delete photo if necessary - if (deletePhoto) - { - _trackInfo.getPhotoList().deletePhoto(photoIndex); - } - else - { - // decouple photo from point - currentPhoto.setDataPoint(null); - } + // decouple photo from point + currentPhoto.setDataPoint(null); } } + // Confirm + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single")); } } } @@ -421,7 +481,7 @@ public class App } } // add information to undo stack - UndoOperation undo = new UndoDeleteRange(_trackInfo); + UndoDeleteRange undo = new UndoDeleteRange(_trackInfo); // delete requested photos for (int i=0; i 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 = JOptionPane.YES_OPTION; + if (!inOverrideAppend) { + answer = JOptionPane.showConfirmDialog(_frame, + I18nManager.getText("dialog.openappend.text"), + I18nManager.getText("dialog.openappend.title"), + JOptionPane.YES_NO_CANCEL_OPTION); + } if (answer == JOptionPane.YES_OPTION) { // append data to current Track @@ -695,6 +899,7 @@ public class App _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos)); _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) @@ -711,7 +916,9 @@ public class App _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); _trackInfo.getFileInfo().setFile(inFilename); } - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); + // Update status bar + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile") + " '" + inFilename + "'"); // update menu _menuManager.informFileLoaded(); } @@ -719,36 +926,30 @@ public class App /** * Accept a list of loaded photos - * @param inPhotoList List of Photo objects + * @param inPhotoSet Set of Photo objects */ - public void informPhotosLoaded(List inPhotoList) + public void informPhotosLoaded(Set inPhotoSet) { - if (inPhotoList != null && !inPhotoList.isEmpty()) + if (inPhotoSet != null && !inPhotoSet.isEmpty()) { - int[] numsAdded = _trackInfo.addPhotos(inPhotoList); + 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)); - // Trigger preloading of photo sizes in separate thread - new PhotoMeasurer(_trackInfo.getPhotoList()).measurePhotos(); } if (numPhotosAdded == 1) { - JOptionPane.showMessageDialog(_frame, - "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"), - I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE); + UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.single")); } else { - JOptionPane.showMessageDialog(_frame, - "" + numPhotosAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"), - I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE); + UpdateMessageBroker.informSubscribers("" + numPhotosAdded + " " + I18nManager.getText("confirm.jpegload.multi")); } // TODO: Improve message when photo(s) fail to load (eg already added) - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); // update menu _menuManager.informFileLoaded(); } @@ -762,13 +963,58 @@ public class App { Photo photo = _trackInfo.getCurrentPhoto(); DataPoint point = _trackInfo.getCurrentPoint(); - if (photo != null && point != null && point.getPhoto() == null) + if (photo != null && point != null) { - // connect - _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); - photo.setDataPoint(point); - point.setPhoto(photo); - //TODO: Confirm connect (maybe with status in photo panel?) + if (point.getPhoto() != null) + { + // point already has a photo, confirm cloning of new point + if (JOptionPane.showConfirmDialog(_frame, + I18nManager.getText("dialog.connectphoto.clonepoint"), + I18nManager.getText("dialog.connect.title"), + JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) + { + // Create undo, clone point and attach + int pointIndex = _trackInfo.getSelection().getCurrentPointIndex() + 1; + // insert new point after current one + point = point.clonePoint(); + UndoConnectPhotoWithClone undo = new UndoConnectPhotoWithClone( + point, photo.getFile().getName(), pointIndex); + _track.insertPoint(point, pointIndex); + photo.setDataPoint(point); + point.setPhoto(photo); + _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect")); + } + } + else + { + // point doesn't currently have a photo, so just connect it + _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); + photo.setDataPoint(point); + point.setPhoto(photo); + UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.connect")); + } + } + } + + + /** + * Disconnect the current photo from its point + */ + public void disconnectPhotoFromPoint() + { + 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); + UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.photo.disconnect")); } } @@ -817,6 +1063,117 @@ public class App } + /** + * 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 Photo Correlator object to reuse it later? + correlator.begin(); + } + + + /** + * Finish the photo correlation process + * @param inPointPairs array of PointPair objects describing operation + */ + public void finishCorrelatePhotos(PointPair[] inPointPairs) + { + // TODO: This method is too big for App, but where should it go? + if (inPointPairs != null && inPointPairs.length > 0) + { + // 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) + { + // 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); + // set to start of segment so not joined in track + pointToAdd.setSegmentStart(true); + // add to point array + addedPoints[pointNum] = pointToAdd; + pointNum++; + } + } + } + // expand track + _track.appendPoints(addedPoints); + } + // add undo information to stack + undo.setNumPhotosCorrelated(numPhotos); + _undoStack.add(undo); + // confirm correlation + UpdateMessageBroker.informSubscribers("" + numPhotos + " " + + (numPhotos==1?I18nManager.getText("confirm.correlate.single"):I18nManager.getText("confirm.correlate.multi"))); + // observers already informed by track update + } + } + + /** * Save the coordinates of photos in their exif data */ @@ -843,6 +1200,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); } @@ -873,7 +1231,7 @@ public class App _undoStack.clear(); _lastSavePosition = 0; if (unsaved) _lastSavePosition = -1; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } } @@ -890,10 +1248,9 @@ public class App { ((UndoOperation) _undoStack.pop()).performUndo(_trackInfo); } - JOptionPane.showMessageDialog(_frame, "" + inNumUndos + " " - + (inNumUndos==1?I18nManager.getText("dialog.confirmundo.single.text"):I18nManager.getText("dialog.confirmundo.multiple.text")), - I18nManager.getText("dialog.confirmundo.title"), - JOptionPane.INFORMATION_MESSAGE); + String message = "" + inNumUndos + " " + + (inNumUndos==1?I18nManager.getText("confirm.undo.single"):I18nManager.getText("confirm.undo.multi")); + UpdateMessageBroker.informSubscribers(message); } catch (UndoException ue) { @@ -902,7 +1259,7 @@ public class App I18nManager.getText("error.undofailed.title"), JOptionPane.ERROR_MESSAGE); _undoStack.clear(); - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } catch (EmptyStackException empty) {} } @@ -927,4 +1284,31 @@ public class App } return num; } + + /** + * Show a brief help message + */ + public void showHelp() + { + // show the dialog and offer to open home page + Object[] buttonTexts = {I18nManager.getText("button.showwebpage"), I18nManager.getText("button.cancel")}; + if (JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.help.help"), + I18nManager.getText("menu.help"), JOptionPane.YES_NO_OPTION, + JOptionPane.INFORMATION_MESSAGE, null, buttonTexts, buttonTexts[1]) + == JOptionPane.YES_OPTION) + { + // User selected to launch home page + if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();} + _browserLauncher.launchBrowser("http://activityworkshop.net/software/prune/index.html"); + } + } + + /** + * Show a map url in an external browser + */ + public void showExternalMap(int inSourceIndex) + { + if (_browserLauncher == null) {_browserLauncher = new BrowserLauncher();} + _browserLauncher.launchBrowser(UrlGenerator.generateUrl(inSourceIndex, _trackInfo)); + } }