X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2FApp.java;h=18a2e79a60fa737cde270045cb0d3b6263dc278f;hb=da0b1f449260a0b4a94318006382a9039726ef3e;hp=a1e6a8b8756d2801bdd320fbf14c86a15a3f7b00;hpb=312fec956e43f5d0a38617da5d0add9c62563e2c;p=GpsPrune.git diff --git a/tim/prune/App.java b/tim/prune/App.java index a1e6a8b..18a2e79 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -1,27 +1,48 @@ package tim.prune; import java.util.EmptyStackException; +import java.util.Set; 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.data.DataPoint; import tim.prune.data.Field; +import tim.prune.data.Photo; +import tim.prune.data.PhotoList; 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.gui.MenuManager; import tim.prune.gui.UndoManager; import tim.prune.load.FileLoader; +import tim.prune.load.JpegLoader; +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; @@ -39,6 +60,11 @@ public class App private int _lastSavePosition = 0; private MenuManager _menuManager = 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; @@ -49,8 +75,6 @@ public class App public static final int REARRANGE_TO_NEAREST = 2; - // TODO: Make waypoint window to allow list of waypoints, edit names etc - /** * Constructor * @param inFrame frame object for application @@ -80,7 +104,8 @@ public class App */ public boolean hasDataUnsaved() { - return _undoStack.size() > _lastSavePosition; + return (_undoStack.size() > _lastSavePosition + && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0)); } /** @@ -112,6 +137,17 @@ public class App } + /** + * Add a photo or a directory of photos which are already correlated + */ + public void addPhotos() + { + if (_jpegLoader == null) + _jpegLoader = new JpegLoader(this, _frame); + _jpegLoader.openFile(); + } + + /** * Save the file in the selected format */ @@ -124,8 +160,10 @@ public class App } else { - FileSaver saver = new FileSaver(this, _frame, _track); - saver.showDialog(_fileLoader.getLastUsedDelimiter()); + if (_fileSaver == null) { + _fileSaver = new FileSaver(this, _frame, _track); + } + _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter()); } } @@ -142,8 +180,89 @@ public class App } else { - KmlExporter exporter = new KmlExporter(this, _frame, _track); - exporter.showDialog(); + // 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); + } + // Initiate export + _povExporter.showDialog(); } } @@ -153,6 +272,9 @@ public class App */ 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() @@ -166,6 +288,63 @@ public class App } + /** + * 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)) + { + _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); + } + } + } + + /** * Delete the currently selected point */ @@ -176,13 +355,44 @@ public class App DataPoint currentPoint = _trackInfo.getCurrentPoint(); if (currentPoint != 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(); - UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint); + 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) + { + // delete photo if necessary + if (deletePhoto) + { + _trackInfo.getPhotoList().deletePhoto(photoIndex); + } + else + { + // decouple photo from point + currentPhoto.setDataPoint(null); + } + } } } } @@ -196,12 +406,77 @@ public class App { if (_track != null) { - // add information to undo stack - UndoOperation undo = new UndoDeleteRange(_trackInfo); - // call track to delete point - if (_trackInfo.deleteRange()) + // Find out if photos should be deleted or not + int selStart = _trackInfo.getSelection().getStart(); + int selEnd = _trackInfo.getSelection().getEnd(); + if (selStart >= 0 && selEnd >= selStart) { - _undoStack.push(undo); + 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 0) { @@ -399,25 +707,42 @@ public class App if (answer == JOptionPane.YES_OPTION) { // append data to current Track - Track loadedTrack = new Track(_broker); - loadedTrack.load(inFieldArray, inDataArray, inAltFormat); _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints())); _track.combine(loadedTrack); - _trackInfo.getFileInfo().addFile(); + // set filename if currently empty + if (_trackInfo.getFileInfo().getNumFiles() == 0) + { + _trackInfo.getFileInfo().setFile(inFilename); + } + else + { + _trackInfo.getFileInfo().addFile(); + } } else if (answer == JOptionPane.NO_OPTION) { // Don't append, replace data - _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length)); + PhotoList photos = null; + if (_trackInfo.getPhotoList().hasCorrelatedPhotos()) + { + photos = _trackInfo.getPhotoList().cloneList(); + } + _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) + { + _trackInfo.getPhotoList().removeCorrelatedPhotos(); + } } } else { // currently no data held, so use received data - _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length)); + _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null)); _lastSavePosition = _undoStack.size(); _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); _trackInfo.getFileInfo().setFile(inFilename); @@ -428,6 +753,243 @@ public class App } + /** + * Accept a list of loaded photos + * @param inPhotoSet Set of Photo objects + */ + public void informPhotosLoaded(Set 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) + { + 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(); + } + } + + + /** + * Connect the current photo to the current point + */ + public void connectPhotoToPoint() + { + 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); + } + } + + + /** + * 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); + _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + } + } + + + /** + * 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); + } + } + // Add undo information to stack if necessary + if (photoDeleted) + { + _undoStack.add(undoAction); + } + } + } + + + /** + * 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 + */ + 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); + // 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 + 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 + } + } + + + /** + * 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 */ @@ -528,4 +1090,14 @@ public class App } return num; } + + /** + * Show a brief help message + */ + public void showHelp() + { + JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.help.help"), + I18nManager.getText("menu.help"), + JOptionPane.INFORMATION_MESSAGE); + } }