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     */
251    private static void addDependencies(
252            final GpxData data,
253            final JCheckBox author,
254            final JosmTextField authorName,
255            final JosmTextField email,
256            final JosmTextField copyright,
257            final JButton predefined,
258            final JosmTextField copyrightYear,
259            final JLabel nameLabel,
260            final JLabel emailLabel,
261            final JLabel copyrightLabel,
262            final JLabel copyrightYearLabel,
263            final JLabel warning) {
264
265        // CHECKSTYLE.ON: ParameterNumber
266        ActionListener authorActionListener = new ActionListener() {
267            @Override
268            public void actionPerformed(ActionEvent e) {
269                boolean b = author.isSelected();
270                authorName.setEnabled(b);
271                email.setEnabled(b);
272                nameLabel.setEnabled(b);
273                emailLabel.setEnabled(b);
274                if (b) {
275                    String sAuthorName = data.getString(META_AUTHOR_NAME);
276                    if (sAuthorName == null) {
277                        sAuthorName = Main.pref.get("lastAuthorName");
278                    }
279                    authorName.setText(sAuthorName);
280                    String sEmail = data.getString(META_AUTHOR_EMAIL);
281                    if (sEmail == null) {
282                        sEmail = Main.pref.get("lastAuthorEmail");
283                    }
284                    email.setText(sEmail);
285                } else {
286                    authorName.setText("");
287                    email.setText("");
288                }
289                boolean isAuthorSet = !authorName.getText().isEmpty();
290                GpxExporter.enableCopyright(data, copyright, predefined, copyrightYear, copyrightLabel, copyrightYearLabel, warning,
291                        b && isAuthorSet);
292            }
293        };
294        author.addActionListener(authorActionListener);
295
296        KeyAdapter authorNameListener = new KeyAdapter() {
297            @Override public void keyReleased(KeyEvent e) {
298                boolean b = !authorName.getText().isEmpty() && author.isSelected();
299                GpxExporter.enableCopyright(data, copyright, predefined, copyrightYear, copyrightLabel, copyrightYearLabel, warning, b);
300            }
301        };
302        authorName.addKeyListener(authorNameListener);
303
304        predefined.addActionListener(new ActionListener() {
305            @Override
306            public void actionPerformed(ActionEvent e) {
307                JList<String> l = new JList<>(LICENSES);
308                l.setVisibleRowCount(LICENSES.length);
309                l.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
310                int answer = JOptionPane.showConfirmDialog(
311                        Main.parent,
312                        new JScrollPane(l),
313                        tr("Choose a predefined license"),
314                        JOptionPane.OK_CANCEL_OPTION,
315                        JOptionPane.QUESTION_MESSAGE
316                );
317                if (answer != JOptionPane.OK_OPTION || l.getSelectedIndex() == -1)
318                    return;
319                StringBuilder license = new StringBuilder();
320                for (int i : l.getSelectedIndices()) {
321                    if (i == 2) {
322                        license = new StringBuilder("public domain");
323                        break;
324                    }
325                    if (license.length() > 0) {
326                        license.append(", ");
327                    }
328                    license.append(URLS[i]);
329                }
330                copyright.setText(license.toString());
331                copyright.setCaretPosition(0);
332            }
333        });
334
335        authorActionListener.actionPerformed(null);
336        authorNameListener.keyReleased(null);
337    }
338}