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