blob: 857fed6ec4c9192a5690e3d7424d1045be400cad [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.skin.terra;
import java.awt.Color;
import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import org.apache.commons.vfs2.FileFilter;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSelectInfo;
import org.apache.commons.vfs2.FileSelector;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileType;
import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.BXMLSerializer;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.text.FileSizeFormat;
import org.apache.pivot.util.Filter;
import org.apache.pivot.util.concurrent.AbortException;
import org.apache.pivot.util.concurrent.Task;
import org.apache.pivot.util.concurrent.TaskExecutionException;
import org.apache.pivot.util.concurrent.TaskListener;
import org.apache.pivot.wtk.ActivityIndicator;
import org.apache.pivot.wtk.BoxPane;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.ButtonPressListener;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ComponentKeyListener;
import org.apache.pivot.wtk.ComponentMouseButtonListener;
import org.apache.pivot.wtk.ComponentTooltipListener;
import org.apache.pivot.wtk.Container;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.Display;
import org.apache.pivot.wtk.FocusTraversalDirection;
import org.apache.pivot.wtk.GridPane;
import org.apache.pivot.wtk.HorizontalAlignment;
import org.apache.pivot.wtk.ImageView;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Keyboard;
import org.apache.pivot.wtk.Keyboard.KeyCode;
import org.apache.pivot.wtk.Keyboard.KeyLocation;
import org.apache.pivot.wtk.Keyboard.Modifier;
import org.apache.pivot.wtk.Label;
import org.apache.pivot.wtk.ListButton;
import org.apache.pivot.wtk.ListButtonSelectionListener;
import org.apache.pivot.wtk.ListView;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Platform;
import org.apache.pivot.wtk.Point;
import org.apache.pivot.wtk.PushButton;
import org.apache.pivot.wtk.ScrollPane;
import org.apache.pivot.wtk.SortDirection;
import org.apache.pivot.wtk.Span;
import org.apache.pivot.wtk.StackPane;
import org.apache.pivot.wtk.Style;
import org.apache.pivot.wtk.TableView;
import org.apache.pivot.wtk.TableViewSelectionListener;
import org.apache.pivot.wtk.TableViewSortListener;
import org.apache.pivot.wtk.TaskAdapter;
import org.apache.pivot.wtk.TextArea;
import org.apache.pivot.wtk.TextInput;
import org.apache.pivot.wtk.TextInputContentListener;
import org.apache.pivot.wtk.Tooltip;
import org.apache.pivot.wtk.VFSBrowser;
import org.apache.pivot.wtk.VerticalAlignment;
import org.apache.pivot.wtk.media.Image;
import org.apache.pivot.wtk.skin.VFSBrowserSkin;
/**
* Terra Commons VFS browser skin.
*/
public class TerraVFSBrowserSkin extends VFSBrowserSkin {
/**
* Abstract renderer for displaying file system contents.
*/
public abstract static class FileRenderer extends BoxPane {
protected ImageView imageView = new ImageView();
protected Label label = new Label();
protected VFSBrowser fileBrowser = null;
public static final int ICON_WIDTH = 16;
public static final int ICON_HEIGHT = 16;
public static final Image FOLDER_IMAGE;
public static final Image HOME_FOLDER_IMAGE;
public static final Image FILE_IMAGE;
static {
try {
FOLDER_IMAGE = Image.load(FileRenderer.class.getResource("folder.png"));
HOME_FOLDER_IMAGE = Image.load(FileRenderer.class.getResource("folder_home.png"));
FILE_IMAGE = Image.load(FileRenderer.class.getResource("page_white.png"));
} catch (TaskExecutionException exception) {
throw new RuntimeException(exception);
}
}
public FileRenderer() {
getStyles().put(Style.verticalAlignment, VerticalAlignment.CENTER);
add(imageView);
add(label);
imageView.setPreferredSize(ICON_WIDTH, ICON_HEIGHT);
imageView.getStyles().put(Style.backgroundColor, null);
}
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
// Since this component doesn't have a parent, it won't be validated
// via layout; ensure that it is valid here
validate();
}
protected void setFileBrowser(VFSBrowser fileBrowser) {
this.fileBrowser = fileBrowser;
}
/**
* @return The icon to display for a given file.
*
* @param file The current file.
*/
public Image getIcon(FileObject file) {
Image icon;
if (file.getName().getType() == FileType.FOLDER) {
icon = file.equals(fileBrowser.getHomeDirectory()) ? HOME_FOLDER_IMAGE
: FOLDER_IMAGE;
} else {
icon = FILE_IMAGE;
}
return icon;
}
public boolean isFileHidden(FileObject file) {
try {
boolean hidden = false;
if (file != null) {
if (file.getName().getBaseName().length() != 0 && file.isHidden()) {
hidden = true;
}
}
return hidden;
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
/**
* List button file renderer.
*/
public static class ListButtonFileRenderer extends FileRenderer implements Button.DataRenderer {
public ListButtonFileRenderer() {
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.LEFT);
}
@Override
public void render(Object data, Button button, boolean highlight) {
if (data != null) {
FileObject file = (FileObject) data;
boolean hidden = isFileHidden(file);
// Update the image view
imageView.setImage(getIcon(file));
imageView.getStyles().put(Style.opacity, button.isEnabled() && !hidden ? 1.0f : 0.5f);
// Update the label
String text = file.getName().getBaseName();
if (text.length() == 0) {
text = FileName.ROOT_PATH;
}
label.setText(text);
Object color = null;
if (button.isEnabled() && !hidden) {
color = button.getStyles().get(Style.color);
} else {
color = button.getStyles().get(Style.disabledColor);
}
label.getStyles().put(Style.color, color);
}
}
@Override
public String toString(Object item) {
FileObject file = (FileObject) item;
String text = file.getName().getBaseName();
if (text.length() == 0) {
text = FileName.ROOT_PATH;
}
return text;
}
}
/**
* List view file renderer.
*/
public static class ListViewFileRenderer extends FileRenderer implements ListView.ItemRenderer {
public ListViewFileRenderer() {
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.LEFT);
getStyles().put(Style.padding, new Insets(2, 3, 2, 3));
}
@Override
public void render(Object item, int index, ListView listView, boolean selected,
Button.State state, boolean highlighted, boolean disabled) {
boolean hidden = false;
label.getStyles().copy(Style.font, listView.getStyles());
if (item != null) {
FileObject file = (FileObject) item;
hidden = isFileHidden(file);
// Update the image view
imageView.setImage(getIcon(file));
imageView.getStyles().put(Style.opacity,
(listView.isEnabled() && !disabled && !hidden) ? 1.0f : 0.5f);
// Update the label
String text = file.getName().getBaseName();
if (text.length() == 0) {
text = FileName.ROOT_PATH;
}
label.setText(text);
}
Object color = null;
if (listView.isEnabled() && !disabled && !hidden) {
if (selected) {
if (listView.isFocused()) {
color = listView.getStyles().get(Style.selectionColor);
} else {
color = listView.getStyles().get(Style.inactiveSelectionColor);
}
} else {
color = listView.getStyles().get(Style.color);
}
} else {
color = listView.getStyles().get(Style.disabledColor);
}
label.getStyles().put(Style.color, color);
}
@Override
public String toString(Object item) {
FileObject file = (FileObject) item;
// TODO: should this be the full path or the base name?
String text = file.getName().getBaseName();
if (text.length() == 0) {
text = FileName.ROOT_PATH;
}
return text;
}
}
/**
* Table view file renderer.
*/
public static class TableViewFileRenderer extends FileRenderer implements
TableView.CellRenderer {
public static final String NAME_KEY = "name";
public static final String SIZE_KEY = "size";
public static final String LAST_MODIFIED_KEY = "lastModified";
public TableViewFileRenderer() {
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.CENTER);
getStyles().put(Style.padding, new Insets(2));
}
@Override
public void render(Object row, int rowIndex, int columnIndex, TableView tableView,
String columnName, boolean selected, boolean highlighted, boolean disabled) {
boolean hidden = false;
if (row != null) {
FileObject file = (FileObject) row;
String text = null;
Image icon = null;
try {
FileType type = file.getType();
hidden = isFileHidden(file);
if (columnName.equals(NAME_KEY)) {
text = file.getName().getBaseName();
icon = getIcon(file);
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.LEFT);
} else if (columnName.equals(SIZE_KEY)) {
if (type == FileType.FOLDER || type == FileType.IMAGINARY) {
text = "";
} else {
long length = file.getContent().getSize();
text = FileSizeFormat.getInstance().format(length);
}
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.RIGHT);
} else if (columnName.equals(LAST_MODIFIED_KEY)) {
if (type == FileType.FOLDER || type == FileType.IMAGINARY) {
text = "";
} else {
long lastModified = file.getContent().getLastModifiedTime();
Date lastModifiedDate = new Date(lastModified);
text = DATE_FORMAT.format(lastModifiedDate);
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.RIGHT);
}
} else {
System.err.println("Unexpected column name in " + getClass().getName()
+ ": " + columnName);
}
} catch (FileSystemException fse) {
// TODO: should we display an exception error here?
throw new RuntimeException(fse);
}
label.setText(text);
imageView.setImage(icon);
imageView.getStyles().put(Style.opacity,
(tableView.isEnabled() && !disabled && !hidden) ? 1.0f : 0.5f);
}
label.getStyles().copy(Style.font, tableView.getStyles());
Color color;
if (tableView.isEnabled() && !disabled && !hidden) {
if (selected) {
if (tableView.isFocused()) {
color = tableView.getStyles().getColor(Style.selectionColor);
} else {
color = tableView.getStyles().getColor(Style.inactiveSelectionColor);
}
} else {
color = tableView.getStyles().getColor(Style.color);
}
} else {
color = tableView.getStyles().getColor(Style.disabledColor);
}
label.getStyles().put(Style.color, color);
}
@Override
public String toString(Object row, String columnName) {
String string;
FileObject file = (FileObject) row;
try {
FileType type = file.getType();
if (columnName.equals(NAME_KEY)) {
string = file.getName().getBaseName();
} else if (columnName.equals(SIZE_KEY)) {
if (type == FileType.FOLDER || type == FileType.IMAGINARY) {
string = "";
} else {
long length = file.getContent().getSize();
string = FileSizeFormat.getInstance().format(length);
}
} else if (columnName.equals(LAST_MODIFIED_KEY)) {
if (type == FileType.FOLDER || type == FileType.IMAGINARY) {
string = "";
} else {
long lastModified = file.getContent().getLastModifiedTime();
Date lastModifiedDate = new Date(lastModified);
string = DATE_FORMAT.format(lastModifiedDate);
}
} else {
System.err.println("Unexpected column name in " + getClass().getName() + ": "
+ columnName);
string = null;
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
return string;
}
}
/**
* Abstract base class for drive renderers.
*/
public abstract static class DriveRenderer extends BoxPane {
protected ImageView imageView = new ImageView();
protected Label label = new Label();
public static final int ICON_WIDTH = 16;
public static final int ICON_HEIGHT = 16;
public static final Image DRIVE_IMAGE;
static {
try {
DRIVE_IMAGE = Image.load(DriveRenderer.class.getResource("drive.png"));
} catch (TaskExecutionException exception) {
throw new RuntimeException(exception);
}
}
public DriveRenderer() {
getStyles().put(Style.verticalAlignment, VerticalAlignment.CENTER);
add(imageView);
add(label);
imageView.setPreferredSize(ICON_WIDTH, ICON_HEIGHT);
imageView.getStyles().put(Style.backgroundColor, null);
}
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
// Since this component doesn't have a parent, it won't be validated
// via layout; ensure that it is valid here
validate();
}
}
/**
* List button drive renderer.
*/
public static class ListButtonDriveRenderer extends DriveRenderer implements
Button.DataRenderer {
public ListButtonDriveRenderer() {
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.LEFT);
}
@Override
public void render(Object data, Button button, boolean highlight) {
if (data != null) {
FileObject file = (FileObject) data;
// Update the image view
imageView.setImage(DRIVE_IMAGE);
imageView.getStyles().put(Style.opacity, button.isEnabled() ? 1.0f : 0.5f);
// Update the label
label.setText(file.toString());
}
}
@Override
public String toString(Object data) {
return null;
}
}
/**
* List view drive renderer.
*/
public static class ListViewDriveRenderer extends DriveRenderer implements
ListView.ItemRenderer {
public ListViewDriveRenderer() {
getStyles().put(Style.horizontalAlignment, HorizontalAlignment.LEFT);
getStyles().put(Style.padding, new Insets(2, 3, 2, 3));
}
@Override
public void render(Object item, int index, ListView listView, boolean selected,
Button.State state, boolean highlighted, boolean disabled) {
label.getStyles().copy(Style.font, listView.getStyles());
Object color = null;
if (listView.isEnabled() && !disabled) {
if (selected) {
if (listView.isFocused()) {
color = listView.getStyles().get(Style.selectionColor);
} else {
color = listView.getStyles().get(Style.inactiveSelectionColor);
}
} else {
color = listView.getStyles().get(Style.color);
}
} else {
color = listView.getStyles().get(Style.disabledColor);
}
label.getStyles().put(Style.color, color);
if (item != null) {
FileObject file = (FileObject) item;
// Update the image view
imageView.setImage(DRIVE_IMAGE);
imageView.getStyles().put(Style.opacity,
(listView.isEnabled() && !disabled) ? 1.0f : 0.5f);
// Update the label
label.setText(file.toString());
}
}
@Override
public String toString(Object item) {
return null;
}
}
public abstract static class FileComparator implements Comparator<FileObject>, Serializable {
private static final long serialVersionUID = 1L;
@Override
public abstract int compare(FileObject f1, FileObject f2);
}
public static class FileNameAscendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
int result;
if (!file1IsFile && file2IsFile) {
result = -1;
} else if (file1IsFile && !file2IsFile) {
result = 1;
} else {
// Do the compare according to the rules of the file system
result = f1.compareTo(f2);
}
return result;
}
}
public static class FileNameDescendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
int result;
if (!file1IsFile && file2IsFile) {
result = -1;
} else if (file1IsFile && !file2IsFile) {
result = 1;
} else {
// Do the compare according to the rules of the file system
result = f2.compareTo(f1);
}
return result;
}
}
public static class FileSizeAscendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
try {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
long size1 = file1IsFile ? f1.getContent().getSize() : 0L;
long size2 = file2IsFile ? f2.getContent().getSize() : 0L;
return Long.signum(size1 - size2);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
public static class FileSizeDescendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
try {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
long size1 = file1IsFile ? f1.getContent().getSize() : 0L;
long size2 = file2IsFile ? f2.getContent().getSize() : 0L;
return Long.signum(size2 - size1);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
public static class FileDateAscendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
try {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
long time1 = file1IsFile ? f1.getContent().getLastModifiedTime() : 0L;
long time2 = file2IsFile ? f2.getContent().getLastModifiedTime() : 0L;
return Long.signum(time1 - time2);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
public static class FileDateDescendingComparator extends FileComparator {
private static final long serialVersionUID = 1L;
@Override
public int compare(FileObject f1, FileObject f2) {
try {
FileType f1Type = f1.getName().getType();
FileType f2Type = f2.getName().getType();
boolean file1IsFile = f1Type == FileType.FILE || f1Type == FileType.FILE_OR_FOLDER;
boolean file2IsFile = f2Type == FileType.FILE || f2Type == FileType.FILE_OR_FOLDER;
long time1 = file1IsFile ? f1.getContent().getLastModifiedTime() : 0L;
long time2 = file2IsFile ? f2.getContent().getLastModifiedTime() : 0L;
return Long.signum(time2 - time1);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
/**
* @return A new {@link FileObject} comparator for the given column and sort order.
* @param columnName Name of the column to sort on.
* @param sortDirection The sort order.
*/
public static FileComparator getFileComparator(String columnName, SortDirection sortDirection) {
if (columnName.equals("name")) {
return sortDirection == SortDirection.ASCENDING ? new FileNameAscendingComparator()
: new FileNameDescendingComparator();
} else if (columnName.equals("size")) {
return sortDirection == SortDirection.ASCENDING ? new FileSizeAscendingComparator()
: new FileSizeDescendingComparator();
} else if (columnName.equals("lastModified")) {
return sortDirection == SortDirection.ASCENDING ? new FileDateAscendingComparator()
: new FileDateDescendingComparator();
} else {
throw new IllegalArgumentException("Invalid column name for file comparator.");
}
}
/**
* Include file filter.
*/
public static class IncludeFileFilter implements Filter<FileObject> {
private String match;
public IncludeFileFilter() {
this(null);
}
public IncludeFileFilter(String match) {
this.match = (match == null ? null : match.toLowerCase());
}
@Override
public boolean include(FileObject file) {
boolean include = true;
if (match != null) {
String name = file.getName().getBaseName();
name = name.toLowerCase();
if (match.startsWith("*")) {
if (match.length() == 1) {
include = true;
} else {
include = name.contains(match.substring(1));
}
} else {
include = name.startsWith(match);
}
}
return include;
}
}
public static class FullFileSelector implements FileSelector {
private FileFilter hiddenFileFilter;
private Filter<FileObject> includeFileFilter;
private Filter<FileObject> excludeFileFilter;
public FullFileSelector(boolean showHiddenFiles, Filter<FileObject> includeFileFilter,
Filter<FileObject> excludeFileFilter) {
this.hiddenFileFilter = showHiddenFiles ? null : HIDDEN_FILE_FILTER;
this.includeFileFilter = includeFileFilter;
this.excludeFileFilter = excludeFileFilter;
}
@Override
public boolean includeFile(FileSelectInfo fileInfo) {
boolean include = hiddenFileFilter == null ? true : hiddenFileFilter.accept(fileInfo);
if (include && includeFileFilter != null) {
include = includeFileFilter.include(fileInfo.getFile());
}
if (include && excludeFileFilter != null) {
include = !excludeFileFilter.include(fileInfo.getFile());
}
// Don't include the base folder itself
if (include && fileInfo.getFile() == fileInfo.getBaseFolder()) {
include = false;
}
return include;
}
@Override
public boolean traverseDescendents(FileSelectInfo fileInfo) {
// Only traverse the first-level descendents
return fileInfo.getDepth() == 0;
}
}
private class RefreshFileListTask extends Task<ArrayList<FileObject>> {
private Filter<FileObject> includeFileFilter;
private Filter<FileObject> excludeFileFilter;
private FileComparator fileComparator;
public RefreshFileListTask(Filter<FileObject> includeFileFilter,
Filter<FileObject> excludeFileFilter, FileComparator fileComparator) {
this.includeFileFilter = includeFileFilter;
this.excludeFileFilter = excludeFileFilter;
this.fileComparator = fileComparator;
}
@Override
public ArrayList<FileObject> execute() {
VFSBrowser fileBrowser = (VFSBrowser) getComponent();
FileObject rootDirectory = fileBrowser.getRootDirectory();
if (abort) {
throw new AbortException();
}
try {
FileObject[] files = rootDirectory.findFiles(new FullFileSelector(
showHiddenFiles, includeFileFilter, excludeFileFilter));
if (abort) {
throw new AbortException();
}
Arrays.sort(files, fileComparator);
return new ArrayList<>(files, 0, files.length);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
private Component content = null;
@BXML
private BoxPane pushButtonPane = null;
@BXML
private ListButton driveListButton = null;
@BXML
private ListButton pathListButton = null;
@BXML
private PushButton goUpButton = null;
@BXML
private PushButton newFolderButton = null;
@BXML
private PushButton goHomeButton = null;
@BXML
private TextInput searchTextInput = null;
@BXML
private StackPane fileStackPane = null;
@BXML
private ScrollPane fileScrollPane = null;
@BXML
private TableView fileTableView = null;
private ActivityIndicator indicator = null;
private GridPane activityGrid = null;
private boolean keyboardFolderTraversalEnabled = true;
private boolean hideDisabledFiles = false;
private boolean showHiddenFiles = false;
private boolean updatingSelection = false;
private boolean refreshRoots = true;
private RefreshFileListTask refreshFileListTask = null;
private FileObject homeDirectory = null;
private static final FileFilter HIDDEN_FILE_FILTER = new FileFilter() {
@Override
public boolean accept(FileSelectInfo fileInfo) {
try {
return !fileInfo.getFile().isHidden();
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
};
private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT,
DateFormat.SHORT);
@Override
public void install(Component component) {
super.install(component);
final VFSBrowser fileBrowser = (VFSBrowser) component;
BXMLSerializer bxmlSerializer = new BXMLSerializer();
try {
content = (Component) bxmlSerializer.readObject(TerraVFSBrowserSkin.class,
"terra_vfs_browser_skin.bxml", true);
} catch (IOException exception) {
throw new RuntimeException(exception);
} catch (SerializationException exception) {
throw new RuntimeException(exception);
}
fileBrowser.add(content);
bxmlSerializer.bind(this, TerraVFSBrowserSkin.class);
// Notify all the renderers of which component they are dealing with
((FileRenderer) pathListButton.getDataRenderer()).setFileBrowser(fileBrowser);
((FileRenderer) pathListButton.getItemRenderer()).setFileBrowser(fileBrowser);
for (TableView.Column col : fileTableView.getColumns()) {
((FileRenderer) col.getCellRenderer()).setFileBrowser(fileBrowser);
}
homeDirectory = fileBrowser.getHomeDirectory();
driveListButton.getListButtonSelectionListeners().add(
new ListButtonSelectionListener() {
@Override
public void selectedItemChanged(ListButton listButton, Object previousSelectedItem) {
if (previousSelectedItem != null) {
FileObject drive = (FileObject) listButton.getSelectedItem();
try {
if (drive.isReadable()) {
fileBrowser.setRootDirectory(drive);
} else {
refreshRoots = true;
listButton.setSelectedItem(previousSelectedItem);
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
});
pathListButton.getListButtonSelectionListeners().add(
new ListButtonSelectionListener() {
@Override
public void selectedItemChanged(ListButton listButton, Object previousSelectedItem) {
FileObject ancestorDirectory = (FileObject) listButton.getSelectedItem();
if (ancestorDirectory != null) {
try {
fileBrowser.setRootDirectory(ancestorDirectory);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
});
goUpButton.getButtonPressListeners().add(new ButtonPressListener() {
@Override
public void buttonPressed(Button button) {
try {
FileObject rootDirectory = fileBrowser.getRootDirectory();
FileObject parentDirectory = rootDirectory.getParent();
fileBrowser.setRootDirectory(parentDirectory);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
});
newFolderButton.getButtonPressListeners().add(new ButtonPressListener() {
@Override
public void buttonPressed(Button button) {
// TODO
}
});
goHomeButton.getButtonPressListeners().add(new ButtonPressListener() {
@Override
public void buttonPressed(Button button) {
try {
fileBrowser.setRootDirectory(fileBrowser.getHomeDirectory());
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
});
/**
* {@link KeyCode#DOWN DOWN} Transfer focus to the file list and select
* the first item.<br> {@link KeyCode#ESCAPE ESCAPE} Clear the search
* field.
*/
searchTextInput.getComponentKeyListeners().add(new ComponentKeyListener() {
@Override
public boolean keyPressed(Component componentArgument, int keyCode,
KeyLocation keyLocation) {
boolean consumed = false;
if (keyCode == KeyCode.ESCAPE) {
searchTextInput.setText("");
consumed = true;
} else if (keyCode == KeyCode.DOWN) {
if (fileTableView.getTableData().getLength() > 0) {
fileTableView.setSelectedIndex(0);
fileTableView.requestFocus();
}
}
return consumed;
}
});
searchTextInput.getTextInputContentListeners().add(new TextInputContentListener() {
@Override
public void textChanged(TextInput textInput) {
refreshFileList();
}
});
fileTableView.getTableViewSelectionListeners().add(new TableViewSelectionListener() {
@Override
public void selectedRangeAdded(TableView tableView, int rangeStart, int rangeEnd) {
if (!updatingSelection) {
updatingSelection = true;
try {
for (int i = rangeStart; i <= rangeEnd; i++) {
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
FileObject file = files.get(i);
fileBrowser.addSelectedFile(file);
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
updatingSelection = false;
}
}
@Override
public void selectedRangeRemoved(TableView tableView, int rangeStart, int rangeEnd) {
if (!updatingSelection) {
updatingSelection = true;
for (int i = rangeStart; i <= rangeEnd; i++) {
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
FileObject file = files.get(i);
fileBrowser.removeSelectedFile(file);
}
updatingSelection = false;
}
}
@Override
public void selectedRangesChanged(TableView tableView,
Sequence<Span> previousSelectedRanges) {
if (!updatingSelection && previousSelectedRanges != null) {
updatingSelection = true;
@SuppressWarnings("unchecked")
Sequence<FileObject> files = (Sequence<FileObject>) tableView.getSelectedRows();
for (int i = 0, n = files.getLength(); i < n; i++) {
FileObject file = files.get(i);
files.update(i, file);
}
try {
fileBrowser.setSelectedFiles(files);
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
updatingSelection = false;
}
}
@Override
public void selectedRowChanged(TableView tableView, Object previousSelectedRow) {
// No-op
}
});
fileTableView.getTableViewSortListeners().add(new TableViewSortListener() {
@Override
public void sortChanged(TableView tableView) {
TableView.SortDictionary sort = fileTableView.getSort();
if (!sort.isEmpty()) {
Dictionary.Pair<String, SortDirection> pair = fileTableView.getSort().get(0);
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
files.setComparator(getFileComparator(pair.key, pair.value));
}
}
});
fileTableView.getComponentMouseButtonListeners().add(
new ComponentMouseButtonListener() {
private int index = -1;
@Override
public boolean mouseClick(Component componentArgument, Mouse.Button button, int x,
int y, int count) {
boolean consumed = false;
if (count == 1) {
index = fileTableView.getRowAt(y);
} else if (count == 2) {
int indexLocal = fileTableView.getRowAt(y);
if (indexLocal != -1 && indexLocal == this.index
&& fileTableView.isRowSelected(indexLocal)) {
FileObject file = (FileObject) fileTableView.getTableData().get(
indexLocal);
try {
if (file.getName().getType() == FileType.FOLDER) {
fileBrowser.setRootDirectory(file);
consumed = true;
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
}
return consumed;
}
});
fileBrowser.setFocusTraversalPolicy(new IndexFocusTraversalPolicy() {
@Override
public Component getNextComponent(Container container, Component componentArgument,
FocusTraversalDirection direction) {
Component nextComponent;
if (componentArgument == null) {
nextComponent = fileTableView;
} else {
nextComponent = super.getNextComponent(container, componentArgument, direction);
}
return nextComponent;
}
});
fileTableView.setSort(TableViewFileRenderer.NAME_KEY, SortDirection.ASCENDING);
fileTableView.getComponentTooltipListeners().add(new ComponentTooltipListener() {
@Override
public void tooltipTriggered(Component comp, int x, int y) {
// Check that we are on the first column.
if (fileTableView.getColumnAt(x) != 0) {
return;
}
// Gets the underlying file
int row = fileTableView.getRowAt(y);
if (row < 0) {
return;
}
FileObject file = (FileObject) fileTableView.getTableData().get(row);
// Construct and show the tooltip.
final Tooltip tooltip = new Tooltip();
String text = null;
if (file != null) {
text = file.getName().getBaseName();
}
if (text == null || text.isEmpty()) {
return;
}
TextArea toolTipTextArea = new TextArea();
toolTipTextArea.setText(text);
toolTipTextArea.getStyles().put(Style.wrapText, true);
toolTipTextArea.getStyles().put(Style.backgroundColor, null);
tooltip.setContent(toolTipTextArea);
Point location = comp.getDisplay().getMouseLocation();
x = location.x;
y = location.y;
// Ensure that the tooltip stays on screen
Display display = comp.getDisplay();
int tooltipHeight = tooltip.getPreferredHeight();
if (y + tooltipHeight > display.getHeight()) {
y -= tooltipHeight;
}
int tooltipXOffset = 16;
int padding = 15;
toolTipTextArea.setMaximumWidth(display.getWidth() - (x + tooltipXOffset + padding));
tooltip.setLocation(x + tooltipXOffset, y);
tooltip.open(comp.getWindow());
}
});
rootDirectoryChanged(fileBrowser, null);
selectedFilesChanged(fileBrowser, null);
}
@Override
public int getPreferredWidth(int height) {
return content.getPreferredWidth(height);
}
@Override
public int getPreferredHeight(int width) {
return content.getPreferredHeight(width);
}
@Override
public Dimensions getPreferredSize() {
return content.getPreferredSize();
}
@Override
public void layout() {
int width = getWidth();
int height = getHeight();
content.setLocation(0, 0);
content.setSize(width, height);
}
@Override
public FileObject getFileAt(int x, int y) {
FileObject file = null;
VFSBrowser fileBrowser = (VFSBrowser) getComponent();
Component component = fileBrowser.getDescendantAt(x, y);
if (component == fileTableView) {
Point location = fileTableView.mapPointFromAncestor(fileBrowser, x, y);
int index = fileTableView.getRowAt(location.y);
if (index != -1) {
file = (FileObject) fileTableView.getTableData().get(index);
}
}
return file;
}
@Override
public void addActionComponent(Component component) {
pushButtonPane.add(component);
}
public boolean isKeyboardFolderTraversalEnabled() {
return keyboardFolderTraversalEnabled;
}
public void setKeyboardFolderTraversalEnabled(boolean keyboardFolderTraversalEnabled) {
this.keyboardFolderTraversalEnabled = keyboardFolderTraversalEnabled;
}
public boolean isHideDisabledFiles() {
return hideDisabledFiles;
}
public void setHideDisabledFiles(boolean hideDisabledFiles) {
this.hideDisabledFiles = hideDisabledFiles;
refreshFileList();
}
/**
* @return Whether hidden files will be shown in the browser.
*/
public boolean isShowHiddenFiles() {
return showHiddenFiles;
}
/**
* Set to determine if hidden files should be shown.
* @param showHiddenFiles Whether to show hidden files.
*/
public void setShowHiddenFiles(boolean showHiddenFiles) {
this.showHiddenFiles = showHiddenFiles;
refreshFileList();
}
/**
* {@link KeyCode#ENTER ENTER} Change into the selected directory if
* {@link #keyboardFolderTraversalEnabled} is true.<br>
* {@link KeyCode#DELETE DELETE} or {@link KeyCode#BACKSPACE BACKSPACE}
* Change into the parent of the current directory.<br> {@link KeyCode#F5
* F5} Refresh the file list.
*/
@Override
public boolean keyPressed(Component component, int keyCode, KeyLocation keyLocation) {
boolean consumed = super.keyPressed(component, keyCode, keyLocation);
VFSBrowser fileBrowser = (VFSBrowser) getComponent();
if (keyCode == KeyCode.ENTER && keyboardFolderTraversalEnabled) {
Sequence<FileObject> selectedFiles = fileBrowser.getSelectedFiles();
if (selectedFiles.getLength() == 1) {
FileObject selectedFile = selectedFiles.get(0);
try {
if (selectedFile.getName().getType() == FileType.FOLDER) {
fileBrowser.setRootDirectory(selectedFile);
consumed = true;
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
}
} else if (keyCode == KeyCode.DELETE || keyCode == KeyCode.BACKSPACE) {
FileObject rootDirectory = fileBrowser.getRootDirectory();
try {
FileObject parentDirectory = rootDirectory.getParent();
if (parentDirectory != null) {
fileBrowser.setRootDirectory(parentDirectory);
consumed = true;
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
} else if (keyCode == KeyCode.F5) {
refreshFileList();
consumed = true;
}
return consumed;
}
/**
* CommandModifier + {@link KeyCode#F F} Transfers focus to the search
* TextInput.
*
* @see Platform#getCommandModifier()
*/
@Override
public boolean keyReleased(Component component, int keyCode, KeyLocation keyLocation) {
boolean consumed = super.keyReleased(component, keyCode, keyLocation);
Modifier commandModifier = Platform.getCommandModifier();
if (keyCode == KeyCode.F && Keyboard.isPressed(commandModifier)) {
searchTextInput.requestFocus();
consumed = true;
}
return consumed;
}
@Override
public void managerChanged(VFSBrowser fileBrowser, FileSystemManager previousManager) {
// TODO: Is there anything to do here? Surely, but what?
}
@Override
public void rootDirectoryChanged(VFSBrowser fileBrowser, FileObject previousRootDirectory) {
ArrayList<FileObject> path = new ArrayList<>();
// FileSystemManager manager = fileBrowser.getManager();
FileObject rootDirectory = fileBrowser.getRootDirectory();
try {
FileObject ancestorDirectory = rootDirectory.getParent();
while (ancestorDirectory != null) {
path.add(ancestorDirectory);
ancestorDirectory = ancestorDirectory.getParent();
}
} catch (FileSystemException fse) {
throw new RuntimeException(fse);
}
@SuppressWarnings("unchecked")
ArrayList<FileObject> drives = (ArrayList<FileObject>) driveListButton.getListData();
if (refreshRoots) {
// TODO: this is ugly -- need to do much better at managing drive
// list with VFS
// There is an open question on the Dev list about adding
// "getFileRoots()" to the VFS API.
/*
* try { FileObject[] roots = new FileObject[1]; roots[0] =
* manager.resolveFile
* (manager.getBaseFile().getName().getRoot().getURI()); drives =
* new ArrayList<>(); for (int i = 0; i < roots.length; i++) {
* FileObject root = roots[i]; if (root.exists()) {
* drives.add(root); } } driveListButton.setListData(drives); }
* catch (FileSystemException fse) { throw new
* RuntimeException(fse); }
*/
refreshRoots = false;
}
driveListButton.setVisible(drives.getLength() > 1);
FileObject drive;
if (path.getLength() == 0) {
drive = rootDirectory;
} else {
drive = path.get(path.getLength() - 1);
}
driveListButton.setSelectedItem(drive);
pathListButton.setListData(path);
pathListButton.setButtonData(rootDirectory);
pathListButton.setEnabled(rootDirectory.getName().getDepth() > 0);
goUpButton.setEnabled(pathListButton.isEnabled());
goHomeButton.setEnabled(!rootDirectory.equals(homeDirectory));
fileScrollPane.setScrollTop(0);
fileScrollPane.setScrollLeft(0);
searchTextInput.setText("");
fileTableView.requestFocus();
}
@Override
public void homeDirectoryChanged(VFSBrowser fileBrowser, FileObject previousHomeDirectory) {
this.homeDirectory = fileBrowser.getHomeDirectory();
goHomeButton.setEnabled(!fileBrowser.getRootDirectory().equals(homeDirectory));
// Refresh the list in order to redo the icons correctly
refreshFileList();
}
@Override
public void selectedFileAdded(VFSBrowser fileBrowser, FileObject file) {
if (!updatingSelection) {
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
int index = files.indexOf(file);
if (index != -1) {
updatingSelection = true;
fileTableView.addSelectedIndex(index);
updatingSelection = false;
}
}
}
@Override
public void selectedFileRemoved(VFSBrowser fileBrowser, FileObject file) {
if (!updatingSelection) {
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
int index = files.indexOf(file);
if (index != -1) {
updatingSelection = true;
fileTableView.removeSelectedIndex(index);
updatingSelection = false;
}
}
}
@Override
public void selectedFilesChanged(VFSBrowser fileBrowser,
Sequence<FileObject> previousSelectedFiles) {
updateSelectedFiles(fileBrowser);
}
private void updateSelectedFiles(VFSBrowser fileBrowser) {
if (!updatingSelection) {
Sequence<FileObject> selectedFiles = fileBrowser.getSelectedFiles();
ArrayList<Span> selectedRanges = new ArrayList<>();
for (int i = 0, n = selectedFiles.getLength(); i < n; i++) {
FileObject selectedFile = selectedFiles.get(i);
@SuppressWarnings("unchecked")
List<FileObject> files = (List<FileObject>) fileTableView.getTableData();
int index = files.indexOf(selectedFile);
if (index != -1) {
selectedRanges.add(new Span(index, index));
}
}
updatingSelection = true;
fileTableView.setSelectedRanges(selectedRanges);
updatingSelection = false;
}
}
@Override
public void multiSelectChanged(VFSBrowser fileBrowser) {
fileTableView.setSelectMode(fileBrowser.isMultiSelect() ? TableView.SelectMode.MULTI
: TableView.SelectMode.SINGLE);
}
@Override
public void disabledFileFilterChanged(VFSBrowser fileBrowser,
Filter<FileObject> previousDisabledFileFilter) {
fileTableView.setDisabledRowFilter(fileBrowser.getDisabledFileFilter());
refreshFileList();
}
private void refreshFileList() {
// Cancel any outstanding task
if (refreshFileListTask != null) {
refreshFileListTask.abort();
if (indicator != null) {
indicator.setActive(false);
fileStackPane.remove(fileStackPane.getLength() - 1, 1);
}
}
if (indicator == null) {
indicator = new ActivityIndicator();
activityGrid = new GridPane(5);
GridPane.Row row1 = new GridPane.Row(activityGrid);
GridPane.Row row2 = new GridPane.Row(activityGrid);
GridPane.Row row3 = new GridPane.Row(activityGrid);
for (int i = 0; i < 5; i++) {
row1.add(new GridPane.Filler());
if (i == 2) {
row2.add(indicator);
} else {
row2.add(new GridPane.Filler());
}
row3.add(new GridPane.Filler());
}
}
fileStackPane.add(activityGrid);
indicator.setActive(true);
fileTableView.setTableData(new ArrayList<FileObject>());
String text = searchTextInput.getText().trim();
Filter<FileObject> disabledFileFilter = hideDisabledFiles ? ((VFSBrowser) getComponent()).getDisabledFileFilter()
: null;
Filter<FileObject> includeFileFilter = text.length() != 0 ? new IncludeFileFilter(text)
: null;
TableView.SortDictionary sort = fileTableView.getSort();
final FileComparator fileComparator;
if (sort.isEmpty()) {
fileComparator = null;
} else {
Dictionary.Pair<String, SortDirection> pair = fileTableView.getSort().get(0);
fileComparator = getFileComparator(pair.key, pair.value);
}
refreshFileListTask = new RefreshFileListTask(includeFileFilter, disabledFileFilter,
fileComparator);
refreshFileListTask.execute(new TaskAdapter<>(new TaskListener<ArrayList<FileObject>>() {
@Override
public void taskExecuted(Task<ArrayList<FileObject>> task) {
if (task == refreshFileListTask) {
indicator.setActive(false);
fileStackPane.remove(fileStackPane.getLength() - 1, 1);
ArrayList<FileObject> fileList = task.getResult();
fileTableView.setTableData(fileList);
updateSelectedFiles((VFSBrowser) getComponent());
refreshFileListTask = null;
}
}
@Override
public void executeFailed(Task<ArrayList<FileObject>> task) {
if (task == refreshFileListTask) {
indicator.setActive(false);
fileStackPane.remove(fileStackPane.getLength() - 1, 1);
refreshFileListTask = null;
}
}
}));
}
}