001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.session; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.CardLayout; 007import java.awt.Font; 008import java.awt.GridBagLayout; 009import java.awt.Insets; 010import java.awt.event.ActionEvent; 011import java.awt.event.ActionListener; 012import java.awt.event.ItemEvent; 013import java.awt.event.ItemListener; 014import java.io.File; 015import java.io.IOException; 016import java.io.OutputStream; 017import java.io.OutputStreamWriter; 018import java.io.PrintWriter; 019import java.io.Writer; 020import java.net.MalformedURLException; 021import java.net.URI; 022import java.net.URL; 023import java.nio.charset.StandardCharsets; 024import java.util.Collection; 025import java.util.Collections; 026 027import javax.swing.AbstractAction; 028import javax.swing.ButtonGroup; 029import javax.swing.JButton; 030import javax.swing.JCheckBox; 031import javax.swing.JLabel; 032import javax.swing.JPanel; 033import javax.swing.JRadioButton; 034import javax.swing.SwingConstants; 035 036import org.openstreetmap.josm.actions.SaveAction; 037import org.openstreetmap.josm.gui.layer.Layer; 038import org.openstreetmap.josm.gui.layer.OsmDataLayer; 039import org.openstreetmap.josm.gui.util.GuiHelper; 040import org.openstreetmap.josm.gui.widgets.JosmTextField; 041import org.openstreetmap.josm.io.OsmWriter; 042import org.openstreetmap.josm.io.OsmWriterFactory; 043import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport; 044import org.openstreetmap.josm.tools.GBC; 045import org.openstreetmap.josm.tools.ImageProvider; 046import org.w3c.dom.Element; 047 048public class OsmDataSessionExporter implements SessionLayerExporter { 049 050 private final OsmDataLayer layer; 051 private JRadioButton link; 052 private JRadioButton include; 053 private JCheckBox export; 054 055 /** 056 * Constructs a new {@code OsmDataSessionExporter}. 057 * @param layer Data layer to export 058 */ 059 public OsmDataSessionExporter(OsmDataLayer layer) { 060 this.layer = layer; 061 } 062 063 @Override 064 public Collection<Layer> getDependencies() { 065 return Collections.emptySet(); 066 } 067 068 private class LayerSaveAction extends AbstractAction { 069 /** 070 * Constructs a new {@code LayerSaveAction}. 071 */ 072 LayerSaveAction() { 073 putValue(SMALL_ICON, new ImageProvider("save").setWidth(16).get()); 074 putValue(SHORT_DESCRIPTION, layer.requiresSaveToFile() ? 075 tr("Layer contains unsaved data - save to file.") : 076 tr("Layer does not contain unsaved data.")); 077 updateEnabledState(); 078 } 079 080 @Override 081 public void actionPerformed(ActionEvent e) { 082 SaveAction.getInstance().doSave(layer); 083 updateEnabledState(); 084 } 085 086 public final void updateEnabledState() { 087 setEnabled(layer.requiresSaveToFile()); 088 } 089 } 090 091 @Override 092 public JPanel getExportPanel() { 093 final JPanel p = new JPanel(new GridBagLayout()); 094 JPanel topRow = new JPanel(new GridBagLayout()); 095 export = new JCheckBox(); 096 export.setSelected(true); 097 final JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT); 098 lbl.setToolTipText(layer.getToolTipText()); 099 lbl.setLabelFor(export); 100 JLabel lblData = new JLabel(tr("Data:")); 101 /* I18n: Refer to a OSM data file in session file */ link = new JRadioButton(tr("local file")); 102 link.putClientProperty("actionname", "link"); 103 link.setToolTipText(tr("Link to a OSM data file on your local disk.")); 104 /* I18n: Include OSM data in session file */ include = new JRadioButton(tr("include")); 105 include.setToolTipText(tr("Include OSM data in the .joz session file.")); 106 include.putClientProperty("actionname", "include"); 107 ButtonGroup group = new ButtonGroup(); 108 group.add(link); 109 group.add(include); 110 111 JPanel cardLink = new JPanel(new GridBagLayout()); 112 final File file = layer.getAssociatedFile(); 113 final LayerSaveAction saveAction = new LayerSaveAction(); 114 final JButton save = new JButton(saveAction); 115 if (file != null && file.exists()) { 116 JosmTextField tf = new JosmTextField(); 117 tf.setText(file.getPath()); 118 tf.setEditable(false); 119 cardLink.add(tf, GBC.std()); 120 save.setMargin(new Insets(0, 0, 0, 0)); 121 cardLink.add(save, GBC.eol().insets(2, 0, 0, 0)); 122 } else { 123 cardLink.add(new JLabel(tr("No file association")), GBC.eol()); 124 } 125 126 JPanel cardInclude = new JPanel(new GridBagLayout()); 127 JLabel lblIncl = new JLabel(tr("OSM data will be included in the session file.")); 128 lblIncl.setFont(lblIncl.getFont().deriveFont(Font.PLAIN)); 129 cardInclude.add(lblIncl, GBC.eol().fill(GBC.HORIZONTAL)); 130 131 final CardLayout cl = new CardLayout(); 132 final JPanel cards = new JPanel(cl); 133 cards.add(cardLink, "link"); 134 cards.add(cardInclude, "include"); 135 136 if (file != null && file.exists()) { 137 link.setSelected(true); 138 } else { 139 link.setEnabled(false); 140 link.setToolTipText(tr("No file association")); 141 include.setSelected(true); 142 cl.show(cards, "include"); 143 } 144 145 link.addActionListener(new ActionListener() { 146 @Override 147 public void actionPerformed(ActionEvent e) { 148 cl.show(cards, "link"); 149 } 150 }); 151 include.addActionListener(new ActionListener() { 152 @Override 153 public void actionPerformed(ActionEvent e) { 154 cl.show(cards, "include"); 155 } 156 }); 157 158 topRow.add(export, GBC.std()); 159 topRow.add(lbl, GBC.std()); 160 topRow.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); 161 p.add(topRow, GBC.eol().fill(GBC.HORIZONTAL)); 162 p.add(lblData, GBC.std().insets(10, 0, 0, 0)); 163 p.add(link, GBC.std()); 164 p.add(include, GBC.eol()); 165 p.add(cards, GBC.eol().insets(15, 0, 3, 3)); 166 167 export.addItemListener(new ItemListener() { 168 @Override 169 public void itemStateChanged(ItemEvent e) { 170 if (e.getStateChange() == ItemEvent.DESELECTED) { 171 GuiHelper.setEnabledRec(p, false); 172 export.setEnabled(true); 173 } else { 174 GuiHelper.setEnabledRec(p, true); 175 save.setEnabled(saveAction.isEnabled()); 176 link.setEnabled(file != null && file.exists()); 177 } 178 } 179 }); 180 return p; 181 } 182 183 @Override 184 public boolean shallExport() { 185 return export.isSelected(); 186 } 187 188 @Override 189 public boolean requiresZip() { 190 return include.isSelected(); 191 } 192 193 @Override 194 public Element export(ExportSupport support) throws IOException { 195 Element layerEl = support.createElement("layer"); 196 layerEl.setAttribute("type", "osm-data"); 197 layerEl.setAttribute("version", "0.1"); 198 199 Element file = support.createElement("file"); 200 layerEl.appendChild(file); 201 202 if (requiresZip()) { 203 String zipPath = "layers/" + String.format("%02d", support.getLayerIndex()) + "/data.osm"; 204 file.appendChild(support.createTextNode(zipPath)); 205 addDataFile(support.getOutputStreamZip(zipPath)); 206 } else { 207 URI uri = layer.getAssociatedFile().toURI(); 208 URL url = null; 209 try { 210 url = uri.toURL(); 211 } catch (MalformedURLException e) { 212 throw new IOException(e); 213 } 214 file.appendChild(support.createTextNode(url.toString())); 215 } 216 return layerEl; 217 } 218 219 protected void addDataFile(OutputStream out) { 220 Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); 221 OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion()); 222 layer.data.getReadLock().lock(); 223 try { 224 w.writeLayer(layer); 225 w.flush(); 226 } finally { 227 layer.data.getReadLock().unlock(); 228 } 229 } 230}