/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.swing.dialog;
import java.awt.Component;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.EnumSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import org.geotools.util.logging.Logging;
/**
* A file chooser dialog for common raster image format files. It provides
* static methods to display the dialog for opening or saving an image file.
* The file formats offered by the dialog are a subset of those supported by
* {@code ImageIO} on the host system.
*
* <pre><code>
* // Prompting for an input image file
* File file = JFileImageChooser.showOpenFile();
* if (file != null) {
* ...
* }
*
* // Prompting for a file name to save an image
* File file = JFileImageChooser.showSaveFile();
* if (file != null) {
* ...
* }
* </code></pre>
*
* @author Michael Bedward
* @since 2.6.1
* @source $URL$
* @version $Id$
*/
public class JFileImageChooser extends JFileChooser {
private static final Logger LOGGER = Logging.getLogger("org.geotools.swing");
/**
* Constants for (possibly) supported image file formats.
*/
private static enum FormatSpecifier {
BMP("bmp", "BMP image", ".bmp"),
GIF("gif", "GIF image", ".gif"),
JPG("jpg", "JPEG image", ".jpg", ".jpeg"),
PNG("png", "PNG image", "png"),
TIF("tif", "TIFF image", ".tif", ".tiff");
private String id;
private String desc;
private String[] suffixes;
private FormatSpecifier(String id, String desc, String ...suffixes) {
this.id = id;
this.desc = desc;
this.suffixes = new String[suffixes.length];
System.arraycopy(suffixes, 0, this.suffixes, 0, suffixes.length);
}
};
private static final EnumSet<FormatSpecifier> supportedReaders =
EnumSet.noneOf(FormatSpecifier.class);
private static final EnumSet<FormatSpecifier> supportedWriters =
EnumSet.noneOf(FormatSpecifier.class);
static {
for (FormatSpecifier format : FormatSpecifier.values()) {
if (ImageIO.getImageReadersBySuffix(format.id).hasNext()) {
supportedReaders.add(format);
}
if (ImageIO.getImageWritersBySuffix(format.id).hasNext()) {
supportedWriters.add(format);
}
}
}
/**
* A file filter which works with the {@code FormatSpecifier} constants.
* It is package-private, rather than private, for unit tests purposes.
*/
static class FormatFilter extends FileFilter {
private FormatSpecifier format;
public FormatFilter(FormatSpecifier format) {
this.format = format;
}
@Override
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
for (String suffix : format.suffixes) {
if (f.getPath().endsWith(suffix) ||
f.getPath().endsWith(suffix.toUpperCase())) {
return true;
}
}
return false;
}
@Override
public String getDescription() {
return format.desc;
}
public String getDefaultSuffix() {
return format.suffixes[0];
}
}
/**
* Prompts for file name to save an image.
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showSaveFile() {
return showSaveFile(null);
}
/**
* Prompts for file name to save an image.
*
* @param parent parent component (may be {@code null})
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showSaveFile(Component parent) {
return showSaveFile(parent, null);
}
/**
* Prompts for file name to save an image.
*
* @param parent parent component (may be {@code null})
* @param workingDir the initial directory
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showSaveFile(final Component parent, final File workingDir) {
final File[] file = new File[1];
if (SwingUtilities.isEventDispatchThread()) {
file[0] = doShow(parent, workingDir, SAVE_DIALOG);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
file[0] = doShow(parent, workingDir, SAVE_DIALOG);
}
});
} catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Thread interrupted while prompting for file", ex);
} catch (InvocationTargetException ex) {
LOGGER.log(Level.SEVERE, "Unexpected problem while prompting for file", ex);
}
}
return file[0];
}
/**
* Prompts for file name to read an image.
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showOpenFile() {
return showOpenFile(null, null);
}
/**
* Prompts for file name to read an image.
*
* @param parent parent component (may be {@code null})
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showOpenFile(Component parent) {
return showOpenFile(parent, null);
}
/**
* Prompts for file name to read an image.
*
* @param parent parent component (may be {@code null})
* @param workingDir the initial directory
*
* @return the selected file or {@code null} if the dialog was cancelled
*/
public static File showOpenFile(final Component parent, final File workingDir) {
final File[] file = new File[1];
if (SwingUtilities.isEventDispatchThread()) {
file[0] = doShow(parent, workingDir, OPEN_DIALOG);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
file[0] = doShow(parent, workingDir, OPEN_DIALOG);
}
});
} catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Thread interrupted while prompting for file", ex);
} catch (InvocationTargetException ex) {
LOGGER.log(Level.SEVERE, "Unexpected problem while prompting for file", ex);
}
}
return file[0];
}
/**
* Helper method which creates and displays the chooser dialog on the event dispatch thread.
*
* @param parent optional parent component
* @param workingDir optional initial working directory
* @param openOrSave either {@linkplain #OPEN_DIALOG} or {@linkplain #SAVE_DIALOG}
*
* @return selected file or {@code null} if the dialog was cancelled
*/
private static File doShow(Component parent, File workingDir, int openOrSave) {
JFileImageChooser chooser = new JFileImageChooser(workingDir);
File file = null;
int dialogRtnValue;
switch (openOrSave) {
case OPEN_DIALOG:
chooser.setFilter(supportedReaders);
chooser.setDialogTitle("Open image");
dialogRtnValue = chooser.showOpenDialog(parent);
break;
case SAVE_DIALOG:
chooser.setFilter(supportedWriters);
chooser.setDialogTitle("Save image");
dialogRtnValue = chooser.showSaveDialog(parent);
break;
default:
// just in case
throw new IllegalArgumentException(
"Invalid value for openOrSave argument" + openOrSave);
}
if (dialogRtnValue == JFileImageChooser.APPROVE_OPTION) {
file = chooser.getSelectedFile();
String name = file.getAbsolutePath();
int dot = name.lastIndexOf('.');
FormatFilter filter = (FormatFilter) chooser.getFileFilter();
if (dot < 0) {
name = name + filter.getDefaultSuffix();
file = new File(name);
}
}
return file;
}
/**
* Private constructor.
*
* @param workingDir the initial directory
*/
private JFileImageChooser(File workingDir) {
super(workingDir);
}
/**
* Validates the selected file name.
*/
@Override
public void approveSelection() {
FormatFilter filter = (FormatFilter) getFileFilter();
File file = getSelectedFile();
String name = file.getAbsolutePath();
int dot = name.lastIndexOf('.');
boolean ok = true;
if (dot < 0) {
/*
* No file extension. Set the default one
*/
name = name + filter.getDefaultSuffix();
file = new File(name);
setSelectedFile(file);
}
if (this.getDialogType() == JFileImageChooser.SAVE_DIALOG) {
if (!filter.accept(getSelectedFile())) {
StringBuilder sb = new StringBuilder();
sb.append("'");
sb.append(name.substring(dot + 1));
sb.append("' ");
sb.append("is not a standard suffix for a ");
sb.append(filter.getDescription());
sb.append(".");
sb.append("\nDo you want to save with this name ?");
int answer = JOptionPane.showConfirmDialog(
getParent(), sb.toString(), "Confirm file name",
JOptionPane.YES_NO_OPTION);
ok = answer == JOptionPane.YES_OPTION;
}
if (ok && file.exists()) {
int answer = JOptionPane.showConfirmDialog(
this,
"Overwrite the existing file ?",
"File exists",
JOptionPane.YES_NO_OPTION);
ok = answer == JOptionPane.YES_OPTION;
}
} else {
if (!file.exists()) {
JOptionPane.showMessageDialog(
this, "Can't file this file", "File not found", JOptionPane.WARNING_MESSAGE);
ok = false;
}
}
if (ok) {
super.approveSelection();
}
}
/**
* Helper method called by {@linkplain #doShow(java.awt.Component, java.io.File, int)
* to set file filters.
*
* @param supportedFormats formats to base filters on
*/
private void setFilter(Set<FormatSpecifier> supportedFormats) {
this.setAcceptAllFileFilterUsed(false);
for (final FormatSpecifier format : supportedFormats) {
addChoosableFileFilter(new FormatFilter(format));
}
}
}