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