blob: e843052d03fd6a64ca7057bbc12191711727ee21 [file] [log] [blame]
/*
* 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.apache.pivot.wtk;
import java.io.File;
import java.net.URI;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.VFS;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.collections.immutable.ImmutableList;
import org.apache.pivot.io.FileObjectList;
import org.apache.pivot.util.Filter;
import org.apache.pivot.util.ListenerList;
/**
* A file browser that uses the Apache Commons VFS (Virtual File System) to be
* able to browse local and remote file systems, and browse inside of .zip,
* .tar, etc. archives as well.
*/
public class VFSBrowser extends Container {
/**
* Commons VFS browser skin interface.
*/
public interface Skin extends org.apache.pivot.wtk.Skin {
public FileObject getFileAt(int x, int y);
public void addActionComponent(Component component);
}
private static final URI USER_HOME = new File(System.getProperty("user.home")).toURI();
private static class FileBrowserListenerList extends WTKListenerList<VFSBrowserListener>
implements VFSBrowserListener {
@Override
public void managerChanged(VFSBrowser fileBrowser, FileSystemManager previousManager) {
for (VFSBrowserListener listener : this) {
listener.managerChanged(fileBrowser, previousManager);
}
}
@Override
public void rootDirectoryChanged(VFSBrowser fileBrowser, FileObject previousRootDirectory) {
for (VFSBrowserListener listener : this) {
listener.rootDirectoryChanged(fileBrowser, previousRootDirectory);
}
}
@Override
public void homeDirectoryChanged(VFSBrowser fileBrowser, FileObject previousHomeDirectory) {
for (VFSBrowserListener listener : this) {
listener.homeDirectoryChanged(fileBrowser, previousHomeDirectory);
}
}
@Override
public void selectedFileAdded(VFSBrowser fileBrowser, FileObject file) {
for (VFSBrowserListener listener : this) {
listener.selectedFileAdded(fileBrowser, file);
}
}
@Override
public void selectedFileRemoved(VFSBrowser fileBrowser, FileObject file) {
for (VFSBrowserListener listener : this) {
listener.selectedFileRemoved(fileBrowser, file);
}
}
@Override
public void selectedFilesChanged(VFSBrowser fileBrowser,
Sequence<FileObject> previousSelectedFiles) {
for (VFSBrowserListener listener : this) {
listener.selectedFilesChanged(fileBrowser, previousSelectedFiles);
}
}
@Override
public void multiSelectChanged(VFSBrowser fileBrowser) {
for (VFSBrowserListener listener : this) {
listener.multiSelectChanged(fileBrowser);
}
}
@Override
public void disabledFileFilterChanged(VFSBrowser fileBrowser,
Filter<FileObject> previousDisabledFileFilter) {
for (VFSBrowserListener listener : this) {
listener.disabledFileFilterChanged(fileBrowser, previousDisabledFileFilter);
}
}
}
private FileSystemManager manager;
private FileName baseFileName;
private FileObject rootDirectory;
private FileObject homeDirectory;
private FileObjectList selectedFiles = new FileObjectList();
private boolean multiSelect = false;
private Filter<FileObject> disabledFileFilter = null;
private FileBrowserListenerList fileBrowserListeners = new FileBrowserListenerList();
/**
* Creates a new VFSBrowser <p> Note that this version sets, by default,
* the mode to open.
*
* @throws FileSystemException if there are problems.
*/
public VFSBrowser() throws FileSystemException {
this(null, USER_HOME, null);
}
/**
* Creates a new VFSBrowser <p> Note that this version of the constructor
* must be used when a custom root folder (that may include a completely
* different URI scheme) has to be set.
*
* @param manager The virtual file system we're going to manage.
* @param rootFolder The root folder full name.
* @param homeFolder The default home folder full name.
* @throws FileSystemException if there are problems.
*/
public VFSBrowser(FileSystemManager manager, URI rootFolder, URI homeFolder) throws FileSystemException {
this(manager,
rootFolder == null ? null : rootFolder.toString(),
homeFolder == null ? null : homeFolder.toString());
}
/**
* Creates a new VFSBrowser <p> Note that this version of the constructor
* must be used when a custom root folder has to be set.
*
* @param manager The virtual file system we're going to manage.
* @param rootFolder The root folder full name.
* @param homeFolder The home folder full name.
* @throws FileSystemException if there are problems.
*/
public VFSBrowser(FileSystemManager manager, String rootFolder, String homeFolder) throws FileSystemException {
if (rootFolder == null) {
throw new IllegalArgumentException("Root folder is null.");
}
// Note: these methods all could trigger events, but since we're
// in the constructor and the skin isn't set yet, there will not
// be any listeners registered yet
setManager(manager);
setRootDirectory(rootFolder);
setHomeDirectory(homeFolder == null ? USER_HOME.toString() : homeFolder);
installSkin(VFSBrowser.class);
}
/**
* @return The current file system manager.
*/
public FileSystemManager getManager() {
return manager;
}
public void setManager(FileSystemManager manager) throws FileSystemException {
FileSystemManager previousManager = this.manager;
if (manager == null) {
this.manager = VFS.getManager();
} else {
this.manager = manager;
}
FileObject baseFile = this.manager.getBaseFile();
if (baseFile != null) {
baseFileName = baseFile.getName();
}
if (previousManager != null && previousManager != this.manager) {
fileBrowserListeners.managerChanged(this, previousManager);
}
}
/**
* @return The current root directory.
*/
public FileObject getRootDirectory() {
return rootDirectory;
}
/**
* Sets the root directory from a string. Clears any existing file
* selection.
*
* @param rootDirectory The new root directory string for this browser.
* @throws FileSystemException if there are any problems.
*/
public void setRootDirectory(String rootDirectory) throws FileSystemException {
setRootDirectory(manager.resolveFile(rootDirectory));
}
/**
* Sets the root directory. Clears any existing file selection.
*
* @param rootDirectory The new root directory for this browser.
* @throws FileSystemException if there are any problems.
*/
public void setRootDirectory(FileObject rootDirectory) throws FileSystemException {
if (rootDirectory == null) {
throw new IllegalArgumentException("Root directory is null.");
}
// Give some grace to set the root folder to an actual file and
// have it work (by using the parent folder instead)
if (rootDirectory.getType() != FileType.FOLDER) {
rootDirectory = rootDirectory.getParent();
if (rootDirectory == null || rootDirectory.getType() != FileType.FOLDER) {
throw new IllegalArgumentException("Root file is not a directory.");
}
}
if (rootDirectory.exists()) {
FileObject previousRootDirectory = this.rootDirectory;
if (!rootDirectory.equals(previousRootDirectory)) {
this.rootDirectory = rootDirectory;
selectedFiles.clear();
fileBrowserListeners.rootDirectoryChanged(this, previousRootDirectory);
}
} else {
setRootDirectory(rootDirectory.getParent());
}
}
/**
* @return The current home directory.
*/
public FileObject getHomeDirectory() {
return homeDirectory;
}
/**
* Sets the home directory from a string.
*
* @param homeDirectory The new home directory string for this browser.
* @throws FileSystemException if there are any problems.
*/
public void setHomeDirectory(String homeDirectory) throws FileSystemException {
setHomeDirectory(manager.resolveFile(homeDirectory));
}
/**
* Sets the home directory.
*
* @param homeDirectory The new home directory for this browser.
* @throws FileSystemException if there are any problems.
*/
public void setHomeDirectory(FileObject homeDirectory) throws FileSystemException {
if (homeDirectory == null) {
throw new IllegalArgumentException("Home directory is null.");
}
// Give some grace to set the home folder to an actual file and
// have it work (by using the parent folder instead)
if (homeDirectory.getType() != FileType.FOLDER) {
homeDirectory = homeDirectory.getParent();
if (homeDirectory == null || homeDirectory.getType() != FileType.FOLDER) {
throw new IllegalArgumentException("Home file is not a directory.");
}
}
if (homeDirectory.exists()) {
FileObject previousHomeDirectory = this.homeDirectory;
if (!homeDirectory.equals(previousHomeDirectory)) {
this.homeDirectory = homeDirectory;
fileBrowserListeners.homeDirectoryChanged(this, previousHomeDirectory);
}
} else {
setHomeDirectory(homeDirectory.getParent());
}
}
/**
* Adds a file to the file selection.
*
* @param file The new file to be selected.
* @return <tt>true</tt> if the file was added; <tt>false</tt> if it was
* already selected.
* @throws FileSystemException if there are any problems.
*/
public boolean addSelectedFile(FileObject file) throws FileSystemException {
if (file == null) {
throw new IllegalArgumentException();
}
// TODO: is this a good way to do this?
// if (file.isAbsolute()) {
if (baseFileName != null && baseFileName.isAncestor(file.getName())) {
if (!file.getParent().equals(rootDirectory)) {
throw new IllegalArgumentException();
}
} else {
file = manager.resolveFile(rootDirectory, file.getName().getBaseName());
}
int index = selectedFiles.add(file);
if (index != -1) {
fileBrowserListeners.selectedFileAdded(this, file);
}
return (index != -1);
}
/**
* Removes a file from the file selection.
*
* @param file The file to be unselected.
* @return <tt>true</tt> if the file was removed; <tt>false</tt> if it was
* not already selected.
*/
public boolean removeSelectedFile(FileObject file) {
if (file == null) {
throw new IllegalArgumentException();
}
int index = selectedFiles.remove(file);
if (index != -1) {
fileBrowserListeners.selectedFileRemoved(this, file);
}
return (index != -1);
}
/**
* When in single-select mode, returns the currently selected file.
*
* @return The currently selected file.
*/
public FileObject getSelectedFile() {
if (multiSelect) {
throw new IllegalStateException("File browser is not in single-select mode.");
}
return (selectedFiles.getLength() == 0) ? null : selectedFiles.get(0);
}
/**
* Sets the selection to a single file.
*
* @param file The new single file selection (or {@code null} to clear the selection).
* @throws FileSystemException if there are any problems.
*/
public void setSelectedFile(FileObject file) throws FileSystemException {
if (file == null) {
clearSelection();
} else {
// TODO: adequate replacement for "isAbsolute"?
// if (file.isAbsolute()) {
if (baseFileName != null && baseFileName.isAncestor(file.getName())) {
setRootDirectory(file.getParent());
}
setSelectedFiles(new ArrayList<>(file));
}
}
/**
* Returns the currently selected files.
*
* @return An immutable list containing the currently selected files. Note
* that the returned list is a wrapper around the actual selection, not a
* copy. Any changes made to the selection state will be reflected in the
* list, but events will not be fired.
*/
public ImmutableList<FileObject> getSelectedFiles() {
return new ImmutableList<>(selectedFiles);
}
/**
* Sets the selected files.
*
* @param selectedFiles The files to select.
* @return The files that were selected, with duplicates eliminated.
* @throws FileSystemException if there are any problems.
*/
public Sequence<FileObject> setSelectedFiles(Sequence<FileObject> selectedFiles)
throws FileSystemException {
if (selectedFiles == null) {
throw new IllegalArgumentException("selectedFiles is null.");
}
if (!multiSelect && selectedFiles.getLength() > 1) {
throw new IllegalArgumentException("Multi-select is not enabled.");
}
// Update the selection
Sequence<FileObject> previousSelectedFiles = getSelectedFiles();
FileObjectList fileList = new FileObjectList();
for (int i = 0, n = selectedFiles.getLength(); i < n; i++) {
FileObject file = selectedFiles.get(i);
if (file == null) {
throw new IllegalArgumentException("file is null.");
}
// TODO: is this correct?
// if (!file.isAbsolute()) {
if (baseFileName == null || !baseFileName.isAncestor(file.getName())) {
file = manager.resolveFile(rootDirectory, file.getName().getBaseName());
}
// TODO: don't do this for now -- revisit later
// if (!file.getParent().equals(rootDirectory)) {
// throw new IllegalArgumentException();
// }
fileList.add(file);
}
this.selectedFiles = fileList;
// Notify listeners
fileBrowserListeners.selectedFilesChanged(this, previousSelectedFiles);
return getSelectedFiles();
}
/**
* Clears the selection.
*
* @throws FileSystemException if there are any problems.
*/
public void clearSelection() throws FileSystemException {
setSelectedFiles(new ArrayList<FileObject>());
}
public boolean isFileSelected(FileObject file) {
return (selectedFiles.indexOf(file) != -1);
}
/**
* @return The file browser's multi-select state.
*/
public boolean isMultiSelect() {
return multiSelect;
}
/**
* Sets the file browser's multi-select state.
*
* @param multiSelect <tt>true</tt> if multi-select is enabled;
* <tt>false</tt>, otherwise.
*/
public void setMultiSelect(boolean multiSelect) {
if (this.multiSelect != multiSelect) {
// Clear any existing selection
selectedFiles.clear();
this.multiSelect = multiSelect;
fileBrowserListeners.multiSelectChanged(this);
}
}
/**
* Returns the current file filter.
*
* @return The current file filter, or <tt>null</tt> if no filter is set.
*/
public Filter<FileObject> getDisabledFileFilter() {
return disabledFileFilter;
}
/**
* Sets the file filter.
*
* @param disabledFileFilter The file filter to use, or <tt>null</tt> for no
* filter.
*/
public void setDisabledFileFilter(Filter<FileObject> disabledFileFilter) {
Filter<FileObject> previousDisabledFileFilter = this.disabledFileFilter;
if (previousDisabledFileFilter != disabledFileFilter) {
this.disabledFileFilter = disabledFileFilter;
fileBrowserListeners.disabledFileFilterChanged(this, previousDisabledFileFilter);
}
}
public FileObject getFileAt(int x, int y) {
VFSBrowser.Skin fileBrowserSkin = (VFSBrowser.Skin) getSkin();
return fileBrowserSkin.getFileAt(x, y);
}
public void addActionComponent(Component component) {
VFSBrowser.Skin fileBrowserSkin = (VFSBrowser.Skin) getSkin();
fileBrowserSkin.addActionComponent(component);
}
public ListenerList<VFSBrowserListener> getFileBrowserListeners() {
return fileBrowserListeners;
}
}