001/** 002 * 003 */ 004package icy.gui.dialog; 005 006import java.awt.Dimension; 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.io.File; 010 011import javax.swing.JFileChooser; 012import javax.swing.filechooser.FileFilter; 013 014import icy.file.FileUtil; 015import icy.file.ImageFileFormat; 016import icy.file.Saver; 017import icy.file.SequenceFileExporter; 018import icy.main.Icy; 019import icy.preferences.ApplicationPreferences; 020import icy.preferences.XMLPreferences; 021import icy.sequence.Sequence; 022import icy.system.thread.ThreadUtil; 023import icy.util.StringUtil; 024import loci.formats.IFormatWriter; 025import loci.formats.gui.ExtensionFileFilter; 026import loci.plugins.out.Exporter; 027 028/** 029 * Saver dialog used to save resource or image from the {@link Exporter} or {@link SequenceFileExporter}. 030 * 031 * @author Stephane 032 * @see Saver 033 */ 034public class SaverDialog extends JFileChooser 035{ 036 private static final String PREF_ID = "frame/imageSaver"; 037 038 private static final String ID_WIDTH = "width"; 039 private static final String ID_HEIGHT = "height"; 040 private static final String ID_PATH = "path"; 041 private static final String ID_MULTIPLEFILE = "multipleFile"; 042 private static final String ID_OVERWRITENAME = "overwriteName"; 043 private static final String ID_FPS = "fps"; 044 private static final String ID_EXTENSION = "extension"; 045 046 // GUI 047 private SaverOptionPanel settingPanel; 048 049 // internal 050 private final XMLPreferences preferences; 051 052 private final boolean singleZ; 053 private final boolean singleT; 054 private final boolean singleImage; 055 056 /** 057 * <b>Saver Dialog</b><br> 058 * <br> 059 * Display a dialog to select the destination file then save the specified sequence.<br> 060 * <br> 061 * To only get selected file from the dialog you must do:<br> 062 * <code> ImageSaverDialog dialog = new ImageSaverDialog(sequence, false);</code><br> 063 * <code> File selectedFile = dialog.getSelectedFile()</code><br> 064 * <br> 065 * To directly save specified sequence to the selected file just use:<br> 066 * <code>new ImageSaverDialog(sequence, true);</code><br> 067 * or<br> 068 * <code>new ImageSaverDialog(sequence);</code> 069 * 070 * @param sequence 071 * The {@link Sequence} we want to save. 072 * @param autoSave 073 * If true the sequence is automatically saved to selected file. 074 */ 075 public SaverDialog(Sequence sequence, boolean autoSave) 076 { 077 super(); 078 079 preferences = ApplicationPreferences.getPreferences().node(PREF_ID); 080 081 singleZ = (sequence.getSizeZ() == 1); 082 singleT = (sequence.getSizeT() == 1); 083 singleImage = singleZ && singleT; 084 085 // can't use WindowsPositionSaver as JFileChooser is a fake JComponent 086 // only dimension is stored 087 setCurrentDirectory(new File(preferences.get(ID_PATH, ""))); 088 setPreferredSize(new Dimension(preferences.getInt(ID_WIDTH, 600), preferences.getInt(ID_HEIGHT, 400))); 089 090 setDialogTitle("Save image file"); 091 092 // remove default filter 093 removeChoosableFileFilter(getAcceptAllFileFilter()); 094 // then add our supported save format 095 addChoosableFileFilter(ImageFileFormat.TIFF.getExtensionFileFilter()); 096 addChoosableFileFilter(ImageFileFormat.PNG.getExtensionFileFilter()); 097 addChoosableFileFilter(ImageFileFormat.JPG.getExtensionFileFilter()); 098 addChoosableFileFilter(ImageFileFormat.AVI.getExtensionFileFilter()); 099 100 // set last used file filter 101 setFileFilter(getFileFilter(preferences.get(ID_EXTENSION, ImageFileFormat.TIFF.getDescription()))); 102 103 setMultiSelectionEnabled(false); 104 // setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 105 // so the filename information is not lost when changing directory 106 setFileSelectionMode(JFileChooser.FILES_ONLY); 107 108 // get filename without extension 109 String filename = FileUtil.getFileName(sequence.getOutputFilename(false), false); 110 // empty filename --> use sequence name as default filename 111 if (StringUtil.isEmpty(filename)) 112 filename = sequence.getName(); 113 if (!StringUtil.isEmpty(filename)) 114 { 115 filename = FileUtil.cleanPath(filename); 116 // test if filename has already a valid extension 117 final String ext = getDialogExtension(filename); 118 // remove file extension 119 if (ext != null) 120 FileUtil.setExtension(filename, ""); 121 // set dialog filename 122 setSelectedFile(new File(filename)); 123 } 124 125 // create extra setting panel 126 settingPanel = new SaverOptionPanel(); 127 settingPanel.setMultipleFiles(preferences.getBoolean(ID_MULTIPLEFILE, false)); 128 settingPanel.setOverwriteMetadata(preferences.getBoolean(ID_OVERWRITENAME, false)); 129 130 // try to set time interval from metadata 131 final double ti = sequence.getTimeInterval() * 1000d; 132 133 if (ti != 0d) 134 settingPanel.setTimeInterval(ti); 135 // otherwise we just use the last used FPS 136 else 137 settingPanel.setFramePerSecond(preferences.getInt(ID_FPS, 20)); 138 139 setAccessory(settingPanel); 140 updateSettingPanel(); 141 142 // listen file filter change 143 addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener() 144 { 145 @Override 146 public void propertyChange(PropertyChangeEvent evt) 147 { 148 updateSettingPanel(); 149 } 150 }); 151 152 ImageFileFormat fileFormat = null; 153 IFormatWriter writer = null; 154 boolean accepted = false; 155 156 while (!accepted) 157 { 158 // display Saver dialog 159 final int value = showSaveDialog(Icy.getMainInterface().getMainFrame()); 160 161 // action canceled --> stop here 162 if (value != JFileChooser.APPROVE_OPTION) 163 break; 164 165 // get selected file format and associated writer 166 fileFormat = getSelectedFileFormat(); 167 writer = Saver.getWriter(fileFormat); 168 169 // selected writer is not compatible ? 170 if (!isCompatible(fileFormat, sequence)) 171 { 172 // incompatible saver for this sequence 173 // new IncompatibleImageFormatDialog(); 174 // return; 175 176 // display a confirm dialog about possible loss in save operation 177 accepted = ConfirmDialog.confirm("Warning", "Some information will be lost in the " + fileFormat 178 + " saved file(s). Do you want to continue ?"); 179 } 180 else 181 accepted = true; 182 } 183 184 // only if accepted... 185 if (accepted) 186 { 187 File file = getSelectedFile(); 188 final String outFilename = file.getAbsolutePath(); 189 190 // destination is a folder ? 191 if (isFolderRequired()) 192 { 193 // remove extension 194 file = new File(FileUtil.setExtension(outFilename, "")); 195 // set it so we can get it from getSelectedFile() 196 setSelectedFile(file); 197 } 198 else 199 { 200 // test and add extension if needed 201 final ExtensionFileFilter extensionFilter = (ExtensionFileFilter) getFileFilter(); 202 203 // add file filter extension to filename if not already present 204 if (!hasExtension(outFilename.toLowerCase(), extensionFilter)) 205 { 206 file = new File(outFilename + "." + extensionFilter.getExtension()); 207 // set it so we can get it from getSelectedFile() 208 setSelectedFile(file); 209 } 210 } 211 212 // save requested ? 213 if (autoSave) 214 { 215 // ask for confirmation as file already exists 216 if (!file.exists() || ConfirmDialog.confirm("Overwrite existing file(s) ?")) 217 { 218 if (file.exists()) 219 FileUtil.delete(file, true); 220 221 // store current path 222 preferences.put(ID_PATH, getCurrentDirectory().getAbsolutePath()); 223 224 // overwrite sequence name with filename 225 if (isOverwriteNameEnabled()) 226 sequence.setName(FileUtil.getFileName(file.getAbsolutePath(), false)); 227 228 final Sequence s = sequence; 229 final File f = file; 230 final IFormatWriter w = writer; 231 232 // do save in background process 233 ThreadUtil.bgRun(new Runnable() 234 { 235 @Override 236 public void run() 237 { 238 Saver.save(w, s, f, getFps(), isSaveAsMultipleFilesEnabled(), true, true); 239 } 240 }); 241 } 242 } 243 244 // store interface option 245 preferences.putInt(ID_WIDTH, getWidth()); 246 preferences.putInt(ID_HEIGHT, getHeight()); 247 // save this information only for TIFF format 248 if (fileFormat == ImageFileFormat.TIFF) 249 preferences.putBoolean(ID_MULTIPLEFILE, isSaveAsMultipleFilesEnabled()); 250 preferences.putBoolean(ID_OVERWRITENAME, settingPanel.getOverwriteMetadata()); 251 // save this information only for AVI format 252 if (fileFormat == ImageFileFormat.AVI) 253 preferences.putInt(ID_FPS, getFps()); 254 preferences.put(ID_EXTENSION, getFileFilter().getDescription()); 255 } 256 } 257 258 /** 259 * <b>Saver Dialog</b><br> 260 * <br> 261 * Display a dialog to select the destination file then save the specified sequence. 262 * 263 * @param sequence 264 * The {@link Sequence} we want to save. 265 */ 266 public SaverDialog(Sequence sequence) 267 { 268 this(sequence, true); 269 } 270 271 /** 272 * @deprecated Use {@link #SaverDialog(Sequence, boolean)} instead 273 */ 274 @Deprecated 275 public SaverDialog(Sequence sequence, int defZ, int defT, boolean autoSave) 276 { 277 this(sequence, autoSave); 278 } 279 280 /** 281 * @deprecated Use {@link #SaverDialog(Sequence)} instead 282 */ 283 @Deprecated 284 public SaverDialog(Sequence sequence, int defZ, int defT) 285 { 286 this(sequence, true); 287 } 288 289 protected FileFilter getFileFilter(String description) 290 { 291 final FileFilter[] filters = getChoosableFileFilters(); 292 293 for (FileFilter filter : filters) 294 if (StringUtil.equals(filter.getDescription(), description)) 295 return filter; 296 297 // default one 298 return ImageFileFormat.TIFF.getExtensionFileFilter(); 299 } 300 301 private static boolean hasExtension(String name, ExtensionFileFilter extensionFilter) 302 { 303 return getExtension(name, extensionFilter) != null; 304 } 305 306 private static String getExtension(String name, ExtensionFileFilter extensionFilter) 307 { 308 for (String ext : extensionFilter.getExtensions()) 309 if (name.endsWith(ext.toLowerCase())) 310 return ext; 311 312 return null; 313 } 314 315 private String getDialogExtension(String name) 316 { 317 for (FileFilter filter : getChoosableFileFilters()) 318 { 319 final String ext = getExtension(name, (ExtensionFileFilter) filter); 320 321 if (ext != null) 322 return ext; 323 } 324 325 return null; 326 } 327 328 public ImageFileFormat getSelectedFileFormat() 329 { 330 final FileFilter ff = getFileFilter(); 331 332 // default 333 if ((ff == null) || !(ff instanceof ExtensionFileFilter)) 334 return ImageFileFormat.TIFF; 335 336 return ImageFileFormat.getWriteFormat(((ExtensionFileFilter) ff).getExtension(), ImageFileFormat.TIFF); 337 } 338 339 /** 340 * Returns <code>true</code> if we require a folder to save the sequence with selected options. 341 */ 342 public boolean isFolderRequired() 343 { 344 return !singleImage && isSaveAsMultipleFilesEnabled(); 345 } 346 347 /** 348 * Returns <code>true</code> if user chosen to save the sequence as multiple image files. 349 */ 350 public boolean isSaveAsMultipleFilesEnabled() 351 { 352 return settingPanel.isMultipleFilesVisible() && settingPanel.getMultipleFiles(); 353 } 354 355 /** 356 * Returns <code>true</code> if user chosen to overwrite the sequence internal name by filename. 357 */ 358 public boolean isOverwriteNameEnabled() 359 { 360 return settingPanel.isOverwriteMetadataVisible() && settingPanel.getOverwriteMetadata(); 361 } 362 363 /** 364 * Returns the desired FPS (Frame Per Second, only for AVI file). 365 */ 366 public int getFps() 367 { 368 if (settingPanel.isFramePerSecondVisible()) 369 return settingPanel.getFramePerSecond(); 370 371 return 1; 372 } 373 374 /** 375 * Returns the desired time interval between 2 frames in ms (only for AVI file). 376 */ 377 public double getTimeInterval() 378 { 379 if (settingPanel.isFramePerSecondVisible()) 380 return settingPanel.getTimeInterval(); 381 382 return 100d; 383 } 384 385 /** 386 * @deprecated 387 */ 388 @Deprecated 389 @SuppressWarnings("static-method") 390 public int getZMin() 391 { 392 return 0; 393 } 394 395 /** 396 * @deprecated 397 */ 398 @Deprecated 399 @SuppressWarnings("static-method") 400 public int getZMax() 401 { 402 return 0; 403 } 404 405 /** 406 * @deprecated 407 */ 408 @Deprecated 409 @SuppressWarnings("static-method") 410 public int getTMin() 411 { 412 return 0; 413 } 414 415 /** 416 * @deprecated 417 */ 418 @Deprecated 419 @SuppressWarnings("static-method") 420 public int getTMax() 421 { 422 return 0; 423 } 424 425 void updateSettingPanel() 426 { 427 final ImageFileFormat fileFormat = getSelectedFileFormat(); 428 429 // single image, no need to display selection option 430 if (singleImage) 431 { 432 settingPanel.setMultipleFilesVisible(false); 433 settingPanel.setForcedMultipleFilesOff(); 434 } 435 else 436 { 437 switch (fileFormat) 438 { 439 case AVI: 440 settingPanel.setMultipleFilesVisible(true); 441 settingPanel.setForcedMultipleFilesOff(); 442 break; 443 444 case JPG: 445 case PNG: 446 settingPanel.setMultipleFilesVisible(true); 447 settingPanel.setForcedMultipleFilesOn(); 448 break; 449 450 case TIFF: 451 settingPanel.setMultipleFilesVisible(true); 452 settingPanel.removeForcedMultipleFiles(); 453 settingPanel.setMultipleFiles(preferences.getBoolean(ID_MULTIPLEFILE, false)); 454 break; 455 } 456 } 457 458 settingPanel.setFramePerSecondVisible(fileFormat == ImageFileFormat.AVI); 459 } 460 461 private boolean isCompatible(ImageFileFormat fileFormat, Sequence sequence) 462 { 463 if (fileFormat == ImageFileFormat.AVI) 464 { 465 // with AVI we force single file saving so we can't save 3D image. 466 if (!singleZ) 467 return false; 468 } 469 470 // just need to test against colormodel now 471 return Saver.isCompatible(fileFormat, sequence.getColorModel()); 472 } 473}