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}