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