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