package net.filebot; import static java.util.Arrays.*; import static java.util.Collections.*; import static java.util.stream.Collectors.*; import static net.filebot.Logging.*; import static net.filebot.Settings.*; import static net.filebot.similarity.Normalization.*; import static net.filebot.util.ui.SwingUI.*; import java.awt.Desktop; import java.awt.Dialog; import java.awt.FileDialog; import java.awt.Frame; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.logging.Level; import javax.swing.JFileChooser; import com.apple.eio.FileManager; import com.sun.jna.platform.FileUtils; import net.filebot.platform.mac.MacAppUtilities; import net.filebot.util.FileUtilities; import net.filebot.util.FileUtilities.ExtensionFileFilter; public class UserFiles { public static void trash(File file) throws IOException { // use system trash if possible try { if (isMacApp()) { // use com.apple.eio package on OS X platform if (FileManager.moveToTrash(file)) { return; } } else if (FileUtils.getInstance().hasTrash()) { // use com.sun.jna.platform package on Windows and Linux FileUtils.getInstance().moveToTrash(new File[] { file }); return; } } catch (Exception e) { debug.log(Level.WARNING, e::toString); } // delete permanently if necessary if (file.exists()) { FileUtilities.delete(file); } } public static void revealFiles(Collection<File> files) { if (isMacApp()) { files.stream().collect(groupingBy(File::getParentFile, LinkedHashMap::new, toList())).forEach((parent, children) -> { try { FileManager.revealInFinder(children.get(children.size() - 1)); } catch (Exception e) { debug.log(Level.WARNING, e::toString); } }); return; } // if we can't reveal the file in folder, just reveal the parent folder files.stream().map(it -> it.getParentFile()).distinct().forEach(it -> { try { Desktop.getDesktop().open(it); } catch (Exception e) { debug.log(Level.WARNING, e::toString); } }); } private static FileChooser defaultFileChooser = getPreferredFileChooser(); public static void setDefaultFileChooser(FileChooser fileChooser) { defaultFileChooser = fileChooser; } public static List<File> showLoadDialogSelectFiles(boolean folderMode, boolean multiSelection, File defaultFile, ExtensionFileFilter filter, String title, ActionEvent evt) { String defaultFileKey = ((folderMode && filter == null) || !(folderMode && filter != null && isShiftOrAltDown(evt))) ? KEY_OPEN_FOLDER : KEY_OPEN_FILE; List<File> files = defaultFileChooser.showLoadDialogSelectFiles(defaultFileKey == KEY_OPEN_FOLDER, multiSelection, getFileChooserDefaultFile(defaultFileKey, defaultFile), filter, title, evt); if (files.size() > 0) { setFileChooserDefaultFile(defaultFileKey, files.get(0)); } return files; } public static File showSaveDialogSelectFile(boolean folderMode, File defaultFile, String title, ActionEvent evt) { File file = defaultFileChooser.showSaveDialogSelectFile(folderMode, getFileChooserDefaultFile(KEY_SAVE, defaultFile), title, evt); if (file != null) { setFileChooserDefaultFile(KEY_SAVE, file); } return file; } public static File showOpenDialogSelectFolder(File defaultFile, String title, ActionEvent evt) { List<File> folder = defaultFileChooser.showLoadDialogSelectFiles(true, false, defaultFile, null, title, evt); return folder.size() > 0 ? folder.get(0) : null; } private static final String PREF_KEY_PREFIX = "dialog."; private static final String KEY_OPEN_FILE = "open.file"; private static final String KEY_OPEN_FOLDER = "open.folder"; private static final String KEY_SAVE = "save.file"; protected static File getFileChooserDefaultFile(String key, File userValue) { if (userValue != null && userValue.getParentFile() != null) return userValue; String path = Settings.forPackage(UserFiles.class).get(PREF_KEY_PREFIX + key); if (path == null || path.isEmpty()) return userValue; if (userValue != null && userValue.getParentFile() == null) return new File(new File(path).getParentFile(), userValue.getName()); return new File(path); } protected static void setFileChooserDefaultFile(String name, File file) { Settings.forPackage(UserFiles.class).put(PREF_KEY_PREFIX + name, file.getPath()); } public enum FileChooser { Swing { @Override public List<File> showLoadDialogSelectFiles(boolean folderMode, boolean multiSelection, File defaultFile, ExtensionFileFilter filter, String title, ActionEvent evt) { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle(title); chooser.setMultiSelectionEnabled(multiSelection); chooser.setFileSelectionMode(folderMode && filter == null ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES); if (defaultFile != null) { if (defaultFile.isFile()) { chooser.setSelectedFile(defaultFile); } else if (defaultFile.getParentFile() != null && defaultFile.getParentFile().isDirectory()) { chooser.setCurrentDirectory(defaultFile.getParentFile()); } } if (filter != null && !filter.acceptAny()) { chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter(truncateText(filter.toString(), 80), filter.extensions())); } if (chooser.showOpenDialog(getWindow(evt.getSource())) == JFileChooser.APPROVE_OPTION) { if (chooser.getSelectedFiles().length > 0) return asList(chooser.getSelectedFiles()); if (chooser.getSelectedFile() != null) return asList(chooser.getSelectedFile()); } return asList(new File[0]); } @Override public File showSaveDialogSelectFile(boolean folderMode, File defaultFile, String title, ActionEvent evt) { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle(title); chooser.setMultiSelectionEnabled(false); chooser.setFileSelectionMode(folderMode ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES); chooser.setSelectedFile(defaultFile); if (chooser.showSaveDialog(getWindow(evt.getSource())) != JFileChooser.APPROVE_OPTION) { return null; } return chooser.getSelectedFile(); } }, AWT { @Override public List<File> showLoadDialogSelectFiles(boolean folderMode, boolean multiSelection, File defaultFile, ExtensionFileFilter filter, String title, ActionEvent evt) { FileDialog fileDialog = createFileDialog(evt, title, FileDialog.LOAD, folderMode); fileDialog.setTitle(title); fileDialog.setMultipleMode(multiSelection); if (defaultFile != null) { if (folderMode && defaultFile.isDirectory()) { fileDialog.setDirectory(defaultFile.getPath()); } else if (defaultFile.getParentFile() != null && defaultFile.getParentFile().isDirectory()) { fileDialog.setDirectory(defaultFile.getParentFile().getPath()); fileDialog.setFile(defaultFile.getName()); } } if (filter != null) { fileDialog.setFilenameFilter(filter); } fileDialog.setVisible(true); return asList(fileDialog.getFiles()); } @Override public File showSaveDialogSelectFile(boolean folderMode, File defaultFile, String title, ActionEvent evt) { FileDialog fileDialog = createFileDialog(evt, title, FileDialog.SAVE, folderMode); fileDialog.setTitle(title); fileDialog.setMultipleMode(false); if (defaultFile != null) { if (defaultFile.getParentFile() != null && defaultFile.getParentFile().isDirectory()) { fileDialog.setDirectory(defaultFile.getParentFile().getPath()); } fileDialog.setFile(defaultFile.getName()); } fileDialog.setVisible(true); File[] files = fileDialog.getFiles(); return files.length > 0 ? files[0] : null; } public FileDialog createFileDialog(ActionEvent evt, String title, int mode, boolean fileDialogForDirectories) { // By default, the AWT File Dialog lets you choose a file. Under certain circumstances, however, it may be proper for you to choose a directory instead. If that is the case, set this property to allow for directory selection in a file dialog. System.setProperty("apple.awt.fileDialogForDirectories", String.valueOf(fileDialogForDirectories)); if (evt.getSource() instanceof Frame) { return new FileDialog((Frame) evt.getSource(), title, mode); } if (evt.getSource() instanceof Dialog) { return new FileDialog((Dialog) evt.getSource(), title, mode); } Frame[] frames = Frame.getFrames(); return new FileDialog(frames.length > 0 ? frames[0] : null, title, mode); } }, COCOA { @Override public List<File> showLoadDialogSelectFiles(boolean folderMode, boolean multiSelection, File defaultFile, ExtensionFileFilter filter, String title, ActionEvent evt) { // directly use NSOpenPanel for via Objective-C bridge for FILES_AND_DIRECTORIES mode if (folderMode && filter != null) { // call native NSOpenPanel openPanel via Objective-C bridge return MacAppUtilities.NSOpenPanel_openPanel_runModal(title, true, true, true, filter.acceptAny() ? null : filter.extensions()); } // default to AWT implementation return AWT.showLoadDialogSelectFiles(folderMode, multiSelection, defaultFile, filter, title, evt); } @Override public File showSaveDialogSelectFile(boolean folderMode, File defaultFile, String title, ActionEvent evt) { // default to AWT implementation return AWT.showSaveDialogSelectFile(folderMode, defaultFile, title, evt); } }, JavaFX { @Override public List<File> showLoadDialogSelectFiles(final boolean folderMode, final boolean multiSelection, final File defaultFile, final ExtensionFileFilter filter, final String title, final ActionEvent evt) { return runAndWait(new Callable<List<File>>() { @Override public List<File> call() throws Exception { // show DirectoryChooser if (folderMode) { javafx.stage.DirectoryChooser directoryChooser = new javafx.stage.DirectoryChooser(); directoryChooser.setTitle(title); if (defaultFile != null && defaultFile.isDirectory()) { directoryChooser.setInitialDirectory(defaultFile); } File file = directoryChooser.showDialog(null); if (file != null) return singletonList(file); else return emptyList(); } // show FileChooser javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); fileChooser.setTitle(title); if (filter != null && !filter.acceptAny()) { String[] globFilter = filter.extensions(); for (int i = 0; i < globFilter.length; i++) { globFilter[i] = "*." + globFilter[i]; } fileChooser.getExtensionFilters().add(new javafx.stage.FileChooser.ExtensionFilter(filter.toString(), globFilter)); } if (defaultFile != null) { if (defaultFile.getParentFile() != null && defaultFile.getParentFile().isDirectory()) { fileChooser.setInitialDirectory(defaultFile.getParentFile()); fileChooser.setInitialFileName(defaultFile.getName()); } } if (multiSelection) { List<File> files = fileChooser.showOpenMultipleDialog(null); if (files != null) return files; } else { File file = fileChooser.showOpenDialog(null); if (file != null) return singletonList(file); } return emptyList(); } }); } @Override public File showSaveDialogSelectFile(final boolean folderMode, final File defaultFile, final String title, final ActionEvent evt) { return runAndWait(new Callable<File>() { @Override public File call() throws Exception { javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); fileChooser.setTitle(title); if (defaultFile != null) { if (defaultFile.getParentFile() != null && defaultFile.getParentFile().isDirectory()) { fileChooser.setInitialDirectory(defaultFile.getParentFile()); } fileChooser.setInitialFileName(defaultFile.getName()); } return fileChooser.showSaveDialog(null); } }); } public <T> T runAndWait(Callable<T> c) { try { // run on FX Thread FutureTask<T> task = new FutureTask<T>(c); invokeJavaFX(task); return task.get(); } catch (Exception e) { throw new RuntimeException(e); } } }; public abstract List<File> showLoadDialogSelectFiles(boolean folderMode, boolean multiSelection, File defaultFile, ExtensionFileFilter filter, String title, ActionEvent evt); public abstract File showSaveDialogSelectFile(boolean folderMode, File defaultFile, String title, ActionEvent evt); } }