001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GridBagLayout;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.awt.event.KeyAdapter;
010import java.awt.event.KeyEvent;
011import java.io.File;
012import java.io.IOException;
013import java.io.OutputStream;
014import java.text.MessageFormat;
015import java.util.Calendar;
016
017import javax.swing.JButton;
018import javax.swing.JCheckBox;
019import javax.swing.JLabel;
020import javax.swing.JList;
021import javax.swing.JOptionPane;
022import javax.swing.JPanel;
023import javax.swing.JScrollPane;
024import javax.swing.ListSelectionModel;
025
026import org.openstreetmap.josm.Main;
027import org.openstreetmap.josm.data.gpx.GpxConstants;
028import org.openstreetmap.josm.data.gpx.GpxData;
029import org.openstreetmap.josm.gui.ExtendedDialog;
030import org.openstreetmap.josm.gui.layer.GpxLayer;
031import org.openstreetmap.josm.gui.layer.Layer;
032import org.openstreetmap.josm.gui.layer.OsmDataLayer;
033import org.openstreetmap.josm.gui.widgets.JosmTextArea;
034import org.openstreetmap.josm.gui.widgets.JosmTextField;
035import org.openstreetmap.josm.tools.CheckParameterUtil;
036import org.openstreetmap.josm.tools.GBC;
037
038/**
039 * Exports data to a .gpx file. Data may be native GPX or OSM data which will be converted.
040 * @since 1949
041 */
042public class GpxExporter extends FileExporter implements GpxConstants {
043
044    private static final String GPL_WARNING = "<html><font color='red' size='-2'>"
045        + tr("Note: GPL is not compatible with the OSM license. Do not upload GPL licensed tracks.") + "</html>";
046
047    private static final String[] LICENSES = {
048            "Creative Commons By-SA",
049            "Open Database License (ODbL)",
050            "public domain",
051            "GNU Lesser Public License (LGPL)",
052            "BSD License (MIT/X11)"};
053
054    private static final String[] URLS = {
055            "https://creativecommons.org/licenses/by-sa/3.0",
056            "http://opendatacommons.org/licenses/odbl/1.0",
057            "public domain",
058            "https://www.gnu.org/copyleft/lesser.html",
059            "http://www.opensource.org/licenses/bsd-license.php"};
060
061    /**
062     * Constructs a new {@code GpxExporter}.
063     */
064    public GpxExporter() {
065        super(GpxImporter.FILE_FILTER);
066    }
067
068    @Override
069    public boolean acceptFile(File pathname, Layer layer) {
070        if (!(layer instanceof OsmDataLayer) && !(layer instanceof GpxLayer))
071            return false;
072        return super.acceptFile(pathname, layer);
073    }
074
075    @Override
076    public void exportData(File file, Layer layer) throws IOException {
077        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
078        if (!(layer instanceof OsmDataLayer) && !(layer instanceof GpxLayer))
079            throw new IllegalArgumentException(MessageFormat.format("Expected instance of OsmDataLayer or GpxLayer. Got ''{0}''.", layer
080                    .getClass().getName()));
081        CheckParameterUtil.ensureParameterNotNull(file, "file");
082
083        String fn = file.getPath();
084        if (fn.indexOf('.') == -1) {
085            fn += ".gpx";
086            file = new File(fn);
087        }
088
089        // open the dialog asking for options
090        JPanel p = new JPanel(new GridBagLayout());
091
092        GpxData gpxData;
093        // At this moment, we only need to know the attributes of the GpxData,
094        // conversion of OsmDataLayer (if needed) will be done after the dialog is closed.
095        if (layer instanceof GpxLayer) {
096            gpxData = ((GpxLayer) layer).data;
097        } else {
098            gpxData = new GpxData();
099        }
100
101        p.add(new JLabel(tr("GPS track description")), GBC.eol());
102        JosmTextArea desc = new JosmTextArea(3, 40);
103        desc.setWrapStyleWord(true);
104        desc.setLineWrap(true);
105        desc.setText(gpxData.getString(META_DESC));
106        p.add(new JScrollPane(desc), GBC.eop().fill(GBC.BOTH));
107
108        JCheckBox author = new JCheckBox(tr("Add author information"), Main.pref.getBoolean("lastAddAuthor", true));
109        p.add(author, GBC.eol());
110
111        JLabel nameLabel = new JLabel(tr("Real name"));
112        p.add(nameLabel, GBC.std().insets(10, 0, 5, 0));
113        JosmTextField authorName = new JosmTextField();
114        p.add(authorName, GBC.eol().fill(GBC.HORIZONTAL));
115        nameLabel.setLabelFor(authorName);
116
117        JLabel emailLabel = new JLabel(tr("E-Mail"));
118        p.add(emailLabel, GBC.std().insets(10, 0, 5, 0));
119        JosmTextField email = new JosmTextField();
120        p.add(email, GBC.eol().fill(GBC.HORIZONTAL));
121        emailLabel.setLabelFor(email);
122
123        JLabel copyrightLabel = new JLabel(tr("Copyright (URL)"));
124        p.add(copyrightLabel, GBC.std().insets(10, 0, 5, 0));
125        JosmTextField copyright = new JosmTextField();
126        p.add(copyright, GBC.std().fill(GBC.HORIZONTAL));
127        copyrightLabel.setLabelFor(copyright);
128
129        JButton predefined = new JButton(tr("Predefined"));
130        p.add(predefined, GBC.eol().insets(5, 0, 0, 0));
131
132        JLabel copyrightYearLabel = new JLabel(tr("Copyright year"));
133        p.add(copyrightYearLabel, GBC.std().insets(10, 0, 5, 5));
134        JosmTextField copyrightYear = new JosmTextField("");
135        p.add(copyrightYear, GBC.eol().fill(GBC.HORIZONTAL));
136        copyrightYearLabel.setLabelFor(copyrightYear);
137
138        JLabel warning = new JLabel("<html><font size='-2'>&nbsp;</html");
139        p.add(warning, GBC.eol().fill(GBC.HORIZONTAL).insets(15, 0, 0, 0));
140        addDependencies(gpxData, author, authorName, email, copyright, predefined, copyrightYear, nameLabel, emailLabel,
141                copyrightLabel, copyrightYearLabel, warning);
142
143        p.add(new JLabel(tr("Keywords")), GBC.eol());
144        JosmTextField keywords = new JosmTextField();
145        keywords.setText(gpxData.getString(META_KEYWORDS));
146        p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
147
148        ExtendedDialog ed = new ExtendedDialog(Main.parent,
149                tr("Export options"),
150                new String[] {tr("Export and Save"), tr("Cancel")});
151        ed.setButtonIcons(new String[] {"exportgpx", "cancel"});
152        ed.setContent(p);
153        ed.showDialog();
154
155        if (ed.getValue() != 1) {
156            setCanceled(true);
157            return;
158        }
159        setCanceled(false);
160
161        Main.pref.put("lastAddAuthor", author.isSelected());
162        if (!authorName.getText().isEmpty()) {
163            Main.pref.put("lastAuthorName", authorName.getText());
164        }
165        if (!copyright.getText().isEmpty()) {
166            Main.pref.put("lastCopyright", copyright.getText());
167        }
168
169        if (layer instanceof OsmDataLayer) {
170            gpxData = ((OsmDataLayer) layer).toGpxData();
171        } else if (layer instanceof GpxLayer) {
172            gpxData = ((GpxLayer) layer).data;
173        } else {
174            gpxData = OsmDataLayer.toGpxData(Main.main.getCurrentDataSet(), file);
175        }
176
177        // add author and copyright details to the gpx data
178        if (author.isSelected()) {
179            if (!authorName.getText().isEmpty()) {
180                gpxData.put(META_AUTHOR_NAME, authorName.getText());
181                gpxData.put(META_COPYRIGHT_AUTHOR, authorName.getText());
182            }
183            if (!email.getText().isEmpty()) {
184                gpxData.put(META_AUTHOR_EMAIL, email.getText());
185            }
186            if (!copyright.getText().isEmpty()) {
187                gpxData.put(META_COPYRIGHT_LICENSE, copyright.getText());
188            }
189            if (!copyrightYear.getText().isEmpty()) {
190                gpxData.put(META_COPYRIGHT_YEAR, copyrightYear.getText());
191            }
192        }
193
194        // add the description to the gpx data
195        if (!desc.getText().isEmpty()) {
196            gpxData.put(META_DESC, desc.getText());
197        }
198
199        // add keywords to the gpx data
200        if (!keywords.getText().isEmpty()) {
201            gpxData.put(META_KEYWORDS, keywords.getText());
202        }
203
204        try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) {
205            new GpxWriter(fo).write(gpxData);
206            fo.flush();
207        } catch (IOException x) {
208            Main.error(x);
209            JOptionPane.showMessageDialog(Main.parent, tr("Error while exporting {0}:\n{1}", fn, x.getMessage()),
210                    tr("Error"), JOptionPane.ERROR_MESSAGE);
211        }
212    }
213
214    private static void enableCopyright(final GpxData data, final JosmTextField copyright, final JButton predefined,
215            final JosmTextField copyrightYear, final JLabel copyrightLabel, final JLabel copyrightYearLabel,
216            final JLabel warning, boolean enable) {
217        copyright.setEnabled(enable);
218        predefined.setEnabled(enable);
219        copyrightYear.setEnabled(enable);
220        copyrightLabel.setEnabled(enable);
221        copyrightYearLabel.setEnabled(enable);
222        warning.setText(enable ? GPL_WARNING : "<html><font size='-2'>&nbsp;</html");
223
224        if (enable) {
225            if (copyrightYear.getText().isEmpty()) {
226                String sCopyrightYear = data.getString(META_COPYRIGHT_YEAR);
227                if (sCopyrightYear == null) {
228                    sCopyrightYear = Integer.toString(Calendar.getInstance().get(Calendar.YEAR));
229                }
230                copyrightYear.setText(sCopyrightYear);
231            }
232            if (copyright.getText().isEmpty()) {
233                String sCopyright = data.getString(META_COPYRIGHT_LICENSE);
234                if (sCopyright == null) {
235                    sCopyright = Main.pref.get("lastCopyright", "https://creativecommons.org/licenses/by-sa/2.5");
236                }
237                copyright.setText(sCopyright);
238                copyright.setCaretPosition(0);
239            }
240        } else {
241            copyrightYear.setText("");
242            copyright.setText("");
243        }
244    }
245
246    // CHECKSTYLE.OFF: ParameterNumber
247
248    /**
249     * Add all those listeners to handle the enable state of the fields.
250     * @param data GPX data
251     * @param author Author checkbox
252     * @param authorName Author name textfield
253     * @param email E-mail textfield
254     * @param copyright Copyright textfield
255     * @param predefined Predefined button
256     * @param copyrightYear Copyright year textfield
257     * @param nameLabel Name label
258     * @param emailLabel E-mail label
259     * @param copyrightLabel Copyright label
260     * @param copyrightYearLabel Copyright year label
261     * @param warning Warning label
262     */
263    private static void addDependencies(
264            final GpxData data,
265            final JCheckBox author,
266            final JosmTextField authorName,
267            final JosmTextField email,
268            final JosmTextField copyright,
269            final JButton predefined,
270            final JosmTextField copyrightYear,
271            final JLabel nameLabel,
272            final JLabel emailLabel,
273            final JLabel copyrightLabel,
274            final JLabel copyrightYearLabel,
275            final JLabel warning) {
276
277        // CHECKSTYLE.ON: ParameterNumber
278        ActionListener authorActionListener = new ActionListener() {
279            @Override
280            public void actionPerformed(ActionEvent e) {
281                boolean b = author.isSelected();
282                authorName.setEnabled(b);
283                email.setEnabled(b);
284                nameLabel.setEnabled(b);
285                emailLabel.setEnabled(b);
286                if (b) {
287                    String sAuthorName = data.getString(META_AUTHOR_NAME);
288                    if (sAuthorName == null) {
289                        sAuthorName = Main.pref.get("lastAuthorName");
290                    }
291                    authorName.setText(sAuthorName);
292                    String sEmail = data.getString(META_AUTHOR_EMAIL);
293                    if (sEmail == null) {
294                        sEmail = Main.pref.get("lastAuthorEmail");
295                    }
296                    email.setText(sEmail);
297                } else {
298                    authorName.setText("");
299                    email.setText("");
300                }
301                boolean isAuthorSet = !authorName.getText().isEmpty();
302                GpxExporter.enableCopyright(data, copyright, predefined, copyrightYear, copyrightLabel, copyrightYearLabel, warning,
303                        b && isAuthorSet);
304            }
305        };
306        author.addActionListener(authorActionListener);
307
308        KeyAdapter authorNameListener = new KeyAdapter() {
309            @Override public void keyReleased(KeyEvent e) {
310                boolean b = !authorName.getText().isEmpty() && author.isSelected();
311                GpxExporter.enableCopyright(data, copyright, predefined, copyrightYear, copyrightLabel, copyrightYearLabel, warning, b);
312            }
313        };
314        authorName.addKeyListener(authorNameListener);
315
316        predefined.addActionListener(new ActionListener() {
317            @Override
318            public void actionPerformed(ActionEvent e) {
319                JList<String> l = new JList<>(LICENSES);
320                l.setVisibleRowCount(LICENSES.length);
321                l.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
322                int answer = JOptionPane.showConfirmDialog(
323                        Main.parent,
324                        new JScrollPane(l),
325                        tr("Choose a predefined license"),
326                        JOptionPane.OK_CANCEL_OPTION,
327                        JOptionPane.QUESTION_MESSAGE
328                );
329                if (answer != JOptionPane.OK_OPTION || l.getSelectedIndex() == -1)
330                    return;
331                StringBuilder license = new StringBuilder();
332                for (int i : l.getSelectedIndices()) {
333                    if (i == 2) {
334                        license = new StringBuilder("public domain");
335                        break;
336                    }
337                    if (license.length() > 0) {
338                        license.append(", ");
339                    }
340                    license.append(URLS[i]);
341                }
342                copyright.setText(license.toString());
343                copyright.setCaretPosition(0);
344            }
345        });
346
347        authorActionListener.actionPerformed(null);
348        authorNameListener.keyReleased(null);
349    }
350}