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