001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.GridBagLayout; 010import java.awt.event.ActionEvent; 011import java.io.File; 012import java.io.IOException; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.Collection; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.List; 019import java.util.Map; 020import java.util.Set; 021 022import javax.swing.BorderFactory; 023import javax.swing.JCheckBox; 024import javax.swing.JFileChooser; 025import javax.swing.JLabel; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.JScrollPane; 029import javax.swing.JTabbedPane; 030import javax.swing.SwingConstants; 031import javax.swing.border.EtchedBorder; 032import javax.swing.filechooser.FileFilter; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.gui.ExtendedDialog; 036import org.openstreetmap.josm.gui.HelpAwareOptionPane; 037import org.openstreetmap.josm.gui.layer.Layer; 038import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 039import org.openstreetmap.josm.io.session.SessionLayerExporter; 040import org.openstreetmap.josm.io.session.SessionWriter; 041import org.openstreetmap.josm.tools.GBC; 042import org.openstreetmap.josm.tools.MultiMap; 043import org.openstreetmap.josm.tools.UserCancelException; 044import org.openstreetmap.josm.tools.Utils; 045import org.openstreetmap.josm.tools.WindowGeometry; 046 047/** 048 * Saves a JOSM session 049 * @since 4685 050 */ 051public class SessionSaveAsAction extends DiskAccessAction { 052 053 private transient List<Layer> layers; 054 private transient Map<Layer, SessionLayerExporter> exporters; 055 private transient MultiMap<Layer, Layer> dependencies; 056 057 /** 058 * Constructs a new {@code SessionSaveAsAction}. 059 */ 060 public SessionSaveAsAction() { 061 super(tr("Save Session As..."), "session", tr("Save the current session to a new file."), null, true, "save_as-session", true); 062 putValue("help", ht("/Action/SessionSaveAs")); 063 } 064 065 @Override 066 public void actionPerformed(ActionEvent e) { 067 try { 068 saveSession(); 069 } catch (UserCancelException ignore) { 070 if (Main.isTraceEnabled()) { 071 Main.trace(ignore.getMessage()); 072 } 073 } 074 } 075 076 /** 077 * Attempts to save the session. 078 * @throws UserCancelException when the user has cancelled the save process. 079 * @since 8913 080 */ 081 public void saveSession() throws UserCancelException { 082 if (!isEnabled()) { 083 return; 084 } 085 086 SessionSaveAsDialog dlg = new SessionSaveAsDialog(); 087 dlg.showDialog(); 088 if (dlg.getValue() != 1) { 089 throw new UserCancelException(); 090 } 091 092 boolean zipRequired = false; 093 for (Layer l : layers) { 094 SessionLayerExporter ex = exporters.get(l); 095 if (ex != null && ex.requiresZip()) { 096 zipRequired = true; 097 break; 098 } 099 } 100 101 FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)")); 102 FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)")); 103 104 AbstractFileChooser fc; 105 106 if (zipRequired) { 107 fc = createAndOpenFileChooser(false, false, tr("Save session"), joz, JFileChooser.FILES_ONLY, "lastDirectory"); 108 } else { 109 fc = createAndOpenFileChooser(false, false, tr("Save session"), Arrays.asList(new FileFilter[]{jos, joz}), jos, 110 JFileChooser.FILES_ONLY, "lastDirectory"); 111 } 112 113 if (fc == null) { 114 throw new UserCancelException(); 115 } 116 117 File file = fc.getSelectedFile(); 118 String fn = file.getName(); 119 120 boolean zip; 121 FileFilter ff = fc.getFileFilter(); 122 if (zipRequired) { 123 zip = true; 124 } else if (joz.equals(ff)) { 125 zip = true; 126 } else if (jos.equals(ff)) { 127 zip = false; 128 } else { 129 if (Utils.hasExtension(fn, "joz")) { 130 zip = true; 131 } else { 132 zip = false; 133 } 134 } 135 if (fn.indexOf('.') == -1) { 136 file = new File(file.getPath() + (zip ? ".joz" : ".jos")); 137 if (!SaveActionBase.confirmOverwrite(file)) { 138 throw new UserCancelException(); 139 } 140 } 141 142 List<Layer> layersOut = new ArrayList<>(); 143 for (Layer layer : layers) { 144 if (exporters.get(layer) == null || !exporters.get(layer).shallExport()) continue; 145 // TODO: resolve dependencies for layers excluded by the user 146 layersOut.add(layer); 147 } 148 149 int active = -1; 150 Layer activeLayer = Main.map.mapView.getActiveLayer(); 151 if (activeLayer != null) { 152 active = layersOut.indexOf(activeLayer); 153 } 154 155 SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip); 156 try { 157 sw.write(file); 158 SaveActionBase.addToFileOpenHistory(file); 159 } catch (IOException ex) { 160 Main.error(ex); 161 HelpAwareOptionPane.showMessageDialogInEDT( 162 Main.parent, 163 tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), ex.getMessage()), 164 tr("IO Error"), 165 JOptionPane.ERROR_MESSAGE, 166 null 167 ); 168 } 169 } 170 171 /** 172 * The "Save Session" dialog 173 */ 174 public class SessionSaveAsDialog extends ExtendedDialog { 175 176 /** 177 * Constructs a new {@code SessionSaveAsDialog}. 178 */ 179 public SessionSaveAsDialog() { 180 super(Main.parent, tr("Save Session"), new String[] {tr("Save As"), tr("Cancel")}); 181 initialize(); 182 setButtonIcons(new String[] {"save_as", "cancel"}); 183 setDefaultButton(1); 184 setRememberWindowGeometry(getClass().getName() + ".geometry", 185 WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 450))); 186 setContent(build(), false); 187 } 188 189 /** 190 * Initializes action. 191 */ 192 public final void initialize() { 193 layers = new ArrayList<>(Main.map.mapView.getAllLayersAsList()); 194 exporters = new HashMap<>(); 195 dependencies = new MultiMap<>(); 196 197 Set<Layer> noExporter = new HashSet<>(); 198 199 for (Layer layer : layers) { 200 SessionLayerExporter exporter = SessionWriter.getSessionLayerExporter(layer); 201 if (exporter != null) { 202 exporters.put(layer, exporter); 203 Collection<Layer> deps = exporter.getDependencies(); 204 if (deps != null) { 205 dependencies.putAll(layer, deps); 206 } else { 207 dependencies.putVoid(layer); 208 } 209 } else { 210 noExporter.add(layer); 211 exporters.put(layer, null); 212 } 213 } 214 215 int numNoExporter = 0; 216 WHILE: while (numNoExporter != noExporter.size()) { 217 numNoExporter = noExporter.size(); 218 for (Layer layer : layers) { 219 if (noExporter.contains(layer)) continue; 220 for (Layer depLayer : dependencies.get(layer)) { 221 if (noExporter.contains(depLayer)) { 222 noExporter.add(layer); 223 exporters.put(layer, null); 224 break WHILE; 225 } 226 } 227 } 228 } 229 } 230 231 protected final Component build() { 232 JPanel p = new JPanel(new GridBagLayout()); 233 JPanel ip = new JPanel(new GridBagLayout()); 234 for (Layer layer : layers) { 235 JPanel wrapper = new JPanel(new GridBagLayout()); 236 wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 237 Component exportPanel; 238 SessionLayerExporter exporter = exporters.get(layer); 239 if (exporter == null) { 240 if (!exporters.containsKey(layer)) throw new AssertionError(); 241 exportPanel = getDisabledExportPanel(layer); 242 } else { 243 exportPanel = exporter.getExportPanel(); 244 } 245 wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL)); 246 ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2)); 247 } 248 ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL)); 249 JScrollPane sp = new JScrollPane(ip); 250 sp.setBorder(BorderFactory.createEmptyBorder()); 251 p.add(sp, GBC.eol().fill()); 252 final JTabbedPane tabs = new JTabbedPane(); 253 tabs.addTab(tr("Layers"), p); 254 return tabs; 255 } 256 257 protected final Component getDisabledExportPanel(Layer layer) { 258 JPanel p = new JPanel(new GridBagLayout()); 259 JCheckBox include = new JCheckBox(); 260 include.setEnabled(false); 261 JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT); 262 lbl.setToolTipText(tr("No exporter for this layer")); 263 lbl.setLabelFor(include); 264 lbl.setEnabled(false); 265 p.add(include, GBC.std()); 266 p.add(lbl, GBC.std()); 267 p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); 268 return p; 269 } 270 } 271 272 @Override 273 protected void updateEnabledState() { 274 setEnabled(Main.isDisplayingMapView()); 275 } 276}