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