]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/App.java
4ccaed688e40dcfd1aef3b1d7e464fad4b1b26ee
[GpsPrune.git] / tim / prune / App.java
1 package tim.prune;
2
3 import java.util.EmptyStackException;
4 import java.util.List;
5 import java.util.Stack;
6
7 import javax.swing.JFrame;
8 import javax.swing.JOptionPane;
9
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;
39
40
41 /**
42  * Main controller for the application
43  */
44 public class App
45 {
46         // Instance variables
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;
58
59         // Constants
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;
63
64
65         /**
66          * Constructor
67          * @param inFrame frame object for application
68          * @param inBroker message broker
69          */
70         public App(JFrame inFrame, UpdateMessageBroker inBroker)
71         {
72                 _frame = inFrame;
73                 _undoStack = new Stack();
74                 _broker = inBroker;
75                 _track = new Track(_broker);
76                 _trackInfo = new TrackInfo(_track, _broker);
77         }
78
79
80         /**
81          * @return the current TrackInfo
82          */
83         public TrackInfo getTrackInfo()
84         {
85                 return _trackInfo;
86         }
87
88         /**
89          * Check if the application has unsaved data
90          * @return true if data is unsaved, false otherwise
91          */
92         public boolean hasDataUnsaved()
93         {
94                 return _undoStack.size() > _lastSavePosition;
95         }
96
97         /**
98          * @return the undo stack
99          */
100         public Stack getUndoStack()
101         {
102                 return _undoStack;
103         }
104
105         /**
106          * Set the MenuManager object to be informed about changes
107          * @param inManager MenuManager object
108          */
109         public void setMenuManager(MenuManager inManager)
110         {
111                 _menuManager = inManager;
112         }
113
114
115         /**
116          * Open a file containing track or waypoint data
117          */
118         public void openFile()
119         {
120                 if (_fileLoader == null)
121                         _fileLoader = new FileLoader(this, _frame);
122                 _fileLoader.openFile();
123         }
124
125
126         /**
127          * Add a photo or a directory of photos which are already correlated
128          */
129         public void addPhotos()
130         {
131                 if (_jpegLoader == null)
132                         _jpegLoader = new JpegLoader(this, _frame);
133                 _jpegLoader.openFile();
134         }
135
136
137         /**
138          * Save the file in the selected format
139          */
140         public void saveFile()
141         {
142                 if (_track == null)
143                 {
144                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
145                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
146                 }
147                 else
148                 {
149                         FileSaver saver = new FileSaver(this, _frame, _track);
150                         saver.showDialog(_fileLoader.getLastUsedDelimiter());
151                 }
152         }
153
154
155         /**
156          * Export track data as Kml
157          */
158         public void exportKml()
159         {
160                 if (_track == null)
161                 {
162                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
163                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
164                 }
165                 else
166                 {
167                         KmlExporter exporter = new KmlExporter(this, _frame, _track);
168                         exporter.showDialog();
169                 }
170         }
171
172
173         /**
174          * Export track data as Pov without specifying settings
175          */
176         public void exportPov()
177         {
178                 exportPov(false, 0.0, 0.0, 0.0, 0);
179         }
180
181         /**
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
187          */
188         public void exportPov(double inX, double inY, double inZ, int inAltitudeCap)
189         {
190                 exportPov(true, inX, inY, inZ, inAltitudeCap);
191         }
192
193         /**
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
199          */
200         private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap)
201         {
202                 // Check track has data to export
203                 if (_track == null || _track.getNumPoints() <= 0)
204                 {
205                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"),
206                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
207                 }
208                 else
209                 {
210                         // Make new exporter if necessary
211                         if (_povExporter == null)
212                         {
213                                 _povExporter = new PovExporter(this, _frame, _track);
214                         }
215                         // Specify angles if necessary
216                         if (inDefineSettings)
217                         {
218                                 _povExporter.setCameraCoordinates(inX, inY, inZ);
219                                 _povExporter.setAltitudeCap(inAltitudeCap);
220                         }
221                         // Initiate export
222                         _povExporter.showDialog();
223                 }
224         }
225
226
227         /**
228          * Exit the application if confirmed
229          */
230         public void exit()
231         {
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)
239                 {
240                         System.exit(0);
241                 }
242         }
243
244
245         /**
246          * Edit the currently selected point
247          */
248         public void editCurrentPoint()
249         {
250                 if (_track != null)
251                 {
252                         DataPoint currentPoint = _trackInfo.getCurrentPoint();
253                         if (currentPoint != null)
254                         {
255                                 // Open point dialog to display details
256                                 PointEditor editor = new PointEditor(this, _frame);
257                                 editor.showDialog(_track, currentPoint);
258                         }
259                 }
260         }
261
262
263         /**
264          * Complete the point edit
265          * @param inEditList list of edits
266          */
267         public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList)
268         {
269                 DataPoint currentPoint = _trackInfo.getCurrentPoint();
270                 if (inEditList != null && inEditList.getNumEdits() > 0 && currentPoint != null)
271                 {
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))
276                         {
277                                 _undoStack.push(undo);
278                         }
279                 }
280         }
281
282
283         /**
284          * Edit the name of the currently selected (way)point
285          */
286         public void editCurrentPointName()
287         {
288                 if (_track != null)
289                 {
290                         DataPoint currentPoint = _trackInfo.getCurrentPoint();
291                         if (currentPoint != null)
292                         {
293                                 // Open point dialog to display details
294                                 PointNameEditor editor = new PointNameEditor(this, _frame);
295                                 editor.showDialog(_track, currentPoint);
296                         }
297                 }
298         }
299
300
301         /**
302          * Delete the currently selected point
303          */
304         public void deleteCurrentPoint()
305         {
306                 if (_track != null)
307                 {
308                         DataPoint currentPoint = _trackInfo.getCurrentPoint();
309                         if (currentPoint != null)
310                         {
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())
316                                 {
317                                         _undoStack.push(undo);
318                                 }
319                         }
320                 }
321         }
322
323
324         /**
325          * Delete the currently selected range
326          */
327         public void deleteSelectedRange()
328         {
329                 if (_track != null)
330                 {
331                         // add information to undo stack
332                         UndoOperation undo = new UndoDeleteRange(_trackInfo);
333                         // call track to delete point
334                         if (_trackInfo.deleteRange())
335                         {
336                                 _undoStack.push(undo);
337                         }
338                 }
339         }
340
341
342         /**
343          * Delete all the duplicate points in the track
344          */
345         public void deleteDuplicates()
346         {
347                 if (_track != null)
348                 {
349                         // Save undo information
350                         UndoOperation undo = new UndoDeleteDuplicates(_track);
351                         // tell track to do it
352                         int numDeleted = _trackInfo.deleteDuplicates();
353                         if (numDeleted > 0)
354                         {
355                                 _undoStack.add(undo);
356                                 String message = null;
357                                 if (numDeleted == 1)
358                                 {
359                                         message = "1 " + I18nManager.getText("dialog.deleteduplicates.single.text");
360                                 }
361                                 else
362                                 {
363                                         message = "" + numDeleted + " " + I18nManager.getText("dialog.deleteduplicates.multi.text");
364                                 }
365                                 JOptionPane.showMessageDialog(_frame, message,
366                                         I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
367                         }
368                         else
369                         {
370                                 JOptionPane.showMessageDialog(_frame,
371                                         I18nManager.getText("dialog.deleteduplicates.nonefound"),
372                                         I18nManager.getText("dialog.deleteduplicates.title"), JOptionPane.INFORMATION_MESSAGE);
373                         }
374                 }
375         }
376
377
378         /**
379          * Compress the track
380          */
381         public void compressTrack()
382         {
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)
395                 {
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);
403                 }
404                 else
405                 {
406                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.compresstrack.nonefound"),
407                                 I18nManager.getText("dialog.compresstrack.title"), JOptionPane.WARNING_MESSAGE);
408                 }
409         }
410
411
412         /**
413          * Reverse a section of the track
414          */
415         public void reverseRange()
416         {
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)))
426                 {
427                         UndoReverseSection undo = new UndoReverseSection(selStart, selEnd);
428                         // call track to reverse range
429                         if (_track.reverseRange(selStart, selEnd))
430                         {
431                                 _undoStack.add(undo);
432                         }
433                 }
434         }
435
436
437         /**
438          * Interpolate the two selected points
439          */
440         public void interpolateSelection()
441         {
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;
449
450                 UndoInsert undo = new UndoInsert(_trackInfo.getSelection().getStart() + 1,
451                         numPoints);
452                 // call track to interpolate
453                 if (_trackInfo.interpolate(numPoints))
454                 {
455                         _undoStack.add(undo);
456                 }
457         }
458
459
460         /**
461          * Rearrange the waypoints into track order
462          */
463         public void rearrangeWaypoints(int inFunction)
464         {
465                 UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(_track);
466                 boolean success = false;
467                 if (inFunction == REARRANGE_TO_START || inFunction == REARRANGE_TO_END)
468                 {
469                         // Collect the waypoints to the start or end of the track
470                         success = _track.collectWaypoints(inFunction == REARRANGE_TO_START);
471                 }
472                 else
473                 {
474                         // Interleave the waypoints into track order
475                         success = _track.interleaveWaypoints();
476                 }
477                 if (success)
478                 {
479                         _undoStack.add(undo);
480                 }
481                 else
482                 {
483                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.rearrange.noop"),
484                                 I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);
485                 }
486         }
487
488
489         /**
490          * Open a new window with the 3d view
491          */
492         public void show3dWindow()
493         {
494                 ThreeDWindow window = WindowFactory.getWindow(this, _frame);
495                 if (window == null)
496                 {
497                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.function.nojava3d"),
498                                 I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE);
499                 }
500                 else
501                 {
502                         try
503                         {
504                                 // Pass the track object and show the window
505                                 window.setTrack(_track);
506                                 window.show();
507                         }
508                         catch (ThreeDException e)
509                         {
510                                 JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.3d") + ": " + e.getMessage(),
511                                         I18nManager.getText("error.3d.title"), JOptionPane.ERROR_MESSAGE);
512                         }
513                 }
514         }
515
516
517         /**
518          * Select all points
519          */
520         public void selectAll()
521         {
522                 _trackInfo.getSelection().select(0, 0, _track.getNumPoints()-1);
523         }
524
525         /**
526          * Select nothing
527          */
528         public void selectNone()
529         {
530                 _trackInfo.getSelection().clearAll();
531         }
532
533         /**
534          * Receive loaded data and optionally merge with current Track
535          * @param inFieldArray array of fields
536          * @param inDataArray array of data
537          */
538         public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename)
539         {
540                 // Decide whether to load or append
541                 if (_track != null && _track.getNumPoints() > 0)
542                 {
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)
549                         {
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();
556                         }
557                         else if (answer == JOptionPane.NO_OPTION)
558                         {
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);
564                         }
565                 }
566                 else
567                 {
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);
573                 }
574                 _broker.informSubscribers();
575                 // update menu
576                 _menuManager.informFileLoaded();
577         }
578
579
580         /**
581          * Accept a list of loaded photos
582          * @param inPhotoList List of Photo objects
583          */
584         public void informPhotosLoaded(List inPhotoList)
585         {
586                 if (inPhotoList != null && !inPhotoList.isEmpty())
587                 {
588                         // TODO: Attempt to restrict loaded photos to current area (if any) ?
589                         int numAdded = _trackInfo.addPhotos(inPhotoList);
590                         if (numAdded > 0)
591                         {
592                                 _undoStack.add(new UndoLoadPhotos(numAdded));
593                         }
594                         if (numAdded == 1)
595                         {
596                                 JOptionPane.showMessageDialog(_frame,
597                                         "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photoadded"),
598                                         I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
599                         }
600                         else
601                         {
602                                 JOptionPane.showMessageDialog(_frame,
603                                         "" + numAdded + " " + I18nManager.getText("dialog.jpegload.photosadded"),
604                                         I18nManager.getText("dialog.jpegload.title"), JOptionPane.INFORMATION_MESSAGE);
605                         }
606                         // TODO: Improve message when photo(s) fail to load (eg already added)
607                         _broker.informSubscribers();
608                         // update menu
609                         _menuManager.informFileLoaded();
610                 }
611         }
612
613
614         /**
615          * Inform the app that the data has been saved
616          */
617         public void informDataSaved()
618         {
619                 _lastSavePosition = _undoStack.size();
620         }
621
622
623         /**
624          * Begin undo process
625          */
626         public void beginUndo()
627         {
628                 if (_undoStack.isEmpty())
629                 {
630                         JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.undo.none.text"),
631                                 I18nManager.getText("dialog.undo.none.title"), JOptionPane.INFORMATION_MESSAGE);
632                 }
633                 else
634                 {
635                         new UndoManager(this, _frame);
636                 }
637         }
638
639
640         /**
641          * Clear the undo stack (losing all undo information
642          */
643         public void clearUndo()
644         {
645                 // Exit if nothing to undo
646                 if (_undoStack == null || _undoStack.isEmpty())
647                         return;
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)
656                 {
657                         _undoStack.clear();
658                         _lastSavePosition = 0;
659                         if (unsaved) _lastSavePosition = -1;
660                         _broker.informSubscribers();
661                 }
662         }
663
664
665         /**
666          * Undo the specified number of actions
667          * @param inNumUndos number of actions to undo
668          */
669         public void undoActions(int inNumUndos)
670         {
671                 try
672                 {
673                         for (int i=0; i<inNumUndos; i++)
674                         {
675                                 ((UndoOperation) _undoStack.pop()).performUndo(_trackInfo);
676                         }
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);
681                 }
682                 catch (UndoException ue)
683                 {
684                         JOptionPane.showMessageDialog(_frame,
685                                 I18nManager.getText("error.undofailed.text") + " : " + ue.getMessage(),
686                                 I18nManager.getText("error.undofailed.title"),
687                                 JOptionPane.ERROR_MESSAGE);
688                         _undoStack.clear();
689                         _broker.informSubscribers();
690                 }
691                 catch (EmptyStackException empty) {}
692         }
693
694
695         /**
696          * Helper method to parse an Object into an integer
697          * @param inObject object, eg from dialog
698          * @return int value given
699          */
700         private static int parseNumber(Object inObject)
701         {
702                 int num = 0;
703                 if (inObject != null)
704                 {
705                         try
706                         {
707                                 num = Integer.parseInt(inObject.toString());
708                         }
709                         catch (NumberFormatException nfe)
710                         {}
711                 }
712                 return num;
713         }
714 }