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