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