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