From d5e024c43e0ce8bc98569fb8d317e1011af59297 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 15 Mar 2023 17:33:07 +0000 Subject: [PATCH] More file browser improvements [release] --- .../io/xpipe/app/browser/FileListComp.java | 90 +++++++++---------- .../io/xpipe/app/browser/FileListEntry.java | 26 ++++-- .../io/xpipe/app/browser/FileListModel.java | 1 + .../app/browser/OpenFileSystemModel.java | 21 ++++- .../io/xpipe/app/resources/style/browser.css | 7 +- .../java/io/xpipe/core/impl/FileNames.java | 8 ++ .../java/io/xpipe/core/store/FileSystem.java | 3 +- version | 2 +- 8 files changed, 101 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/browser/FileListComp.java b/app/src/main/java/io/xpipe/app/browser/FileListComp.java index f9f6585a..aa77e162 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListComp.java @@ -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 createTable() { - var editing = new SimpleObjectProperty(); var filenameCol = new TableColumn("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("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) c -> { - fileList.getSelected().setAll(c.getList()); - fileList.getFileSystemModel().getBrowserModel().getSelectedFiles().setAll(c.getList()); - }); + table.getSelectionModel().getSelectedItems().addListener((ListChangeListener) + 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 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(); + 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 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 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); + : ""); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/FileListEntry.java b/app/src/main/java/io/xpipe/app/browser/FileListEntry.java index cc47740c..b9f9d47d 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListEntry.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/browser/FileListModel.java b/app/src/main/java/io/xpipe/app/browser/FileListModel.java index 2c0ec4d0..2aa1a8c1 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListModel.java @@ -39,6 +39,7 @@ final class FileListModel { private final Property draggedOverDirectory = new SimpleObjectProperty(); private final Property draggedOverEmpty = new SimpleBooleanProperty(); + private final Property editing = new SimpleObjectProperty<>(); public FileListModel(OpenFileSystemModel fileSystemModel) { this.fileSystemModel = fileSystemModel; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index b848462b..1f44c499 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -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 cd(String path) { diff --git a/app/src/main/resources/io/xpipe/app/resources/style/browser.css b/app/src/main/resources/io/xpipe/app/resources/style/browser.css index 95b7e6ab..cc6055af 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/browser.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/browser.css @@ -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 { diff --git a/core/src/main/java/io/xpipe/core/impl/FileNames.java b/core/src/main/java/io/xpipe/core/impl/FileNames.java index 97aff146..82aa2d87 100644 --- a/core/src/main/java/io/xpipe/core/impl/FileNames.java +++ b/core/src/main/java/io/xpipe/core/impl/FileNames.java @@ -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); } diff --git a/core/src/main/java/io/xpipe/core/store/FileSystem.java b/core/src/main/java/io/xpipe/core/store/FileSystem.java index 8231eb41..fb784589 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -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; diff --git a/version b/version index 7988f134..63df0754 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.13 \ No newline at end of file +0.5.14 \ No newline at end of file