]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - tim/prune/App.java
Version 3, August 2007
[GpsPrune.git] / tim / prune / App.java
index a1e6a8b8756d2801bdd320fbf14c86a15a3f7b00..e403722217d51bbe889168d7e66b03b3496899f8 100644 (file)
@@ -1,6 +1,7 @@
 package tim.prune;
 
 import java.util.EmptyStackException;
+import java.util.List;
 import java.util.Stack;
 
 import javax.swing.JFrame;
@@ -8,20 +9,36 @@ import javax.swing.JOptionPane;
 
 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.load.PhotoMeasurer;
+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.UndoCompress;
+import tim.prune.undo.UndoConnectPhoto;
 import tim.prune.undo.UndoDeleteDuplicates;
+import tim.prune.undo.UndoDeletePhoto;
 import tim.prune.undo.UndoDeletePoint;
 import tim.prune.undo.UndoDeleteRange;
+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 +56,9 @@ public class App
        private int _lastSavePosition = 0;
        private MenuManager _menuManager = null;
        private FileLoader _fileLoader = null;
+       private JpegLoader _jpegLoader = null;
+       private KmlExporter _exporter = null;
+       private PovExporter _povExporter = null;
        private Stack _undoStack = null;
        private UpdateMessageBroker _broker = null;
        private boolean _reversePointsConfirmed = false;
@@ -49,8 +69,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 +98,8 @@ public class App
         */
        public boolean hasDataUnsaved()
        {
-               return _undoStack.size() > _lastSavePosition;
+               return (_undoStack.size() > _lastSavePosition
+                       && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
        }
 
        /**
@@ -112,6 +131,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
         */
@@ -142,8 +172,66 @@ public class App
                }
                else
                {
-                       KmlExporter exporter = new KmlExporter(this, _frame, _track);
-                       exporter.showDialog();
+                       // Invoke the export
+                       if (_exporter == null)
+                       {
+                               _exporter = new KmlExporter(_frame, _trackInfo);
+                       }
+                       _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(_frame, _track);
+                       }
+                       // Specify angles if necessary
+                       if (inDefineSettings)
+                       {
+                               _povExporter.setCameraCoordinates(inX, inY, inZ);
+                               _povExporter.setAltitudeCap(inAltitudeCap);
+                       }
+                       // Initiate export
+                       _povExporter.showDialog();
                }
        }
 
@@ -153,6 +241,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 +257,62 @@ 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 list of edits
+        */
+       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 +323,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 +374,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<numToDelete; i++)
+                               {
+                                       point = _trackInfo.getTrack().getPoint(i + selStart);
+                                       if (point != null && point.getPhoto() != null)
+                                       {
+                                               if (deleteAll)
+                                               {
+                                                       deletePhotos[i] = true;
+                                                       photosToDelete[i] = point.getPhoto();
+                                               }
+                                               else if (deleteNone) {deletePhotos[i] = false;}
+                                               else
+                                               {
+                                                       int response = JOptionPane.showOptionDialog(_frame,
+                                                               I18nManager.getText("dialog.deletepoint.deletephoto") + " " + point.getPhoto().getFile().getName(),
+                                                               I18nManager.getText("dialog.deletepoint.title"),
+                                                               JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+                                                               questionOptions, questionOptions[1]);
+                                                       // check for cancel or close
+                                                       if (response == 4 || response == -1) {return;}
+                                                       // check for yes or yes to all
+                                                       if (response == 0 || response == 2)
+                                                       {
+                                                               deletePhotos[i] = true;
+                                                               photosToDelete[i] = point.getPhoto();
+                                                               if (response == 2) {deleteAll = true;}
+                                                       }
+                                                       // check for no to all
+                                                       if (response == 3) {deleteNone = true;}
+                                               }
+                                       }
+                               }
+                               // add information to undo stack
+                               UndoOperation undo = new UndoDeleteRange(_trackInfo);
+                               // delete requested photos
+                               for (int i=0; i<numToDelete; i++)
+                               {
+                                       point = _trackInfo.getTrack().getPoint(i + selStart);
+                                       if (point != null && point.getPhoto() != null)
+                                       {
+                                               if (deletePhotos[i])
+                                               {
+                                                       // delete photo from list
+                                                       _trackInfo.getPhotoList().deletePhoto(_trackInfo.getPhotoList().getPhotoIndex(point.getPhoto()));
+                                               }
+                                               else
+                                               {
+                                                       // decouple from point
+                                                       point.getPhoto().setDataPoint(null);
+                                               }
+                                       }
+                               }
+                               // call track to delete range
+                               if (_trackInfo.deleteRange())
+                               {
+                                       _undoStack.push(undo);
+                               }
                        }
                }
        }
@@ -359,9 +602,26 @@ public class App
         */
        public void show3dWindow()
        {
-               // TODO: open 3d view window
-               JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.notimplemented"),
-                       I18nManager.getText("error.function.notimplemented.title"), JOptionPane.WARNING_MESSAGE);
+               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)
+                       {
+                               JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(),
+                                       I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE);
+                       }
+               }
        }
 
 
@@ -378,9 +638,11 @@ public class App
         */
        public void selectNone()
        {
+               // deselect point, range and photo
                _trackInfo.getSelection().clearAll();
        }
 
+
        /**
         * Receive loaded data and optionally merge with current Track
         * @param inFieldArray array of fields
@@ -388,6 +650,17 @@ public class App
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
        {
+               // Check whether loaded array can be properly parsed into a Track
+               Track loadedTrack = new Track(_broker);
+               loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
+               if (loadedTrack.getNumPoints() <= 0)
+               {
+                       JOptionPane.showMessageDialog(_frame,
+                               I18nManager.getText("error.load.nopoints"),
+                               I18nManager.getText("error.load.dialogtitle"),
+                               JOptionPane.ERROR_MESSAGE);
+                       return;
+               }
                // Decide whether to load or append
                if (_track != null && _track.getNumPoints() > 0)
                {
@@ -399,25 +672,41 @@ 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.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 +717,116 @@ public class App
        }
 
 
+       /**
+        * Accept a list of loaded photos
+        * @param inPhotoList List of Photo objects
+        */
+       public void informPhotosLoaded(List inPhotoList)
+       {
+               if (inPhotoList != null && !inPhotoList.isEmpty())
+               {
+                       int[] numsAdded = _trackInfo.addPhotos(inPhotoList);
+                       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);
+                       }
+                       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);
+                       //TODO: Confirm connect (maybe with status in photo panel?)
+               }
+       }
+
+
+       /**
+        * 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
         */