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.MainApplication; 038import org.openstreetmap.josm.gui.MapFrame; 039import org.openstreetmap.josm.gui.MapFrameListener; 040import org.openstreetmap.josm.gui.layer.Layer; 041import org.openstreetmap.josm.gui.util.WindowGeometry; 042import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 043import org.openstreetmap.josm.io.session.SessionLayerExporter; 044import org.openstreetmap.josm.io.session.SessionWriter; 045import org.openstreetmap.josm.tools.GBC; 046import org.openstreetmap.josm.tools.Logging; 047import org.openstreetmap.josm.tools.MultiMap; 048import org.openstreetmap.josm.tools.UserCancelException; 049import org.openstreetmap.josm.tools.Utils; 050 051/** 052 * Saves a JOSM session 053 * @since 4685 054 */ 055public class SessionSaveAsAction extends DiskAccessAction implements MapFrameListener { 056 057 private transient List<Layer> layers; 058 private transient Map<Layer, SessionLayerExporter> exporters; 059 private transient MultiMap<Layer, Layer> dependencies; 060 061 /** 062 * Constructs a new {@code SessionSaveAsAction}. 063 */ 064 public SessionSaveAsAction() { 065 this(true, true); 066 } 067 068 /** 069 * Constructs a new {@code SessionSaveAsAction}. 070 * @param toolbar Register this action for the toolbar preferences? 071 * @param installAdapters False, if you don't want to install layer changed and selection changed adapters 072 */ 073 protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) { 074 super(tr("Save Session As..."), "session", tr("Save the current session to a new file."), 075 null, toolbar, "save_as-session", installAdapters); 076 putValue("help", ht("/Action/SessionSaveAs")); 077 MainApplication.addMapFrameListener(this); 078 } 079 080 @Override 081 public void actionPerformed(ActionEvent e) { 082 try { 083 saveSession(); 084 } catch (UserCancelException ignore) { 085 Logging.trace(ignore); 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(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 || 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 = 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 Logging.error(ex); 172 HelpAwareOptionPane.showMessageDialogInEDT( 173 Main.parent, 174 tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", 175 file.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())), 176 tr("IO Error"), 177 JOptionPane.ERROR_MESSAGE, 178 null 179 ); 180 } 181 } 182 183 /** 184 * The "Save Session" dialog 185 */ 186 public class SessionSaveAsDialog extends ExtendedDialog { 187 188 /** 189 * Constructs a new {@code SessionSaveAsDialog}. 190 */ 191 public SessionSaveAsDialog() { 192 super(Main.parent, tr("Save Session"), tr("Save As"), tr("Cancel")); 193 initialize(); 194 setButtonIcons("save_as", "cancel"); 195 setDefaultButton(1); 196 setRememberWindowGeometry(getClass().getName() + ".geometry", 197 WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 450))); 198 setContent(build(), false); 199 } 200 201 /** 202 * Initializes action. 203 */ 204 public final void initialize() { 205 layers = new ArrayList<>(getLayerManager().getLayers()); 206 exporters = new HashMap<>(); 207 dependencies = new MultiMap<>(); 208 209 Set<Layer> noExporter = new HashSet<>(); 210 211 for (Layer layer : layers) { 212 SessionLayerExporter exporter = SessionWriter.getSessionLayerExporter(layer); 213 if (exporter != null) { 214 exporters.put(layer, exporter); 215 Collection<Layer> deps = exporter.getDependencies(); 216 if (deps != null) { 217 dependencies.putAll(layer, deps); 218 } else { 219 dependencies.putVoid(layer); 220 } 221 } else { 222 noExporter.add(layer); 223 exporters.put(layer, null); 224 } 225 } 226 227 int numNoExporter = 0; 228 WHILE: while (numNoExporter != noExporter.size()) { 229 numNoExporter = noExporter.size(); 230 for (Layer layer : layers) { 231 if (noExporter.contains(layer)) continue; 232 for (Layer depLayer : dependencies.get(layer)) { 233 if (noExporter.contains(depLayer)) { 234 noExporter.add(layer); 235 exporters.put(layer, null); 236 break WHILE; 237 } 238 } 239 } 240 } 241 } 242 243 protected final Component build() { 244 JPanel ip = new JPanel(new GridBagLayout()); 245 for (Layer layer : layers) { 246 JPanel wrapper = new JPanel(new GridBagLayout()); 247 wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 248 Component exportPanel; 249 SessionLayerExporter exporter = exporters.get(layer); 250 if (exporter == null) { 251 if (!exporters.containsKey(layer)) throw new AssertionError(); 252 exportPanel = getDisabledExportPanel(layer); 253 } else { 254 exportPanel = exporter.getExportPanel(); 255 } 256 wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL)); 257 ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2)); 258 } 259 ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL)); 260 JScrollPane sp = new JScrollPane(ip); 261 sp.setBorder(BorderFactory.createEmptyBorder()); 262 JPanel p = new JPanel(new GridBagLayout()); 263 p.add(sp, GBC.eol().fill()); 264 final JTabbedPane tabs = new JTabbedPane(); 265 tabs.addTab(tr("Layers"), p); 266 return tabs; 267 } 268 269 protected final Component getDisabledExportPanel(Layer layer) { 270 JPanel p = new JPanel(new GridBagLayout()); 271 JCheckBox include = new JCheckBox(); 272 include.setEnabled(false); 273 JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT); 274 lbl.setToolTipText(tr("No exporter for this layer")); 275 lbl.setLabelFor(include); 276 lbl.setEnabled(false); 277 p.add(include, GBC.std()); 278 p.add(lbl, GBC.std()); 279 p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); 280 return p; 281 } 282 } 283 284 @Override 285 protected void updateEnabledState() { 286 setEnabled(MainApplication.isDisplayingMapView()); 287 } 288 289 @Override 290 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { 291 updateEnabledState(); 292 } 293}