3 import java.util.EmptyStackException;
5 import java.util.Stack;
7 import javax.swing.JFrame;
8 import javax.swing.JOptionPane;
10 import tim.prune.data.DataPoint;
11 import tim.prune.data.Field;
12 import tim.prune.data.Track;
13 import tim.prune.data.TrackInfo;
14 import tim.prune.edit.FieldEditList;
15 import tim.prune.edit.PointEditor;
16 import tim.prune.edit.PointNameEditor;
17 import tim.prune.gui.MenuManager;
18 import tim.prune.gui.UndoManager;
19 import tim.prune.load.FileLoader;
20 import tim.prune.load.JpegLoader;
21 import tim.prune.save.FileSaver;
22 import tim.prune.save.KmlExporter;
23 import tim.prune.save.PovExporter;
24 import tim.prune.threedee.ThreeDException;
25 import tim.prune.threedee.ThreeDWindow;
26 import tim.prune.threedee.WindowFactory;
27 import tim.prune.undo.UndoCompress;
28 import tim.prune.undo.UndoDeleteDuplicates;
29 import tim.prune.undo.UndoDeletePoint;
30 import tim.prune.undo.UndoDeleteRange;
31 import tim.prune.undo.UndoEditPoint;
32 import tim.prune.undo.UndoException;
33 import tim.prune.undo.UndoInsert;
34 import tim.prune.undo.UndoLoad;
35 import tim.prune.undo.UndoLoadPhotos;
36 import tim.prune.undo.UndoOperation;
37 import tim.prune.undo.UndoRearrangeWaypoints;
38 import tim.prune.undo.UndoReverseSection;
42 * Main controller for the application
47 private JFrame _frame = null;
48 private Track _track = null;
49 private TrackInfo _trackInfo = null;
50 private int _lastSavePosition = 0;
51 private MenuManager _menuManager = null;
52 private FileLoader _fileLoader = null;
53 private JpegLoader _jpegLoader = null;
54 private PovExporter _povExporter = null;
55 private Stack _undoStack = null;
56 private UpdateMessageBroker _broker = null;
57 private boolean _reversePointsConfirmed = false;
60 public static final int REARRANGE_TO_START = 0;
61 public static final int REARRANGE_TO_END = 1;
62 public static final int REARRANGE_TO_NEAREST = 2;
67 * @param inFrame frame object for application
68 * @param inBroker message broker
70 public App(JFrame inFrame, UpdateMessageBroker inBroker)
73 _undoStack = new Stack();
75 _track = new Track(_broker);
76 _trackInfo = new TrackInfo(_track, _broker);
81 * @return the current TrackInfo
83 public TrackInfo getTrackInfo()
89 * Check if the application has unsaved data
90 * @return true if data is unsaved, false otherwise
92 public boolean hasDataUnsaved()
94 return _undoStack.size() > _lastSavePosition;
98 * @return the undo stack
100 public Stack getUndoStack()
106 * Set the MenuManager object to be informed about changes
107 * @param inManager MenuManager object
109 public void setMenuManager(MenuManager inManager)
111 _menuManager = inManager;
116 * Open a file containing track or waypoint data
118 public void openFile()
120 if (_fileLoader == null)
121 _fileLoader = new FileLoader(this, _frame);
122 _fileLoader.openFile();
127 * Add a photo or a directory of photos which are already correlated
129 public void addPhotos()
131 if (_jpegLoader == null)
132 _jpegLoader = new JpegLoader(this, _frame);
133 _jpegLoader.openFile();
138 * Save the file in the selected format
140 public void saveFile()
144 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
145 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
149 FileSaver saver = new FileSaver(this, _frame, _track);
150 saver.showDialog(_fileLoader.getLastUsedDelimiter());
156 * Export track data as Kml
158 public void exportKml()
162 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
163 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
167 KmlExporter exporter = new KmlExporter(this, _frame, _track);
168 exporter.showDialog();
174 * Export track data as Pov without specifying settings
176 public void exportPov()
178 exportPov(false, 0.0, 0.0, 0.0, 0);
182 * Export track data as Pov and also specify settings
183 * @param inX X component of unit vector
184 * @param inY Y component of unit vector
185 * @param inZ Z component of unit vector
186 * @param inAltitudeCap altitude cap
188 public void exportPov(double inX, double inY, double inZ, int inAltitudeCap)
190 exportPov(true, inX, inY, inZ, inAltitudeCap);
194 * Export track data as Pov with optional angle specification
195 * @param inDefineAngles true to define angles, false to ignore
196 * @param inX X component of unit vector
197 * @param inY Y component of unit vector
198 * @param inZ Z component of unit vector
200 private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap)
202 // Check track has data to export
203 if (_track == null || _track.getNumPoints() <= 0)
205 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
206 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
210 // Make new exporter if necessary
211 if (_povExporter == null)
213 _povExporter = new PovExporter(this, _frame, _track);
215 // Specify angles if necessary
216 if (inDefineSettings)
218 _povExporter.setCameraCoordinates(inX, inY, inZ);
219 _povExporter.setAltitudeCap(inAltitudeCap);
222 _povExporter.showDialog();
228 * Exit the application if confirmed
232 // check if ok to exit
233 Object[] buttonTexts = {I18nManager.getText("button.exit"), I18nManager.getText("button.cancel")};
234 if (!hasDataUnsaved()
235 || JOptionPane.showOptionDialog(_frame, I18nManager.getText("dialog.exit.confirm.text"),
236 I18nManager.getText("dialog.exit.confirm.title"), JOptionPane.YES_NO_OPTION,
237 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
238 == JOptionPane.YES_OPTION)
246 * Edit the currently selected point
248 public void editCurrentPoint()
252 DataPoint currentPoint = _trackInfo.getCurrentPoint();
253 if (currentPoint != null)
255 // Open point dialog to display details
256 PointEditor editor = new PointEditor(this, _frame);
257 editor.showDialog(_track, currentPoint);
264 * Complete the point edit
265 * @param inEditList list of edits
267 public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
269 DataPoint currentPoint = _trackInfo.getCurrentPoint();
270 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
272 // add information to undo stack
273 UndoOperation undo = new UndoEditPoint(currentPoint, inUndoList);
274 // pass to track for completion
275 if (_track.editPoint(currentPoint, inEditList))
277 _undoStack.push(undo);
284 * Edit the name of the currently selected (way)point
286 public void editCurrentPointName()
290 DataPoint currentPoint = _trackInfo.getCurrentPoint();
291 if (currentPoint != null)
293 // Open point dialog to display details
294 PointNameEditor editor = new PointNameEditor(this, _frame);
295 editor.showDialog(_track, currentPoint);
302 * Delete the currently selected point
304 public void deleteCurrentPoint()
308 DataPoint currentPoint = _trackInfo.getCurrentPoint();
309 if (currentPoint != null)
311 // add information to undo stack
312 int pointIndex = _trackInfo.getSelection().getCurrentPointIndex();
313 UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint);
314 // call track to delete point
315 if (_trackInfo.deletePoint())
317 _undoStack.push(undo);
325 * Delete the currently selected range
327 public void deleteSelectedRange()
331 // add information to undo stack
332 UndoOperation undo = new UndoDeleteRange(_trackInfo);
333 // call track to delete point
334 if (_trackInfo.deleteRange())
336 _undoStack.push(undo);
343 * Delete all the duplicate points in the track
345 public void deleteDuplicates()
349 // Save undo information
350 UndoOperation undo = new UndoDeleteDuplicates(_track);
351 // tell track to do it
352 int numDeleted = _trackInfo.deleteDuplicates();
355 _undoStack.add(undo);
356 String message = null;
359 message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text");
363 message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text");
365 JOptionPane.showMessageDialog(_frame, message,
366 I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
370 JOptionPane.showMessageDialog(_frame,
371 I18nManager.getText("dialog.deleteduplicates.nonefound"),
372 I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
381 public void compressTrack()
383 UndoCompress undo = new UndoCompress(_track);
384 // Get compression parameter
385 Object compParam = JOptionPane.showInputDialog(_frame,
386 I18nManager.getText("dialog.compresstrack.parameter.text"),
387 I18nManager.getText("dialog.compresstrack.title"),
388 JOptionPane.QUESTION_MESSAGE, null, null, "100");
389 int compNumber = parseNumber(compParam);
390 if (compNumber <= 0) return;
391 // call track to do compress
392 int numPointsDeleted = _trackInfo.compress(compNumber);
393 // add to undo stack if successful
394 if (numPointsDeleted > 0)
396 undo.setNumPointsDeleted(numPointsDeleted);
397 _undoStack.add(undo);
398 JOptionPane.showMessageDialog(_frame,
399 I18nManager.getText("dialog.compresstrack.text") + " - "
400 + numPointsDeleted + " "
401 + (numPointsDeleted==1?I18nManager.getText("dialog.compresstrack.single.text"):I18nManager.getText("dialog.compresstrack.multi.text")),
402 I18nManager.getText("dialog.compresstrack.title"), JOptionPane.INFORMATION_MESSAGE);
406 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.compresstrack.nonefound"),
407 I18nManager.getText("dialog.compresstrack.title"), JOptionPane.WARNING_MESSAGE);
413 * Reverse a section of the track
415 public void reverseRange()
417 // check whether Timestamp field exists, and if so confirm reversal
418 int selStart = _trackInfo.getSelection().getStart();
419 int selEnd = _trackInfo.getSelection().getEnd();
420 if (!_track.hasData(Field.TIMESTAMP, selStart, selEnd)
421 || _reversePointsConfirmed
422 || (JOptionPane.showConfirmDialog(_frame,
423 I18nManager.getText("dialog.confirmreversetrack.text"),
424 I18nManager.getText("dialog.confirmreversetrack.title"),
425 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION && (_reversePointsConfirmed = true)))
427 UndoReverseSection undo = new UndoReverseSection(selStart, selEnd);
428 // call track to reverse range
429 if (_track.reverseRange(selStart, selEnd))
431 _undoStack.add(undo);
438 * Interpolate the two selected points
440 public void interpolateSelection()
442 // Get number of points to add
443 Object numPointsStr = JOptionPane.showInputDialog(_frame,
444 I18nManager.getText("dialog.interpolate.parameter.text"),
445 I18nManager.getText("dialog.interpolate.title"),
446 JOptionPane.QUESTION_MESSAGE, null, null, "");
447 int numPoints = parseNumber(numPointsStr);
448 if (numPoints <= 0) return;
450 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
452 // call track to interpolate
453 if (_trackInfo.interpolate(numPoints))
455 _undoStack.add(undo);
461 * Rearrange the waypoints into track order
463 public void rearrangeWaypoints(int inFunction)
465 UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(_track);
466 boolean success = false;
467 if (inFunction == REARRANGE_TO_START || inFunction == REARRANGE_TO_END)
469 // Collect the waypoints to the start or end of the track
470 success = _track.collectWaypoints(inFunction == REARRANGE_TO_START);
474 // Interleave the waypoints into track order
475 success = _track.interleaveWaypoints();
479 _undoStack.add(undo);
483 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"),
484 I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);
490 * Open a new window with the 3d view
492 public void show3dWindow()
494 ThreeDWindow window = WindowFactory.getWindow(this, _frame);
497 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"),
498 I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE);
504 // Pass the track object and show the window
505 window.setTrack(_track);
508 catch (ThreeDException e)
510 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(),
511 I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE);
520 public void selectAll()
522 _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1);
528 public void selectNone()
530 _trackInfo.getSelection().clearAll();
534 * Receive loaded data and optionally merge with current Track
535 * @param inFieldArray array of fields
536 * @param inDataArray array of data
538 public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
540 // Decide whether to load or append
541 if (_track != null && _track.getNumPoints() > 0)
543 // ask whether to replace or append
544 int answer = JOptionPane.showConfirmDialog(_frame,
545 I18nManager.getText("dialog.openappend.text"),
546 I18nManager.getText("dialog.openappend.title"),
547 JOptionPane.YES_NO_CANCEL_OPTION);
548 if (answer == JOptionPane.YES_OPTION)
550 // append data to current Track
551 Track loadedTrack = new Track(_broker);
552 loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
553 _undoStack.add(new UndoLoad(_track.getNumPoints(), loadedTrack.getNumPoints()));
554 _track.combine(loadedTrack);
555 _trackInfo.getFileInfo().addFile();
557 else if (answer == JOptionPane.NO_OPTION)
559 // Don't append, replace data
560 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length));
561 _lastSavePosition = _undoStack.size();
562 _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
563 _trackInfo.getFileInfo().setFile(inFilename);
568 // currently no data held, so use received data
569 _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length));
570 _lastSavePosition = _undoStack.size();
571 _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat);
572 _trackInfo.getFileInfo().setFile(inFilename);
574 _broker.informSubscribers();
576 _menuManager.informFileLoaded();
581 * Accept a list of loaded photos
582 * @param inPhotoList List of Photo objects
584 public void informPhotosLoaded(List inPhotoList)
586 if (inPhotoList != null && !inPhotoList.isEmpty())
588 // TODO: Attempt to restrict loaded photos to current area (if any) ?
589 int numAdded = _trackInfo.addPhotos(inPhotoList);
592 _undoStack.add(new UndoLoadPhotos(numAdded));
596 JOptionPane.showMessageDialog(_frame,
597 "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
598 I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
602 JOptionPane.showMessageDialog(_frame,
603 "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
604 I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
606 // TODO: Improve message when photo(s) fail to load (eg already added)
607 _broker.informSubscribers();
609 _menuManager.informFileLoaded();
615 * Inform the app that the data has been saved
617 public void informDataSaved()
619 _lastSavePosition = _undoStack.size();
626 public void beginUndo()
628 if (_undoStack.isEmpty())
630 JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
631 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
635 new UndoManager(this, _frame);
641 * Clear the undo stack (losing all undo information
643 public void clearUndo()
645 // Exit if nothing to undo
646 if (_undoStack == null || _undoStack.isEmpty())
648 // Has track got unsaved data?
649 boolean unsaved = hasDataUnsaved();
650 // Confirm operation with dialog
651 int answer = JOptionPane.showConfirmDialog(_frame,
652 I18nManager.getText("dialog.clearundo.text"),
653 I18nManager.getText("dialog.clearundo.title"),
654 JOptionPane.YES_NO_OPTION);
655 if (answer == JOptionPane.YES_OPTION)
658 _lastSavePosition = 0;
659 if (unsaved) _lastSavePosition = -1;
660 _broker.informSubscribers();
666 * Undo the specified number of actions
667 * @param inNumUndos number of actions to undo
669 public void undoActions(int inNumUndos)
673 for (int i=0; i<inNumUndos; i++)
675 ((UndoOperation) _undoStack.pop()).performUndo(_trackInfo);
677 JOptionPane.showMessageDialog(_frame, "" + inNumUndos + " "
678 + (inNumUndos==1?I18nManager.getText("dialog.confirmundo.single.text"):I18nManager.getText("dialog.confirmundo.multiple.text")),
679 I18nManager.getText("dialog.confirmundo.title"),
680 JOptionPane.INFORMATION_MESSAGE);
682 catch (UndoException ue)
684 JOptionPane.showMessageDialog(_frame,
685 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage(),
686 I18nManager.getText("error.undofailed.title"),
687 JOptionPane.ERROR_MESSAGE);
689 _broker.informSubscribers();
691 catch (EmptyStackException empty) {}
696 * Helper method to parse an Object into an integer
697 * @param inObject object, eg from dialog
698 * @return int value given
700 private static int parseNumber(Object inObject)
703 if (inObject != null)
707 num = Integer.parseInt(inObject.toString());
709 catch (NumberFormatException nfe)