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 b05be15e..b2e894b3 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListComp.java @@ -17,10 +17,14 @@ import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; +import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; +import javafx.scene.control.skin.TableViewSkin; +import javafx.scene.control.skin.VirtualFlow; +import javafx.scene.input.DragEvent; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; @@ -29,6 +33,7 @@ import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; import java.util.Comparator; +import java.util.Objects; import static io.xpipe.app.util.HumanReadableFormat.byteCount; import static javafx.scene.control.TableColumn.SortType.ASCENDING; @@ -125,7 +130,7 @@ final class FileListComp extends AnchorPane { } }); - var emptyEntry = new FileListEntry(table, null, fileList); + var emptyEntry = new FileListCompEntry(table, null, fileList); table.setOnDragOver(event -> { emptyEntry.onDragOver(event); }); @@ -145,7 +150,7 @@ final class FileListComp extends AnchorPane { table.setRowFactory(param -> { TableRow row = new TableRow<>(); var listEntry = Bindings.createObjectBinding( - () -> new FileListEntry(row, row.getItem(), fileList), row.itemProperty()); + () -> new FileListCompEntry(row, row.getItem(), fileList), row.itemProperty()); row.selectedProperty().addListener((observable, oldValue, newValue) -> { if (newValue && listEntry.get().isSynthetic()) { @@ -180,6 +185,7 @@ final class FileListComp extends AnchorPane { listEntry.get().onDragEntered(event); }); row.setOnDragOver(event -> { + borderScroll(table, event); listEntry.get().onDragOver(event); }); row.setOnDragDetected(event -> { @@ -195,6 +201,7 @@ final class FileListComp extends AnchorPane { return row; }); + var lastDir = new SimpleObjectProperty(); SimpleChangeListener.apply(fileList.getShown(), (newValue) -> { PlatformThread.runLaterIfNeeded(() -> { var newItems = new ArrayList(); @@ -204,16 +211,36 @@ final class FileListComp extends AnchorPane { } newItems.addAll(newValue); table.getItems().setAll(newItems); -// if (newValue.size() > 0) { -// table.scrollTo(0); -// } + + var currentDirectory = fileList.getFileSystemModel().getCurrentDirectory(); + if (!Objects.equals(lastDir.get(), currentDirectory)) { + table.scrollTo(0); + } + lastDir.setValue(currentDirectory); }); }); return table; } - /////////////////////////////////////////////////////////////////////////// + private void borderScroll(TableView tableView, DragEvent event) { + TableViewSkin skin = (TableViewSkin) tableView.getSkin(); + VirtualFlow flow = (VirtualFlow) skin.getChildren().get(1); + ScrollBar vbar = (ScrollBar) flow.getChildrenUnmodifiable().get(2); + + double proximity = 100; + Bounds tableBounds = tableView.localToScene(tableView.getBoundsInParent()); + double dragY = event.getSceneY(); + double topYProximity = tableBounds.getMinY() + proximity; + double bottomYProximity = tableBounds.getMaxY() - proximity; + if (dragY < topYProximity) { + var scrollValue = Math.min(topYProximity - dragY, 100) / 10000.0; + vbar.setValue(vbar.getValue() - scrollValue); + } else if (dragY > bottomYProximity) { + var scrollValue = Math.min(dragY - bottomYProximity, 100) / 10000.0; + vbar.setValue(vbar.getValue() + scrollValue); + } + } private class FilenameCell extends TableCell { @@ -265,7 +292,11 @@ final class FileListComp extends AnchorPane { pseudoClassStateChanged(FOLDER, isDirectory); - var fileName = getTableRow().getItem().equals(fileList.getFileSystemModel().getCurrentParentDirectory()) ? ".." : 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); diff --git a/app/src/main/java/io/xpipe/app/browser/FileListEntry.java b/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java similarity index 95% rename from app/src/main/java/io/xpipe/app/browser/FileListEntry.java rename to app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java index b9f9d47d..3ef41184 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java @@ -13,7 +13,7 @@ import java.util.Timer; import java.util.TimerTask; @Getter -public class FileListEntry { +public class FileListCompEntry { public static final Timer DROP_TIMER = new Timer("dnd", true); @@ -24,7 +24,7 @@ public class FileListEntry { private Point2D lastOver = new Point2D(-1, -1); private TimerTask activeTask; - public FileListEntry(Node row, FileSystem.FileEntry item, FileListModel model) { + public FileListCompEntry(Node row, FileSystem.FileEntry item, FileListModel model) { this.row = row; this.item = item; this.model = model; @@ -47,6 +47,11 @@ public class FileListEntry { } private boolean acceptsDrop(DragEvent event) { + // Accept drops from outside the app window + if (event.getGestureSource() == null) { + return true; + } + if (FileBrowserClipboard.currentDragClipboard == null) { return false; } diff --git a/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java b/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java index 2a0f7657..9c6b09d9 100644 --- a/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java @@ -7,11 +7,9 @@ import io.xpipe.app.core.mode.OperationMode; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.UserReportComp; -import io.xpipe.app.util.DesktopHelper; -import io.xpipe.app.util.DynamicOptionsBuilder; -import io.xpipe.app.util.FileOpener; -import io.xpipe.app.util.ScriptHelper; +import io.xpipe.app.util.*; import io.xpipe.core.impl.FileNames; +import io.xpipe.core.process.OsType; import io.xpipe.core.store.ShellStore; import io.xpipe.core.util.XPipeInstallation; import javafx.scene.layout.Region; @@ -35,9 +33,9 @@ public class BrowseDirectoryComp extends SimpleComp { "logFile", new ButtonComp(AppI18n.observable("openCurrentLogFile"), () -> { FileOpener.openInTextEditor(AppLogs.get() - .getSessionLogsDirectory() - .resolve("xpipe.log") - .toString()); + .getSessionLogsDirectory() + .resolve("xpipe.log") + .toString()); }), null) .addComp( @@ -45,8 +43,15 @@ public class BrowseDirectoryComp extends SimpleComp { new ButtonComp(AppI18n.observable("launchDebugMode"), () -> { OperationMode.executeAfterShutdown(() -> { try (var sc = ShellStore.createLocal().create().start()) { - var script = FileNames.join(XPipeInstallation.getCurrentInstallationBasePath().toString(), XPipeInstallation.getDaemonDebugScriptPath(sc.getOsType())); - sc.executeSimpleCommand(ScriptHelper.createDetachCommand(sc, script)); + var script = FileNames.join( + XPipeInstallation.getCurrentInstallationBasePath() + .toString(), + XPipeInstallation.getDaemonDebugScriptPath(sc.getOsType())); + if (sc.getOsType().equals(OsType.WINDOWS)) { + sc.executeSimpleCommand(ScriptHelper.createDetachCommand(sc, "\"" + script + "\"")); + } else { + TerminalHelper.open("X-Pipe Debug", "\"" + script + "\""); + } } }); DesktopHelper.browsePath(AppLogs.get().getSessionLogsDirectory()); diff --git a/app/src/main/java/io/xpipe/app/test/TestModule.java b/app/src/main/java/io/xpipe/app/test/TestModule.java index 3e83150f..e408ed0c 100644 --- a/app/src/main/java/io/xpipe/app/test/TestModule.java +++ b/app/src/main/java/io/xpipe/app/test/TestModule.java @@ -9,7 +9,7 @@ import java.util.stream.Stream; public abstract class TestModule { - private static final Map, Map> values = new HashMap<>(); + private static final Map, Map> values = new LinkedHashMap<>(); @SuppressWarnings({"unchecked"}) public static Map get(Class c, String... classes) { diff --git a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java index f15f44c4..8207e14c 100644 --- a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java @@ -71,7 +71,7 @@ public class ConnectionFileSystem implements FileSystem { @Override public boolean exists(String file) throws Exception { try (var pc = shellControl.command(proc -> proc.getShellDialect() - .getFileExistsCommand(proc.getOsType().normalizeFileName(file))) + .getFileExistsCommand(proc.getOsType().normalizeFileName(file))).complex() .start()) { return pc.discardAndCheckExit(); } @@ -80,7 +80,7 @@ public class ConnectionFileSystem implements FileSystem { @Override public void delete(String file) throws Exception { try (var pc = shellControl.command(proc -> proc.getShellDialect() - .getFileDeleteCommand(proc.getOsType().normalizeFileName(file))) + .getFileDeleteCommand(proc.getOsType().normalizeFileName(file))).complex() .start()) { pc.discardOrThrow(); } @@ -107,7 +107,7 @@ public class ConnectionFileSystem implements FileSystem { @Override public boolean mkdirs(String file) throws Exception { try (var pc = shellControl.command(proc -> proc.getShellDialect() - .getMkdirsCommand(proc.getOsType().normalizeFileName(file))) + .getMkdirsCommand(proc.getOsType().normalizeFileName(file))).complex() .start()) { return pc.discardAndCheckExit(); } diff --git a/version b/version index ab245e97..a6db491d 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.15 \ No newline at end of file +0.5.16 \ No newline at end of file