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'> </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'> </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}