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