X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2FApp.java;h=ced255ae111cc194eac8fb0d1db64b2f2b019b36;hb=54b9d8bc8f0025ccf97a67d9dd217ef1f9cf082f;hp=4ccaed688e40dcfd1aef3b1d7e464fad4b1b26ee;hpb=d3679d647d57c2ee7376ddbf6def2d5b23c04307;p=GpsPrune.git diff --git a/tim/prune/App.java b/tim/prune/App.java index 4ccaed6..ced255a 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -1,40 +1,51 @@ 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.data.Altitude; +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; import tim.prune.data.TrackInfo; -import tim.prune.edit.FieldEditList; -import tim.prune.edit.PointEditor; -import tim.prune.edit.PointNameEditor; +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.function.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.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.UndoDeleteDuplicates; +import tim.prune.undo.UndoConnectPhoto; +import tim.prune.undo.UndoConnectPhotoWithClone; +import tim.prune.undo.UndoCreatePoint; +import tim.prune.undo.UndoCutAndMove; +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; @@ -51,29 +62,21 @@ public class App private MenuManager _menuManager = null; private FileLoader _fileLoader = null; private JpegLoader _jpegLoader = null; - private PovExporter _povExporter = null; - private Stack _undoStack = null; - private UpdateMessageBroker _broker = null; - private boolean _reversePointsConfirmed = false; - - // 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; - + private FileSaver _fileSaver = null; + private Stack _undoStack = null; + private boolean _mangleTimestampsConfirmed = false; /** * 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); } @@ -85,23 +88,43 @@ 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 */ public boolean hasDataUnsaved() { - return _undoStack.size() > _lastSavePosition; + return (_undoStack.size() > _lastSavePosition + && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0)); } /** * @return the undo stack */ - public Stack getUndoStack() + public Stack getUndoStack() { return _undoStack; } + /** + * 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); + } + /** * Set the MenuManager object to be informed about changes * @param inManager MenuManager object @@ -124,102 +147,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 { - FileSaver saver = new FileSaver(this, _frame, _track); - saver.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 - { - KmlExporter exporter = new KmlExporter(this, _frame, _track); - exporter.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 - */ - 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(this, _frame, _track); - } - // Specify angles if necessary - if (inDefineSettings) - { - _povExporter.setCameraCoordinates(inX, inY, inZ); - _povExporter.setAltitudeCap(inAltitudeCap); + if (_fileSaver == null) { + _fileSaver = new FileSaver(this, _frame, _track); } - // Initiate export - _povExporter.showDialog(); + char delim = ','; + if (_fileLoader != null) {delim = _fileLoader.getLastUsedDelimiter();} + _fileSaver.showDialog(delim); } } @@ -229,6 +181,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() @@ -262,7 +217,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) { @@ -275,6 +231,8 @@ public class App if (_track.editPoint(currentPoint, inEditList)) { _undoStack.push(undo); + // Confirm point edit + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.point.edit")); } } } @@ -292,7 +250,7 @@ public class App { // Open point dialog to display details PointNameEditor editor = new PointNameEditor(this, _frame); - editor.showDialog(_track, currentPoint); + editor.showDialog(currentPoint); } } } @@ -303,114 +261,167 @@ 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) { - // add information to undo stack - int pointIndex = _trackInfo.getSelection().getCurrentPointIndex(); - UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint); - // 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.getFile().getName(), + I18nManager.getText("dialog.deletepoint.title"), + JOptionPane.YES_NO_CANCEL_OPTION); + if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION) { - _undoStack.push(undo); + // cancel pressed- abort delete + return; } + if (response == JOptionPane.YES_OPTION) {deletePhoto = true;} } - } - } - - - /** - * Delete the currently selected range - */ - public void deleteSelectedRange() - { - if (_track != null) - { - // add information to undo stack - UndoOperation undo = new UndoDeleteRange(_trackInfo); + // 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.deleteRange()) + if (_trackInfo.deletePoint()) { + // Delete was successful so add undo info to stack _undoStack.push(undo); + if (currentPhoto != null) + { + // delete photo if necessary + if (deletePhoto) + { + _trackInfo.getPhotoList().deletePhoto(photoIndex); + } + else + { + // decouple photo from point + currentPhoto.setDataPoint(null); + } + } + // Confirm + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.deletepoint.single")); } } } /** - * Delete all the duplicate points in the track + * Delete the currently selected range */ - public void deleteDuplicates() + public void deleteSelectedRange() { if (_track != null) { - // Save undo information - UndoOperation undo = new UndoDeleteDuplicates(_track); - // tell track to do it - int numDeleted = _trackInfo.deleteDuplicates(); - if (numDeleted > 0) + // 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.add(undo); - String message = null; - if (numDeleted == 1) + 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) { 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.compress.nonefound"); } } - /** - * Reverse a section of the track + * Reverse the currently selected section of the track */ public void reverseRange() { @@ -418,17 +429,60 @@ 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")); + } + } + } + + /** + * 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)) + { + _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.addtimeoffset")); + } + } + + + /** + * 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 (_track.mergeTrackSegments(selStart, selEnd)) { + _undoStack.add(undo); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.mergetracksegments")); } } } @@ -458,57 +512,79 @@ public class App /** - * Rearrange the waypoints into track order + * Average the selected points */ - public void rearrangeWaypoints(int inFunction) + public void averageSelection() { - 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); - } - else - { - // Interleave the waypoints into track order - success = _track.interleaveWaypoints(); - } - if (success) + // 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); } - else - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"), - I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE); - } } /** - * Open a new window with the 3d view + * Create a new point at the given lat/long coordinates + * @param inLat latitude + * @param inLong longitude */ - public void show3dWindow() + public void createPoint(double inLat, double inLong) { - ThreeDWindow window = WindowFactory.getWindow(this, _frame); - if (window == null) - { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"), - I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE); - } - else - { - try - { - // Pass the track object and show the window - window.setTrack(_track); - window.show(); - } - catch (ThreeDException e) + // create undo object + UndoCreatePoint undo = new UndoCreatePoint(); + // create point and add to track + DataPoint point = new DataPoint(new Latitude(inLat, Coordinate.FORMAT_NONE), new Longitude(inLong, Coordinate.FORMAT_NONE), null); + point.setSegmentStart(true); + _track.appendPoints(new DataPoint[] {point}); + _trackInfo.getSelection().selectPoint(_trackInfo.getTrack().getNumPoints()-1); + // add undo object to stack + _undoStack.add(undo); + // 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)) { - JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(), - I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE); + // 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().deselectRange(); + UpdateMessageBroker.informSubscribers(); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.cutandmove")); } } } @@ -527,18 +603,32 @@ public class App */ public void selectNone() { + // deselect point, range and photo _trackInfo.getSelection().clearAll(); + _track.clearDeletionMarkers(); } + /** * Receive loaded data and optionally merge with current Track * @param inFieldArray array of fields * @param inDataArray array of data + * @param inAltFormat altitude format + * @param inFilename filename used */ - public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename) + public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, Altitude.Format inAltFormat, + String inFilename) { + // Check whether loaded array can be properly parsed into a Track + Track loadedTrack = new Track(); + loadedTrack.load(inFieldArray, inDataArray, inAltFormat); + if (loadedTrack.getNumPoints() <= 0) + { + showErrorMessage("error.load.dialogtitle", "error.load.nopoints"); + return; + } // 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, @@ -548,30 +638,49 @@ 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(); - _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); + _trackInfo.getSelection().clearAll(); + _track.load(loadedTrack); _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)); + // Currently no data held, so transfer received data + _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, null)); _lastSavePosition = _undoStack.size(); - _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); + _trackInfo.getSelection().clearAll(); + _track.load(loadedTrack); _trackInfo.getFileInfo().setFile(inFilename); } - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); + // Update status bar + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile") + " '" + inFilename + "'"); // update menu _menuManager.informFileLoaded(); } @@ -579,38 +688,153 @@ 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()) { - // TODO: Attempt to restrict loaded photos to current area (if any) ? - int numAdded = _trackInfo.addPhotos(inPhotoList); - if (numAdded > 0) + int[] numsAdded = _trackInfo.addPhotos(inPhotoSet); + int numPhotosAdded = numsAdded[0]; + int numPointsAdded = numsAdded[1]; + if (numPhotosAdded > 0) { - _undoStack.add(new UndoLoadPhotos(numAdded)); + // Save numbers so load can be undone + _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded)); } - if (numAdded == 1) + if (numPhotosAdded == 1) { - JOptionPane.showMessageDialog(_frame, - "" + numAdded + " " + 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, - "" + numAdded + " " + 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(); } } + /** + * Connect the current photo to the current point + */ + public void connectPhotoToPoint() + { + Photo photo = _trackInfo.getCurrentPhoto(); + DataPoint point = _trackInfo.getCurrentPoint(); + if (photo != null && point != null) + { + 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")); + } + } + + + /** + * 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); + } + } + } + + + /** + * 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 */ @@ -627,6 +851,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); } @@ -657,7 +882,7 @@ public class App _undoStack.clear(); _lastSavePosition = 0; if (unsaved) _lastSavePosition = -1; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } } @@ -672,21 +897,18 @@ public class App { for (int i=0; i