All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.filesystems.FileChooserBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.filesystems;

import java.awt.Component;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.HeadlessException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import org.netbeans.modules.openide.filesystems.FileFilterSupport;
import org.openide.filesystem.spi.FileChooserBuilderProvider;
import org.openide.util.*;

/**
 * Utility class for working with JFileChoosers.  In particular, remembering
 * the last-used directory for a given file is made transparent.  You pass an
 * ad-hoc string key to the constructor (the fully qualified name of the
 * calling class is good for uniqueness, and there is a constructor that takes
 * a Class object as an argument for this purpose).  That key is
 * used to look up the most recently-used directory from any previous invocations
 * with the same key.  This makes it easy to have your user interface
 * “remember” where the user keeps particular types of files, and
 * saves the user from having to navigate through the same set of directories
 * every time they need to locate a file from a particular place.
 * 

* FileChooserBuilder's methods each return this, so * it is possible to chain invocations to simplify setting up a file chooser. * Example usage: *

 *      //The default dir to use if no value is stored
 *      File home = new File (System.getProperty("user.home") + File.separator + "lib");
 *      //Now build a file chooser and invoke the dialog in one line of code
 *      //"libraries-dir" is our unique key
 *      File toAdd = new FileChooserBuilder ("libraries-dir").setTitle("Add Library").
 *              setDefaultWorkingDirectory(home).setApproveText("Add").showOpenDialog();
 *      //Result will be null if the user clicked cancel or closed the dialog w/o OK
 *      if (toAdd != null) {
 *          //do something
 *      }
 *
*

* Instances of this class are intended to be thrown away after use. Typically * you create a builder, set it to create file choosers as you wish, then * use it to show a dialog or create a file chooser you then do something * with. *

* Supports the most common subset of JFileChooser functionality; if you * need to do something exotic with a file chooser, you are probably better * off creating your own. *

* Note: If you use the constructor that takes a Class object, * please use new FileChooserBuilder(MyClass.class), not * new FileChooserBuilder(getClass()). This avoids unexpected * behavior in the case of subclassing. * * @author Tim Boudreau */ public class FileChooserBuilder { private boolean dirsOnly; private BadgeProvider badger; private String title; private String approveText; //Just in case... private static boolean PREVENT_SYMLINK_TRAVERSAL = !Boolean.getBoolean("allow.filechooser.symlink.traversal"); //NOI18N private final String dirKey; private File failoverDir; private FileFilter filter; private boolean fileHiding; private boolean controlButtonsShown = true; private String aDescription; private boolean filesOnly; private static final boolean DONT_STORE_DIRECTORIES = Boolean.getBoolean("forget.recent.dirs"); private SelectionApprover approver; private final List filters = new ArrayList(3); private boolean useAcceptAllFileFilter = true; /** * Creates a new FileChooserBuilder which can interact with the given file system. * @param fileSystem A virtual file system * @return FileChooserBuilder related to the given file system * @since 9.11 */ public static FileChooserBuilder create(FileSystem fileSystem) { Collection providers = Lookup.getDefault().lookupAll(FileChooserBuilderProvider.class); for (FileChooserBuilderProvider provider : providers) { FileChooserBuilder builder = provider.createFileChooserBuilder(fileSystem); if (builder != null) { return builder; } } return new FileChooserBuilder(fileSystem.getDisplayName()); } /** * Create a new FileChooserBuilder using the name of the passed class * as the metadata for looking up a starting directory from previous * application sessions or invocations. * @param type A non-null class object, typically the calling class */ public FileChooserBuilder(Class type) { this(type.getName()); } /** * Create a new FileChooserBuilder. The passed key is used as a key * into NbPreferences to look up the directory the file chooser should * initially be rooted on. * * @param dirKey A non-null ad-hoc string. If a FileChooser was previously * used with the same string as is passed, then the initial directory */ public FileChooserBuilder(String dirKey) { Parameters.notNull("dirKey", dirKey); this.dirKey = dirKey; } /** * Set whether or not any file choosers created by this builder will show * only directories. * @param val true if files should not be shown * @return this */ public FileChooserBuilder setDirectoriesOnly(boolean val) { dirsOnly = val; assert !filesOnly : "FilesOnly and DirsOnly are mutually exclusive"; return this; } public FileChooserBuilder setFilesOnly(boolean val) { filesOnly = val; assert !dirsOnly : "FilesOnly and DirsOnly are mutually exclusive"; return this; } /** * Provide an implementation of BadgeProvider which will "badge" the * icons of some files. * * @param provider A badge provider which will alter the icon of files * or folders that may be of particular interest to the user * @return this */ public FileChooserBuilder setBadgeProvider(BadgeProvider provider) { this.badger = provider; return this; } /** * Set the dialog title for any JFileChoosers created by this builder. * @param val A localized, human-readable title * @return this */ public FileChooserBuilder setTitle(String val) { title = val; return this; } /** * Set the text on the OK button for any file chooser dialogs produced * by this builder. * @param val A short, localized, human-readable string * @return this */ public FileChooserBuilder setApproveText(String val) { approveText = val; return this; } /** * Set a file filter which filters the list of selectable files. * @param filter * @return this */ public FileChooserBuilder setFileFilter (FileFilter filter) { this.filter = filter; return this; } /** * Determines whether the AcceptAll FileFilter is used * as an available choice in the choosable filter list. * If false, the AcceptAll file filter is removed from * the list of available file filters. * If true, the AcceptAll file filter will become the * the actively used file filter. * @param accept whether the AcceptAll FileFilter is used * @return this * @since 8.3 */ public FileChooserBuilder setAcceptAllFileFilterUsed(boolean accept) { useAcceptAllFileFilter = accept; return this; } /** * Set the current directory which should be used only if * a last-used directory cannot be found for the key string passed * into this builder's constructor. * @param dir A directory to root any created file choosers on if * there is no stored path for this builder's key * @return this */ public FileChooserBuilder setDefaultWorkingDirectory (File dir) { failoverDir = dir; return this; } /** * Enable file hiding in any created file choosers * @param fileHiding Whether or not to hide files. Default is no. * @return this */ public FileChooserBuilder setFileHiding(boolean fileHiding) { this.fileHiding = fileHiding; return this; } /** * Show/hide control buttons * @param val Whether or not to hide files. Default is no. * @return this */ public FileChooserBuilder setControlButtonsAreShown(boolean val) { this.controlButtonsShown = val; return this; } /** * Set the accessible description for any file choosers created by this * builder * @param aDescription The description * @return this */ public FileChooserBuilder setAccessibleDescription(String aDescription) { this.aDescription = aDescription; return this; } /** * Create a JFileChooser that conforms to the parameters set in this * builder. * @return A file chooser */ public JFileChooser createFileChooser() { JFileChooser result = new SavedDirFileChooser(dirKey, failoverDir, force, approver); prepareFileChooser(result); return result; } private boolean force = false; /** * Force use of the failover directory - i.e. ignore the directory key * passed in. * @param val * @return this */ public FileChooserBuilder forceUseOfDefaultWorkingDirectory(boolean val) { this.force = val; return this; } /** * Show an open dialog that allows multiple selection. * @return An array of files, or null if the user cancelled the dialog */ public File[] showMultiOpenDialog() { JFileChooser chooser = createFileChooser(); chooser.setMultiSelectionEnabled(true); int result = chooser.showOpenDialog(Utilities.findDialogParent()); if (JFileChooser.APPROVE_OPTION == result) { File[] files = chooser.getSelectedFiles(); return files == null ? new File[0] : files; } else { return null; } } /** * Show an open dialog with a file chooser set up according to the * parameters of this builder. * @return A file if the user clicks the accept button and a file or * folder was selected at the time the user clicked cancel. */ public File showOpenDialog() { JFileChooser chooser = createFileChooser(); if( Boolean.getBoolean("nb.native.filechooser") ) { //NOI18N FileDialog fileDialog = createFileDialog( chooser.getCurrentDirectory() ); if( null != fileDialog ) { return showFileDialog(fileDialog, FileDialog.LOAD ); } } chooser.setMultiSelectionEnabled(false); int dlgResult = chooser.showOpenDialog(Utilities.findDialogParent()); if (JFileChooser.APPROVE_OPTION == dlgResult) { File result = chooser.getSelectedFile(); if (result != null && !result.exists()) { result = null; } return result; } else { return null; } } /** * Show a save dialog with the file chooser set up according to the * parameters of this builder. * @return A file if the user clicks the accept button and a file or * folder was selected at the time the user clicked cancel. */ public File showSaveDialog() { JFileChooser chooser = createFileChooser(); if( Boolean.getBoolean("nb.native.filechooser") ) { //NOI18N FileDialog fileDialog = createFileDialog( chooser.getCurrentDirectory() ); if( null != fileDialog ) { return showFileDialog( fileDialog, FileDialog.SAVE ); } } int result = chooser.showSaveDialog(Utilities.findDialogParent()); if (JFileChooser.APPROVE_OPTION == result) { return chooser.getSelectedFile(); } else { return null; } } private File showFileDialog( FileDialog fileDialog, int mode ) { String oldFileDialogProp = System.getProperty("apple.awt.fileDialogForDirectories"); //NOI18N if( dirsOnly ) { System.setProperty("apple.awt.fileDialogForDirectories", "true"); //NOI18N } fileDialog.setMode( mode ); fileDialog.setVisible(true); if( dirsOnly ) { if( null != oldFileDialogProp ) { System.setProperty("apple.awt.fileDialogForDirectories", oldFileDialogProp); //NOI18N } else { System.clearProperty("apple.awt.fileDialogForDirectories"); //NOI18N } } if( fileDialog.getDirectory() != null && fileDialog.getFile() != null ) { String selFile = fileDialog.getFile(); File dir = new File( fileDialog.getDirectory() ); return new File( dir, selFile ); } return null; } private void prepareFileChooser(JFileChooser chooser) { chooser.setFileSelectionMode(dirsOnly ? JFileChooser.DIRECTORIES_ONLY : filesOnly ? JFileChooser.FILES_ONLY : JFileChooser.FILES_AND_DIRECTORIES); chooser.setFileHidingEnabled(fileHiding); chooser.setControlButtonsAreShown(controlButtonsShown); chooser.setAcceptAllFileFilterUsed(useAcceptAllFileFilter); if (title != null) { chooser.setDialogTitle(title); } if (approveText != null) { chooser.setApproveButtonText(approveText); } if (badger != null) { chooser.setFileView(new CustomFileView(new BadgeIconProvider(badger), chooser.getFileSystemView())); } if (PREVENT_SYMLINK_TRAVERSAL) { FileUtil.preventFileChooserSymlinkTraversal(chooser, chooser.getCurrentDirectory()); } if (filter != null) { chooser.setFileFilter(filter); } if (aDescription != null) { chooser.getAccessibleContext().setAccessibleDescription(aDescription); } if (!filters.isEmpty()) { for (FileFilter f : filters) { chooser.addChoosableFileFilter(f); } } } private FileDialog createFileDialog( File currentDirectory ) { if( badger != null ) return null; if( !Boolean.getBoolean("nb.native.filechooser") ) return null; if( dirsOnly && !BaseUtilities.isMac() ) return null; Component parentComponent = Utilities.findDialogParent(); Frame parentFrame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, parentComponent); FileDialog fileDialog = new FileDialog(parentFrame); if (title != null) { fileDialog.setTitle(title); } if( null != currentDirectory ) fileDialog.setDirectory(currentDirectory.getAbsolutePath()); return fileDialog; } /** * Equivalent to calling JFileChooser.addChoosableFileFilter(filter). * Adds another file filter that can be displayed in the file filters combo * box in the file chooser. * * @param filter The file filter to add * @return this * @since 7.26.0 */ public FileChooserBuilder addFileFilter (FileFilter filter) { filters.add (filter); return this; } /** * Add all default file filters to the file chooser. * * @see MIMEResolver.Registration#showInFileChooser() * @see MIMEResolver.ExtensionRegistration#showInFileChooser() * @return this * @since 8.1 */ public FileChooserBuilder addDefaultFileFilters() { filters.addAll(FileFilterSupport.findRegisteredFileFilters()); return this; } /** * Set a selection approver which can display an "Overwrite file?" * or similar dialog if necessary, when the user presses the accept button * in the file chooser dialog. * * @param approver A SelectionApprover which will determine if the selection * is valid * @return this * @since 7.26.0 */ public FileChooserBuilder setSelectionApprover (SelectionApprover approver) { this.approver = approver; return this; } /** * Object which can approve the selection (enabling the OK button or * equivalent) in a JFileChooser. Equivalent to overriding * JFileChooser.approveSelection() * @since 7.26.0 */ public interface SelectionApprover { /** * Approve the selection, enabling the dialog to be closed. Called by * the JFileChooser's approveSelection() method. Use this * interface if you want to, for example, show a dialog asking * "Overwrite File X?" or similar. * * @param selection The selected file(s) at the time the user presses * the Open, Save or OK button * @return true if the selection is accepted, false if it is not and * the dialog should not be closed */ public boolean approve (File[] selection); } private static final class SavedDirFileChooser extends JFileChooser { private final String dirKey; private final SelectionApprover approver; SavedDirFileChooser(String dirKey, File failoverDir, boolean force, SelectionApprover approver) { this.dirKey = dirKey; this.approver = approver; if (force && failoverDir != null && failoverDir.exists() && failoverDir.isDirectory()) { setCurrentDirectory(failoverDir); } else { String path = DONT_STORE_DIRECTORIES ? null : NbPreferences.forModule(FileChooserBuilder.class).get(dirKey, null); if (path != null) { File f = new File(path); if (f.exists() && f.isDirectory()) { setCurrentDirectory(f); } else if (failoverDir != null) { setCurrentDirectory(failoverDir); } } else if (failoverDir != null) { setCurrentDirectory(failoverDir); } } } @Override public void approveSelection() { if (approver != null) { File[] selected = getSelectedFiles(); final File sf = getSelectedFile(); if ((selected == null || selected.length == 0) && sf != null) { selected = new File[] { sf }; } boolean approved = approver.approve(selected); if (approved) { super.approveSelection(); } } else { super.approveSelection(); } } @Override public int showDialog(Component parent, String approveButtonText) throws HeadlessException { int result = super.showDialog(parent, approveButtonText); if (result == APPROVE_OPTION) { saveCurrentDir(); } return result; } private void saveCurrentDir() { File dir = super.getCurrentDirectory(); if (!DONT_STORE_DIRECTORIES && dir != null && dir.exists() && dir.isDirectory()) { NbPreferences.forModule(FileChooserBuilder.class).put(dirKey, dir.getPath()); } } } //Can open this API later if there is a use-case interface IconProvider { public Icon getIcon(File file, Icon orig); } /** * Provides "badges" for icons that indicate files or folders of particular * interest to the user. * @see FileChooserBuilder#setBadgeProvider */ public interface BadgeProvider { /** * Get the badge the passed file should use. Note: this method * is called for every visible file. The negative test (deciding * not to badge a file) should be very, very fast and immediately * return null. * @param file The file in question * @return an icon or null if no change to the appearance of the file * is needed */ public Icon getBadge(File file); /** * Get the x offset for badges produced by this provider. This is * the location of the badge icon relative to the real icon for the * file. * @return a rightward pixel offset */ public int getXOffset(); /** * Get the y offset for badges produced by this provider. This is * the location of the badge icon relative to the real icon for the * file. * @return a downward pixel offset */ public int getYOffset(); } private static final class BadgeIconProvider implements IconProvider { private final BadgeProvider badger; public BadgeIconProvider(BadgeProvider badger) { this.badger = badger; } public Icon getIcon(File file, Icon orig) { Icon badge = badger.getBadge(file); if (badge != null && orig != null) { return new MergedIcon(orig, badge, badger.getXOffset(), badger.getYOffset()); } return orig; } } private static final class CustomFileView extends FileView { private final IconProvider provider; private final FileSystemView view; CustomFileView(IconProvider provider, FileSystemView view) { this.provider = provider; this.view = view; } @Override public Icon getIcon(File f) { Icon result = view.getSystemIcon(f); result = provider.getIcon(f, result); return result; } } private static class MergedIcon implements Icon { private Icon icon1; private Icon icon2; private int xMerge; private int yMerge; MergedIcon(Icon icon1, Icon icon2, int xMerge, int yMerge) { assert icon1 != null; assert icon2 != null; this.icon1 = icon1; this.icon2 = icon2; if (xMerge == -1) { xMerge = icon1.getIconWidth() - icon2.getIconWidth(); } if (yMerge == -1) { yMerge = icon1.getIconHeight() - icon2.getIconHeight(); } this.xMerge = xMerge; this.yMerge = yMerge; } public int getIconHeight() { return Math.max(icon1.getIconHeight(), yMerge + icon2.getIconHeight()); } public int getIconWidth() { return Math.max(icon1.getIconWidth(), yMerge + icon2.getIconWidth()); } public void paintIcon(java.awt.Component c, java.awt.Graphics g, int x, int y) { icon1.paintIcon(c, g, x, y); icon2.paintIcon(c, g, x + xMerge, y + yMerge); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy