1 package tim.prune.load;
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
6 import java.util.ArrayList;
8 import javax.swing.BorderFactory;
9 import javax.swing.BoxLayout;
10 import javax.swing.JButton;
11 import javax.swing.JCheckBox;
12 import javax.swing.JDialog;
13 import javax.swing.JFileChooser;
14 import javax.swing.JFrame;
15 import javax.swing.JLabel;
16 import javax.swing.JOptionPane;
17 import javax.swing.JPanel;
18 import javax.swing.JProgressBar;
21 import tim.prune.I18nManager;
22 import tim.prune.data.Altitude;
23 import tim.prune.data.DataPoint;
24 import tim.prune.data.Latitude;
25 import tim.prune.data.Longitude;
26 import tim.prune.data.Photo;
27 import tim.prune.drew.jpeg.ExifReader;
28 import tim.prune.drew.jpeg.JpegData;
29 import tim.prune.drew.jpeg.JpegException;
30 import tim.prune.drew.jpeg.Rational;
33 * Class to manage the loading of Jpegs and dealing with the GPS data from them
35 public class JpegLoader implements Runnable
37 private App _app = null;
38 private JFrame _parentFrame = null;
39 private JFileChooser _fileChooser = null;
40 private JCheckBox _subdirCheckbox = null;
41 private JDialog _progressDialog = null;
42 private JProgressBar _progressBar = null;
43 private int[] _fileCounts = null;
44 private boolean _cancelled = false;
45 private ArrayList _photos = null;
50 * @param inApp Application object to inform of photo load
51 * @param inParentFrame parent frame to reference for dialogs
53 public JpegLoader(App inApp, JFrame inParentFrame)
56 _parentFrame = inParentFrame;
60 * Select an input file and open the GUI frame
61 * to select load options
63 public void openFile()
65 if (_fileChooser == null)
67 _fileChooser = new JFileChooser();
68 _fileChooser.setMultiSelectionEnabled(true);
69 _fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
70 _subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories"));
71 _subdirCheckbox.setSelected(true);
72 _fileChooser.setAccessory(_subdirCheckbox);
74 if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
76 // Bring up dialog before starting
78 new Thread(this).start();
84 * Show the main dialog
86 private void showDialog()
88 _progressDialog = new JDialog(_parentFrame, I18nManager.getText("dialog.jpegload.progress.title"));
89 _progressDialog.setLocationRelativeTo(_parentFrame);
90 _progressBar = new JProgressBar(0, 100);
91 _progressBar.setValue(0);
92 _progressBar.setStringPainted(true);
93 _progressBar.setString("");
94 JPanel panel = new JPanel();
95 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
96 panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
97 panel.add(new JLabel(I18nManager.getText("dialog.jpegload.progress")));
98 panel.add(_progressBar);
99 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
100 cancelButton.addActionListener(new ActionListener() {
101 public void actionPerformed(ActionEvent e)
106 panel.add(cancelButton);
107 _progressDialog.getContentPane().add(panel);
108 _progressDialog.pack();
109 _progressDialog.show();
114 * Run method for performing tasks in separate thread
118 // Initialise arrays, errors, summaries
119 _fileCounts = new int[4]; // files, jpegs, exifs, gps
120 _photos = new ArrayList();
121 // Loop over selected files/directories
122 File[] files = _fileChooser.getSelectedFiles();
123 int numFiles = countFileList(files, true, _subdirCheckbox.isSelected());
124 // if (false) System.out.println("Found " + numFiles + " files");
125 _progressBar.setMaximum(numFiles);
126 _progressBar.setValue(0);
128 processFileList(files, true, _subdirCheckbox.isSelected());
129 _progressDialog.hide();
130 if (_cancelled) return;
131 // System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1] + ", " + _fileCounts[2] + ", " + _fileCounts[3]);
132 if (_fileCounts[0] == 0)
134 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nofilesfound"),
135 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
137 else if (_fileCounts[1] == 0)
139 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nojpegsfound"),
140 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
142 else if (_fileCounts[2] == 0)
144 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.noexiffound"),
145 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
147 else if (_fileCounts[3] == 0)
149 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nogpsfound"),
150 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
154 // Load information into dialog for confirmation
155 _app.informPhotosLoaded(_photos);
161 * Process a list of files and/or directories
162 * @param inFiles array of file/directories
163 * @param inFirstDir true if first directory
164 * @param inDescend true to descend to subdirectories
166 private void processFileList(File[] inFiles, boolean inFirstDir, boolean inDescend)
170 // Loop over elements in array
171 for (int i=0; i<inFiles.length; i++)
173 File file = inFiles[i];
174 if (file.exists() && file.canRead())
176 // Check whether it's a file or a directory
181 else if (file.isDirectory() && (inFirstDir || inDescend))
183 // Always process first directory,
184 // only process subdirectories if checkbox selected
185 processDirectory(file, inDescend);
190 // file doesn't exist or isn't readable - record error
193 if (_cancelled) break;
200 * Process the given file, by attempting to extract its tags
201 * @param inFile file object to read
203 private void processFile(File inFile)
205 _fileCounts[0]++; // file found
206 _progressBar.setValue(_fileCounts[0]);
207 _progressBar.setString("" + _fileCounts[0] + " / " + _progressBar.getMaximum());
208 _progressBar.repaint();
211 JpegData jpegData = new ExifReader(inFile).extract();
212 _fileCounts[1]++; // jpeg found (no exception thrown)
213 // if (jpegData.getNumErrors() > 0)
214 // System.out.println("Number of errors was: " + jpegData.getNumErrors() + ": " + jpegData.getErrors().get(0));
215 if (jpegData.getExifDataPresent())
216 _fileCounts[2]++; // exif found
217 if (jpegData.isValid())
219 // if (false && jpegData.getTimestamp() != null)
220 // System.out.println("Timestamp is " + jpegData.getTimestamp()[0].toString() + ":" + jpegData.getTimestamp()[1].toString() + ":" + jpegData.getTimestamp()[2].toString());
221 // if (false && jpegData.getDatestamp() != null)
222 // System.out.println("Datestamp is " + jpegData.getDatestamp()[0].toString() + ":" + jpegData.getDatestamp()[1].toString() + ":" + jpegData.getDatestamp()[2].toString());
223 // Make DataPoint and Photo
224 DataPoint point = createDataPoint(jpegData);
225 Photo photo = new Photo(inFile);
226 point.setPhoto(photo);
227 photo.setDataPoint(point);
229 // System.out.println("Made photo: " + photo.getFile().getAbsolutePath() + " with the datapoint: "
230 // + point.getLatitude().output(Latitude.FORMAT_DEG_MIN_SEC) + ", "
231 // + point.getLongitude().output(Longitude.FORMAT_DEG_MIN_SEC) + ", "
232 // + point.getAltitude().getValue(Altitude.FORMAT_METRES));
236 catch (JpegException jpe) { // don't list errors, just count them
242 * Process the given directory, by looping over its contents
243 * and recursively through its subdirectories
244 * @param inDirectory directory to read
245 * @param inDescend true to descend subdirectories
247 private void processDirectory(File inDirectory, boolean inDescend)
249 File[] files = inDirectory.listFiles();
250 processFileList(files, false, inDescend);
255 * Recursively count the selected Files so we can draw a progress bar
256 * @param inFiles file list
257 * @param inFirstDir true if first directory
258 * @param inDescend true to descend to subdirectories
259 * @return count of the files selected
261 private int countFileList(File[] inFiles, boolean inFirstDir, boolean inDescend)
266 // Loop over elements in array
267 for (int i=0; i<inFiles.length; i++)
269 File file = inFiles[i];
270 if (file.exists() && file.canRead())
272 // Check whether it's a file or a directory
277 else if (file.isDirectory() && (inFirstDir || inDescend))
279 fileCount += countFileList(file.listFiles(), false, inDescend);
289 * Create a DataPoint object from the given jpeg data
290 * @param inData Jpeg data including coordinates
291 * @return DataPoint object for Track
293 private static DataPoint createDataPoint(JpegData inData)
295 // Create model objects from jpeg data
296 double latval = getCoordinateDoubleValue(inData.getLatitude(),
297 inData.getLatitudeRef() == 'N' || inData.getLatitudeRef() == 'n');
298 Latitude latitude = new Latitude(latval, Latitude.FORMAT_NONE);
299 double lonval = getCoordinateDoubleValue(inData.getLongitude(),
300 inData.getLongitudeRef() == 'E' || inData.getLongitudeRef() == 'e');
301 Longitude longitude = new Longitude(lonval, Longitude.FORMAT_NONE);
302 Altitude altitude = new Altitude(inData.getAltitude().intValue(), Altitude.FORMAT_METRES);
303 return new DataPoint(latitude, longitude, altitude);
308 * Convert an array of 3 Rational numbers into a double coordinate value
309 * @param inRationals array of three Rational objects
310 * @param isPositive true for positive hemisphere, for positive double value
311 * @return double value of coordinate, either positive or negative
313 private static double getCoordinateDoubleValue(Rational[] inRationals, boolean isPositive)
315 if (inRationals == null || inRationals.length != 3) return 0.0;
316 double value = inRationals[0].doubleValue() // degrees
317 + inRationals[1].doubleValue() / 60.0 // minutes
318 + inRationals[2].doubleValue() / 60.0 / 60.0; // seconds
319 // make sure it's the correct sign
320 value = Math.abs(value);
321 if (!isPositive) value = -value;