More file browser improvements [release]

This commit is contained in:
crschnick 2023-03-15 17:33:07 +00:00
parent 355de8e2fc
commit d5e024c43e
8 changed files with 101 additions and 57 deletions

View file

@ -22,14 +22,13 @@ import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.stream.Stream;
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
import static javafx.scene.control.TableColumn.SortType.ASCENDING;
@ -41,7 +40,6 @@ final class FileListComp extends AnchorPane {
private static final PseudoClass DRAG = PseudoClass.getPseudoClass("drag");
private static final PseudoClass DRAG_OVER = PseudoClass.getPseudoClass("drag-over");
private static final PseudoClass DRAG_INTO_CURRENT = PseudoClass.getPseudoClass("drag-into-current");
private static final String UNKNOWN = "unknown";
private final FileListModel fileList;
@ -59,7 +57,6 @@ final class FileListComp extends AnchorPane {
@SuppressWarnings("unchecked")
private TableView<FileSystem.FileEntry> createTable() {
var editing = new SimpleObjectProperty<FileSystem.FileEntry>();
var filenameCol = new TableColumn<FileSystem.FileEntry, String>("Name");
filenameCol.setCellValueFactory(param -> new SimpleStringProperty(
param.getValue() != null
@ -67,7 +64,7 @@ final class FileListComp extends AnchorPane {
: null));
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
filenameCol.setSortType(ASCENDING);
filenameCol.setCellFactory(col -> new FilenameCell(editing));
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
var sizeCol = new TableColumn<FileSystem.FileEntry, Number>("Size");
sizeCol.setCellValueFactory(
@ -91,22 +88,29 @@ final class FileListComp extends AnchorPane {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
filenameCol.minWidthProperty().bind(table.widthProperty().multiply(0.5));
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER) || fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)
|| fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
} else {
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
}
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
fileList.getSelected().setAll(c.getList());
fileList.getFileSystemModel().getBrowserModel().getSelectedFiles().setAll(c.getList());
});
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>)
c -> {
fileList.getSelected().setAll(c.getList());
fileList.getFileSystemModel()
.getBrowserModel()
.getSelectedFiles()
.setAll(c.getList());
});
table.setOnKeyPressed(event -> {
if (event.isControlDown()
&& event.getCode().equals(KeyCode.C)
&& table.getSelectionModel().getSelectedItems().size() > 0) {
FileBrowserClipboard.startCopy(fileList.getFileSystemModel().getCurrentDirectory(), table.getSelectionModel().getSelectedItems());
FileBrowserClipboard.startCopy(
fileList.getFileSystemModel().getCurrentDirectory(),
table.getSelectionModel().getSelectedItems());
event.consume();
}
@ -140,31 +144,20 @@ final class FileListComp extends AnchorPane {
table.setRowFactory(param -> {
TableRow<FileSystem.FileEntry> row = new TableRow<>();
var listEntry = Bindings.createObjectBinding(() -> new FileListEntry(row, row.getItem(), fileList), row.itemProperty());
var listEntry = Bindings.createObjectBinding(
() -> new FileListEntry(row, row.getItem(), fileList), row.itemProperty());
row.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue && listEntry.get().isSynthetic()) {
row.updateSelected(false);
}
});
row.itemProperty().addListener((observable, oldValue, newValue) -> {
row.pseudoClassStateChanged(DRAG, false);
row.pseudoClassStateChanged(DRAG_OVER, false);
});
row.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> {
t.consume();
if (row.isEmpty()) {
return;
}
var cm = new FileContextMenu(fileList.getFileSystemModel(), row.getItem(), editing);
if (t.getButton() == MouseButton.SECONDARY) {
cm.show(row, t.getScreenX(), t.getScreenY());
}
});
row.setOnMouseClicked(e -> {
if (e.getClickCount() == 2 && !row.isEmpty()) {
fileList.onDoubleClick(row.getItem());
}
});
fileList.getDraggedOverDirectory().addListener((observable, oldValue, newValue) -> {
row.pseudoClassStateChanged(DRAG_OVER, newValue != null && newValue == row.getItem());
});
@ -173,6 +166,16 @@ final class FileListComp extends AnchorPane {
table.pseudoClassStateChanged(DRAG_INTO_CURRENT, newValue);
});
row.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> {
listEntry.get().onMouseClick(t);
});
row.setOnMouseClicked(e -> {
if (e.getClickCount() == 2 && !row.isEmpty()) {
fileList.onDoubleClick(row.getItem());
}
});
row.setOnDragEntered(event -> {
listEntry.get().onDragEntered(event);
});
@ -194,7 +197,13 @@ final class FileListComp extends AnchorPane {
SimpleChangeListener.apply(fileList.getShown(), (newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
table.getItems().setAll(newValue);
var newItems = new ArrayList<FileSystem.FileEntry>();
var parentEntry = fileList.getFileSystemModel().getCurrentParentDirectory();
if (parentEntry != null) {
newItems.add(parentEntry);
}
newItems.addAll(newValue);
table.getItems().setAll(newItems);
if (newValue.size() > 0) {
table.scrollTo(0);
}
@ -217,8 +226,7 @@ final class FileListComp extends AnchorPane {
public FilenameCell(Property<FileSystem.FileEntry> editing) {
editing.addListener((observable, oldValue, newValue) -> {
if (getTableRow().getItem() != null
&& getTableRow().getItem().equals(newValue)) {
if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) {
textField.requestFocus();
}
});
@ -257,7 +265,7 @@ final class FileListComp extends AnchorPane {
pseudoClassStateChanged(FOLDER, isDirectory);
var fileName = FileNames.getFileName(fullPath);
var fileName = getTableRow().getItem().equals(fileList.getFileSystemModel().getCurrentParentDirectory()) ? ".." : FileNames.getFileName(fullPath);
var hidden = getTableRow().getItem().isHidden() || fileName.startsWith(".");
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
text.set(fileName);
@ -277,17 +285,7 @@ final class FileListComp extends AnchorPane {
} else {
var path = getTableRow().getItem();
if (path.isDirectory()) {
if (true) {
setText("");
return;
}
try (Stream<FileSystem.FileEntry> stream =
path.getFileSystem().listFiles(path.getPath())) {
setText(stream.count() + " items");
} catch (Exception e) {
setText(UNKNOWN);
}
setText("");
} else {
setText(byteCount(fileSize.longValue()));
}
@ -307,7 +305,7 @@ final class FileListComp extends AnchorPane {
fileTime != null
? HumanReadableFormat.date(
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
: UNKNOWN);
: "");
}
}
}

View file

@ -5,10 +5,7 @@ import io.xpipe.core.store.FileSystem;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.input.*;
import lombok.Getter;
import java.io.File;
@ -33,6 +30,22 @@ public class FileListEntry {
this.model = model;
}
public void onMouseClick(MouseEvent t) {
t.consume();
if (item == null || isSynthetic()) {
return;
}
var cm = new FileContextMenu(model.getFileSystemModel(), item, model.getEditing());
if (t.getButton() == MouseButton.SECONDARY) {
cm.show(row, t.getScreenX(), t.getScreenY());
}
}
public boolean isSynthetic() {
return item != null && item.equals(model.getFileSystemModel().getCurrentParentDirectory());
}
private boolean acceptsDrop(DragEvent event) {
if (FileBrowserClipboard.currentDragClipboard == null) {
return false;
@ -95,6 +108,10 @@ public class FileListEntry {
return;
}
if (isSynthetic()) {
return;
}
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "img/file_drag_icon.png")
.orElseThrow();
var image = new Image(url.toString(), 80, 80, true, false);
@ -116,7 +133,6 @@ public class FileListEntry {
}
private void handleHoverTimer(DragEvent event) {
if (item == null || !item.isDirectory()) {
return;
}

View file

@ -39,6 +39,7 @@ final class FileListModel {
private final Property<FileSystem.FileEntry> draggedOverDirectory = new SimpleObjectProperty<FileSystem.FileEntry>();
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
private final Property<FileSystem.FileEntry> editing = new SimpleObjectProperty<>();
public FileListModel(OpenFileSystemModel fileSystemModel) {
this.fileSystemModel = fileSystemModel;

View file

@ -6,6 +6,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileSystem;
@ -52,8 +53,26 @@ final class OpenFileSystemModel {
cdSync(currentPath.get());
}
public FileSystem.FileEntry getCurrentParentDirectory() {
var current = getCurrentDirectory();
if (current == null) {
return null;
}
var parent = FileNames.getParent(currentPath.get());
if (parent == null) {
return null;
}
return new FileSystem.FileEntry(fileSystem, parent, null, true, false, false, 0);
}
public FileSystem.FileEntry getCurrentDirectory() {
return new FileSystem.FileEntry(fileSystem, currentPath.get(), Instant.now(), true, false, false, 0);
if (currentPath.get() == null) {
return null;
}
return new FileSystem.FileEntry(fileSystem, currentPath.get(), null, true, false, false, 0);
}
public Optional<String> cd(String path) {

View file

@ -43,8 +43,11 @@
}
.browser .table-directory-view .table-view:drag-into-current {
-fx-border-color: -color-success-muted;
-fx-border-width: 2px;
-fx-background-color: -color-success-muted;
}
.browser .table-directory-view .table-view:drag-into-current .table-row-cell {
-fx-opacity: 0.8;
}
.browser .table-row-cell:drag-over {

View file

@ -65,6 +65,14 @@ public class FileNames {
}
public static String getParent(String file) {
if (split(file).size() == 0) {
return null;
}
if (split(file).size() == 1) {
return file.startsWith("/") && !file.equals("/") ? "/" : null;
}
return file.substring(0, file.length() - getFileName(file).length() - 1);
}

View file

@ -21,7 +21,6 @@ public interface FileSystem extends Closeable, AutoCloseable {
FileSystem fileSystem;
@NonNull
String path;
@NonNull
Instant date;
boolean directory;
boolean hidden;
@ -29,7 +28,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
long size;
public FileEntry(
@NonNull FileSystem fileSystem, @NonNull String path, @NonNull Instant date, boolean directory, boolean hidden, Boolean executable,
@NonNull FileSystem fileSystem, @NonNull String path, Instant date, boolean directory, boolean hidden, Boolean executable,
long size
) {
this.fileSystem = fileSystem;

View file

@ -1 +1 @@
0.5.13
0.5.14