1 package tim.prune.save;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.GridLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.OutputStreamWriter;
13 import java.io.Writer;
14 import java.nio.charset.Charset;
16 import javax.swing.BorderFactory;
17 import javax.swing.Box;
18 import javax.swing.BoxLayout;
19 import javax.swing.JButton;
20 import javax.swing.JCheckBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JPanel;
27 import javax.swing.JTextField;
30 import tim.prune.GenericFunction;
31 import tim.prune.GpsPruner;
32 import tim.prune.I18nManager;
33 import tim.prune.UpdateMessageBroker;
34 import tim.prune.config.Config;
35 import tim.prune.data.Altitude;
36 import tim.prune.data.AudioFile;
37 import tim.prune.data.Coordinate;
38 import tim.prune.data.DataPoint;
39 import tim.prune.data.Field;
40 import tim.prune.data.MediaFile;
41 import tim.prune.data.Photo;
42 import tim.prune.data.Timestamp;
43 import tim.prune.data.TrackInfo;
44 import tim.prune.gui.DialogCloser;
45 import tim.prune.load.GenericFileFilter;
46 import tim.prune.save.xml.GpxCacherList;
50 * Class to export track information
51 * into a specified Gpx file
53 public class GpxExporter extends GenericFunction implements Runnable
55 private TrackInfo _trackInfo = null;
56 private JDialog _dialog = null;
57 private JTextField _nameField = null;
58 private JTextField _descriptionField = null;
59 private PointTypeSelector _pointTypeSelector = null;
60 private JCheckBox _timestampsCheckbox = null;
61 private JCheckBox _copySourceCheckbox = null;
62 private File _exportFile = null;
64 /** this program name */
65 private static final String GPX_CREATOR = "Prune v" + GpsPruner.VERSION_NUMBER + " activityworkshop.net";
70 * @param inApp app object
72 public GpxExporter(App inApp)
75 _trackInfo = inApp.getTrackInfo();
79 public String getNameKey() {
80 return "function.exportgpx";
84 * Show the dialog to select options and export file
91 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
92 _dialog.setLocationRelativeTo(_parentFrame);
93 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
94 _dialog.getContentPane().add(makeDialogComponents());
97 _pointTypeSelector.init(_app.getTrackInfo());
98 _dialog.setVisible(true);
103 * Create dialog components
104 * @return Panel containing all gui elements in dialog
106 private Component makeDialogComponents()
108 JPanel dialogPanel = new JPanel();
109 dialogPanel.setLayout(new BorderLayout());
110 JPanel mainPanel = new JPanel();
111 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
112 // Make a central panel with the text boxes
113 JPanel descPanel = new JPanel();
114 descPanel.setLayout(new GridLayout(2, 2));
115 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
116 _nameField = new JTextField(10);
117 descPanel.add(_nameField);
118 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
119 _descriptionField = new JTextField(10);
120 descPanel.add(_descriptionField);
121 mainPanel.add(descPanel);
122 mainPanel.add(Box.createVerticalStrut(5));
123 // point type selection (track points, waypoints, photo points)
124 _pointTypeSelector = new PointTypeSelector();
125 mainPanel.add(_pointTypeSelector);
126 // checkboxes for timestamps and copying
127 JPanel checkPanel = new JPanel();
128 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
129 _timestampsCheckbox.setSelected(true);
130 checkPanel.add(_timestampsCheckbox);
131 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
132 _copySourceCheckbox.setSelected(true);
133 checkPanel.add(_copySourceCheckbox);
134 mainPanel.add(checkPanel);
135 dialogPanel.add(mainPanel, BorderLayout.CENTER);
137 // close dialog if escape pressed
138 _nameField.addKeyListener(new DialogCloser(_dialog));
139 // button panel at bottom
140 JPanel buttonPanel = new JPanel();
141 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
142 JButton okButton = new JButton(I18nManager.getText("button.ok"));
143 ActionListener okListener = new ActionListener() {
144 public void actionPerformed(ActionEvent e)
149 okButton.addActionListener(okListener);
150 _descriptionField.addActionListener(okListener);
151 buttonPanel.add(okButton);
152 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
153 cancelButton.addActionListener(new ActionListener() {
154 public void actionPerformed(ActionEvent e) {
158 buttonPanel.add(cancelButton);
159 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
160 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
166 * Start the export process based on the input parameters
168 private void startExport()
170 // OK pressed, so check selections
171 if (!_pointTypeSelector.getAnythingSelected()) {
172 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
173 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
176 // Choose output file
177 File saveFile = chooseGpxFile(_parentFrame);
178 if (saveFile != null)
180 // New file or overwrite confirmed, so initiate export in separate thread
181 _exportFile = saveFile;
182 new Thread(this).start();
187 * Select a GPX file to save to
188 * @param inParentFrame parent frame for file chooser dialog
189 * @return selected File, or null if selection cancelled
191 public static File chooseGpxFile(JFrame inParentFrame)
193 File saveFile = null;
194 JFileChooser fileChooser = new JFileChooser();
195 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
196 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
197 fileChooser.setAcceptAllFileFilterUsed(false);
198 // start from directory in config which should be set
199 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
200 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
202 // Allow choose again if an existing file is selected
203 boolean chooseAgain = false;
207 if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
209 // OK pressed and file chosen
210 File file = fileChooser.getSelectedFile();
211 // Check file extension
212 if (!file.getName().toLowerCase().endsWith(".gpx"))
214 file = new File(file.getAbsolutePath() + ".gpx");
216 // Check if file exists and if necessary prompt for overwrite
217 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
218 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
219 I18nManager.getText("dialog.save.overwrite.text"),
220 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
221 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
222 == JOptionPane.YES_OPTION)
224 // new file or overwrite confirmed
229 // file exists and overwrite cancelled - select again
233 } while (chooseAgain);
238 * Run method for controlling separate thread for exporting
242 OutputStreamWriter writer = null;
245 // normal writing to file
246 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
247 final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
248 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
249 _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
251 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
252 _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
256 // Store directory in config for later
257 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
259 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
260 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
261 + " " + _exportFile.getAbsolutePath());
262 // export successful so need to close dialog and return
266 catch (IOException ioe)
268 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
270 if (writer != null) writer.close();
272 catch (IOException ioe2) {}
273 JOptionPane.showMessageDialog(_parentFrame,
274 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
275 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
277 // if not returned already, export failed so need to recall the file selection
283 * Export the information to the given writer
284 * @param inWriter writer object
285 * @param inInfo track info object
286 * @param inName name of track (optional)
287 * @param inDesc description of track (optional)
288 * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
289 * @param inUseCopy true to copy source if available
290 * @return number of points written
291 * @throws IOException if io errors occur on write
293 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
294 String inDesc, boolean[] inSaveFlags, boolean inUseCopy) throws IOException
296 // Instantiate source file cachers in case we want to copy output
297 GpxCacherList gpxCachers = null;
298 if (inUseCopy) gpxCachers = new GpxCacherList(inInfo.getFileInfo());
299 // Write or copy headers
300 inWriter.write(getXmlHeaderString(inWriter));
301 inWriter.write(getGpxHeaderString(gpxCachers));
303 String trackName = "PruneTrack";
304 if (inName != null && !inName.equals(""))
307 inWriter.write("\t<name>");
308 inWriter.write(trackName);
309 inWriter.write("</name>\n");
312 inWriter.write("\t<desc>");
313 inWriter.write((inDesc != null && !inDesc.equals(""))?inDesc:"Export from Prune");
314 inWriter.write("</desc>\n");
317 DataPoint point = null;
318 final boolean exportTrackpoints = inSaveFlags[0];
319 final boolean exportWaypoints = inSaveFlags[1];
320 final boolean exportPhotos = inSaveFlags[2];
321 final boolean exportAudios = inSaveFlags[3];
322 final boolean exportSelection = inSaveFlags[4];
323 final boolean exportTimestamps = inSaveFlags[5];
325 int selStart = -1, selEnd = -1;
326 if (exportSelection) {
327 selStart = inInfo.getSelection().getStart();
328 selEnd = inInfo.getSelection().getEnd();
330 // Loop over waypoints
331 final int numPoints = inInfo.getTrack().getNumPoints();
333 for (i=0; i<numPoints; i++)
335 point = inInfo.getTrack().getPoint(i);
336 if (!exportSelection || (i>=selStart && i<=selEnd)) {
337 // Make a wpt element for each waypoint
338 if (point.isWaypoint()) {
341 String pointSource = (inUseCopy?getPointSource(gpxCachers, point):null);
342 if (pointSource != null) {
343 inWriter.write(pointSource);
344 inWriter.write('\n');
347 exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
354 // Export both route points and then track points
355 if (exportTrackpoints || exportPhotos || exportAudios)
357 // Output all route points (if any)
358 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
359 exportAudios, exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n",
361 // Output all track points, if any
362 String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
363 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
364 exportAudios, exportTimestamps, false, gpxCachers, "<trkpt", trackStart,
365 "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
368 inWriter.write("</gpx>\n");
373 * Loop through the track outputting the relevant track points
374 * @param inWriter writer object for output
375 * @param inInfo track info object containing track
376 * @param inExportSelection true to just output current selection
377 * @param inExportTrackpoints true to output track points
378 * @param inExportPhotos true to output photo points
379 * @param inExportAudios true to output audio points
380 * @param inExportTimestamps true to include timestamps in export
381 * @param inOnlyCopies true to only export if source can be copied
382 * @param inCachers list of GpxCachers
383 * @param inPointTag tag to match for each point
384 * @param inStartTag start tag to output
385 * @param inSegmentTag tag to output between segments (or null)
386 * @param inEndTag end tag to output
388 private static int writeTrackPoints(OutputStreamWriter inWriter,
389 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
390 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
391 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
392 String inStartTag, String inSegmentTag, String inEndTag)
395 // Note: far too many input parameters to this method but avoids duplication
396 // of output functionality for writing track points and route points
397 int numPoints = inInfo.getTrack().getNumPoints();
398 int selStart = inInfo.getSelection().getStart();
399 int selEnd = inInfo.getSelection().getEnd();
401 // Loop over track points
402 for (int i=0; i<numPoints; i++)
404 DataPoint point = inInfo.getTrack().getPoint(i);
405 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
407 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
408 || (point.getAudio()!=null && inExportAudios))
410 // get the source from the point (if any)
411 String pointSource = getPointSource(inCachers, point);
412 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
413 if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
414 if (pointSource != null || !inOnlyCopies)
416 // restart track segment if necessary
417 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
418 inWriter.write(inSegmentTag);
420 if (numSaved == 0) {inWriter.write(inStartTag);}
421 if (pointSource != null) {
422 inWriter.write(pointSource);
423 inWriter.write('\n');
426 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
433 if (numSaved > 0) {inWriter.write(inEndTag);}
439 * Get the point source for the specified point
440 * @param inCachers list of GPX cachers to ask for source
441 * @param inPoint point object
442 * @return xml source if available, or null otherwise
444 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
446 if (inCachers == null || inPoint == null) {return null;}
447 String source = inCachers.getSourceString(inPoint);
448 if (source == null || !inPoint.isModified()) {return source;}
449 // Point has been modified - maybe it's possible to modify the source
450 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
451 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
452 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
453 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
454 if (inPoint.isWaypoint()) {source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());} // only for waypoints
455 // photo / audio links
456 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
457 source = replaceMediaLinks(source, makeMediaLink(inPoint));
463 * Replace the given value into the given XML string
464 * @param inSource source XML for point
465 * @param inStartTag start tag for field
466 * @param inEndTag end tag for field
467 * @param inValue value to replace between start tag and end tag
468 * @return modified String, or null if not possible
470 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
472 if (inSource == null) {return null;}
473 // Look for start and end tags within source
474 final int startPos = inSource.indexOf(inStartTag);
475 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
476 if (startPos > 0 && endPos > 0)
478 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
479 if (inValue != null && origValue.equals(inValue)) {
483 else if (inValue == null || inValue.equals("")) {
484 // Need to delete value
485 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
488 // Need to replace value
489 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
492 // Value not found for this field in original source
493 if (inValue == null || inValue.equals("")) {return inSource;}
498 * Replace the media tags in the given XML string
499 * @param inSource source XML for point
500 * @param inValue value for the current point
501 * @return modified String, or null if not possible
503 private static String replaceMediaLinks(String inSource, String inValue)
505 if (inSource == null) {return null;}
506 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
507 // and the tags must have attributes. So either one heavily parameterized method or two.
508 // Look for start and end tags within source
509 final String STARTTEXT = "<link";
510 final String ENDTEXT = "</link>";
511 final int startPos = inSource.indexOf(STARTTEXT);
512 final int endPos = inSource.lastIndexOf(ENDTEXT);
513 if (startPos > 0 && endPos > 0)
515 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
516 if (inValue != null && origValue.equals(inValue)) {
520 else if (inValue == null || inValue.equals("")) {
521 // Need to delete value
522 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
525 // Need to replace value
526 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
529 // Value not found for this field in original source
530 if (inValue == null || inValue.equals("")) {return inSource;}
535 * Get the header string for the xml document including encoding
536 * @param inWriter writer object
537 * @return header string defining encoding
539 private static String getXmlHeaderString(OutputStreamWriter inWriter)
541 String encoding = inWriter.getEncoding();
543 encoding = Charset.forName(encoding).name();
545 catch (Exception e) {} // ignore failure to find encoding
546 return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n";
550 * Get the header string for the gpx tag
551 * @param inCachers cacher list to ask for headers, if available
552 * @return header string from cachers or as default
554 private static String getGpxHeaderString(GpxCacherList inCachers)
556 String gpxHeader = null;
557 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
558 if (gpxHeader == null || gpxHeader.length() < 5)
560 // Create default (1.0) header
561 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
562 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
563 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
564 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
566 return gpxHeader + "\n";
570 * Export the specified waypoint into the file
571 * @param inPoint waypoint to export
572 * @param inWriter writer object
573 * @param inTimestamps true to export timestamps too
574 * @param inPhoto true to export link to photo
575 * @param inAudio true to export link to audio
576 * @throws IOException on write failure
578 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
579 boolean inPhoto, boolean inAudio)
582 inWriter.write("\t<wpt lat=\"");
583 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
584 inWriter.write("\" lon=\"");
585 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
586 inWriter.write("\">\n");
587 // altitude if available
588 if (inPoint.hasAltitude())
590 inWriter.write("\t\t<ele>");
591 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
592 inWriter.write("</ele>\n");
594 // timestamp if available (point might have timestamp and then be turned into a waypoint)
595 if (inPoint.hasTimestamp() && inTimestamps)
597 inWriter.write("\t\t<time>");
598 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
599 inWriter.write("</time>\n");
601 // write waypoint name after elevation and time
602 inWriter.write("\t\t<name>");
603 inWriter.write(inPoint.getWaypointName().trim());
604 inWriter.write("</name>\n");
605 // Media links, if any
606 if (inPhoto && inPoint.getPhoto() != null)
608 inWriter.write("\t\t");
609 inWriter.write(makeMediaLink(inPoint.getPhoto()));
610 inWriter.write('\n');
612 if (inAudio && inPoint.getAudio() != null)
614 inWriter.write("\t\t");
615 inWriter.write(makeMediaLink(inPoint.getAudio()));
616 inWriter.write('\n');
618 // write waypoint type if any
619 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
623 if (!type.equals(""))
625 inWriter.write("\t\t<type>");
626 inWriter.write(type);
627 inWriter.write("</type>\n");
630 inWriter.write("\t</wpt>\n");
635 * Export the specified trackpoint into the file
636 * @param inPoint trackpoint to export
637 * @param inWriter writer object
638 * @param inTimestamps true to export timestamps too
639 * @param inExportPhoto true to export photo link
640 * @param inExportAudio true to export audio link
642 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
643 boolean inExportPhoto, boolean inExportAudio)
646 inWriter.write("\t\t<trkpt lat=\"");
647 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
648 inWriter.write("\" lon=\"");
649 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
650 inWriter.write("\">");
652 if (inPoint.hasAltitude())
654 inWriter.write("<ele>");
655 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
656 inWriter.write("</ele>");
658 // timestamp if available (and selected)
659 if (inPoint.hasTimestamp() && inTimestamps)
661 inWriter.write("<time>");
662 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
663 inWriter.write("</time>");
666 if (inPoint.getPhoto() != null && inExportPhoto) {
667 inWriter.write(makeMediaLink(inPoint.getPhoto()));
669 if (inPoint.getAudio() != null && inExportAudio) {
670 inWriter.write(makeMediaLink(inPoint.getAudio()));
672 inWriter.write("</trkpt>\n");
676 * Make the xml for the media link(s)
677 * @param inPoint point to generate text for
678 * @return link tags, or null if no links
680 private static String makeMediaLink(DataPoint inPoint)
682 Photo photo = inPoint.getPhoto();
683 AudioFile audio = inPoint.getAudio();
684 if (photo == null && audio == null) {
687 String linkText = "";
689 linkText = makeMediaLink(photo);
692 linkText += makeMediaLink(audio);
698 * Make the media link for a single media item
699 * @param inMedia media item, either photo or audio
700 * @return link for this media
702 private static String makeMediaLink(MediaFile inMedia)
704 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getFile().getName() + "</text></link>";