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