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