diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index e54899e8d..ea9bce6ca 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -94,7 +94,7 @@ public class BrowserNavBar extends Comp { homeButton.getStyleClass().add(Styles.LEFT_PILL); homeButton.getStyleClass().add("path-graphic-button"); new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, () -> { - return model.getInOverview().get() ? null : new BrowserContextMenu(model, null); + return model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false); }) .augment(new SimpleCompStructure<>(homeButton)); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java index 97be0db3f..a323b7fad 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -134,7 +134,7 @@ public class BrowserStatusBarComp extends SimpleComp { new ContextMenuAugment<>( mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, null, - () -> new BrowserContextMenu(model, null)) + () -> new BrowserContextMenu(model, null, false)) .augment(new SimpleCompStructure<>(r)); } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java index 9db73e323..f6df6d318 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java @@ -21,10 +21,12 @@ public final class BrowserContextMenu extends ContextMenu { private final OpenFileSystemModel model; private final BrowserEntry source; + private final boolean quickAccess; - public BrowserContextMenu(OpenFileSystemModel model, BrowserEntry source) { + public BrowserContextMenu(OpenFileSystemModel model, BrowserEntry source, boolean quickAccess) { this.model = model; this.source = source; + this.quickAccess = quickAccess; createMenu(); } @@ -50,7 +52,7 @@ public final class BrowserContextMenu extends ContextMenu { var selected = new ArrayList<>( empty ? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList())) - : model.getFileList().getSelection()); + : quickAccess ? List.of() : model.getFileList().getSelection()); if (source != null && !selected.contains(source)) { selected.add(source); } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 038db691a..7a7ef6d08 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -255,7 +255,7 @@ public final class BrowserFileListComp extends SimpleComp { }, null, () -> { - return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem()); + return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem(), false); }) .augment(new SimpleCompStructure<>(row)); var listEntry = Bindings.createObjectBinding( @@ -507,10 +507,17 @@ public final class BrowserFileListComp extends SimpleComp { return; } - fileList.rename(oldValue, newValue); - textField.getScene().getRoot().requestFocus(); + getTableRow().requestFocus(); + var it = getTableRow().getItem(); editing.setValue(null); - updateItem(getItem(), isEmpty()); + ThreadHelper.runAsync(() -> { + var r = fileList.rename(it, newValue); + Platform.runLater(() -> { + updateItem(getItem(), isEmpty()); + fileList.getSelection().setAll(r); + getTableView().scrollTo(r); + }); + }); }; text.addListener(listener); @@ -534,7 +541,7 @@ public final class BrowserFileListComp extends SimpleComp { var selected = fileList.getSelection(); // Only show one menu across all selected entries if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) { - var cm = new BrowserContextMenu(fileList.getFileSystemModel(), getTableRow().getItem()); + var cm = new BrowserContextMenu(fileList.getFileSystemModel(), getTableRow().getItem(), false); ContextMenuHelper.toggleShow(cm, this, Side.RIGHT); event.consume(); } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java index 8ff3074eb..33fc472af 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java @@ -96,8 +96,8 @@ public final class BrowserFileListModel { return us; } - public boolean rename(String filename, String newName) { - var fullPath = FileNames.join(fileSystemModel.getCurrentPath().get(), filename); + public BrowserEntry rename(BrowserEntry old, String newName) { + var fullPath = FileNames.join(fileSystemModel.getCurrentPath().get(), old.getFileName()); var newFullPath = FileNames.join(fileSystemModel.getCurrentPath().get(), newName); boolean exists; @@ -106,7 +106,7 @@ public final class BrowserFileListModel { || fileSystemModel.getFileSystem().directoryExists(newFullPath); } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); - return false; + return old; } if (exists) { @@ -114,16 +114,17 @@ public final class BrowserFileListModel { .expected() .handle(); fileSystemModel.refresh(); - return false; + return old; } try { fileSystemModel.getFileSystem().move(fullPath, newFullPath); fileSystemModel.refresh(); - return true; + var b = all.getValue().stream().filter(browserEntry -> browserEntry.getRawFileEntry().getPath().equals(newFullPath)).findFirst().orElse(old); + return b; } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); - return false; + return old; } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index 41260cdfb..1f45b0a1a 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -125,8 +125,12 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { private final BrowserEntry browserEntry; private final Menu menu; private ContextMenu browserActionMenu; + private final MenuItem empty; public QuickAccessMenu(BrowserEntry browserEntry) { + empty = new Menu("..."); + empty.getStyleClass().add("leaf"); + this.browserEntry = browserEntry; this.menu = new Menu( // Use original name, not the link target @@ -165,18 +169,13 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { } }); menu.getStyleClass().add("leaf"); - - var empty = new MenuItem("..."); - empty.setDisable(true); menu.getItems().add(empty); } private void createDirectoryMenu() { menu.setMnemonicParsing(false); - var empty = new MenuItem("..."); - empty.setDisable(true); menu.getItems().add(empty); - addHoverHandling(empty); + addHoverHandling(); menu.setOnAction(event -> { if (event.getTarget() != menu) { @@ -216,7 +215,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { }); } - private void addHoverHandling(MenuItem empty) { + private void addHoverHandling() { var hover = new SimpleBooleanProperty(); menu.addEventFilter(Menu.ON_SHOWING, event -> { if (!keyBasedNavigation) { @@ -239,7 +238,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { if (contextMenu != null) { contextMenu.addEventFilter(KeyEvent.KEY_PRESSED, event -> { keyBasedNavigation = true; - if (event.getCode().equals(KeyCode.ENTER)) { + if (event.getCode().equals(KeyCode.SPACE) || event.getCode().equals(KeyCode.ENTER)) { expandBrowserActionMenuKey = true; } else { expandBrowserActionMenuKey = false; @@ -290,7 +289,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { private void showBrowserActionsMenu() { if (browserActionMenu == null) { - this.browserActionMenu = new BrowserContextMenu(model, browserEntry); + this.browserActionMenu = new BrowserContextMenu(model, browserEntry, true); this.browserActionMenu.setOnAction(e -> { hide(); }); diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java index 22cadb094..9fe6c5654 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java @@ -17,7 +17,6 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment; import io.xpipe.app.fxcomps.impl.TooltipAugment; import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.util.InputHelper; -import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.MenuButton; @@ -73,7 +72,7 @@ public class OpenFileSystemComp extends SimpleComp { new ContextMenuAugment<>( event -> event.getButton() == MouseButton.PRIMARY, null, - () -> new BrowserContextMenu(model, null)) + () -> new BrowserContextMenu(model, null, false)) .augment(new SimpleCompStructure<>(menuButton)); menuButton.disableProperty().bind(model.getInOverview()); menuButton.setAccessibleText("Directory options"); @@ -100,7 +99,12 @@ public class OpenFileSystemComp extends SimpleComp { var content = createFileListContent(); root.getChildren().addAll(topBar, content); VBox.setVgrow(content, Priority.ALWAYS); - root.setPadding(Insets.EMPTY); + root.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + content.requestFocus(); + } + }); + InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN), true, keyEvent -> { filter.toggleButton().fire(); filter.textField().requestFocus(); @@ -133,7 +137,13 @@ public class OpenFileSystemComp extends SimpleComp { var statusBar = new BrowserStatusBarComp(model); fileListElements.add(statusBar); } - var fileList = new VerticalComp(fileListElements); + var fileList = new VerticalComp(fileListElements).apply(struc -> { + struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + struc.get().getChildren().getFirst().requestFocus(); + } + }); + }); var home = new BrowserOverviewComp(model); var stack = new MultiContentComp(Map.of( @@ -141,6 +151,16 @@ public class OpenFileSystemComp extends SimpleComp { model.getCurrentPath().isNull(), fileList, model.getCurrentPath().isNull().not())); - return stack.createRegion(); + var r = stack.createRegion(); + r.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + if (r.getChildrenUnmodifiable().get(0).isVisible()) { + r.getChildrenUnmodifiable().getFirst().requestFocus(); + } else { + r.getChildrenUnmodifiable().get(1).requestFocus(); + } + } + }); + return r; } } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java index 007f27086..073ba0f02 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java @@ -122,6 +122,9 @@ public class BrowserSessionTabsComp extends SimpleComp { } tabs.getSelectionModel().select(toSelect); + Platform.runLater(() -> { + toSelect.getContent().requestFocus(); + }); }); }); diff --git a/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java index 39ad1acdf..8aa7d85cf 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java @@ -38,6 +38,16 @@ public class ModalOverlayComp extends SimpleComp { modal.getStyleClass().add("modal-overlay-comp"); var pane = new StackPane(bgRegion, modal); pane.setPickOnBounds(false); + pane.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + if (modal.isDisplay()) { + modal.requestFocus(); + } else { + bgRegion.requestFocus(); + } + } + }); + PlatformThread.sync(overlayContent).addListener((observable, oldValue, newValue) -> { if (oldValue != null && newValue == null && modal.isDisplay()) { modal.hide(true); diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index 575dcab84..16993e302 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -1,3 +1,10 @@ +/* For development +*:focused { + -fx-border-width: 1; + -fx-border-color: red; +} +*/ + .store-layout .split-pane-divider { -fx-background-color: transparent; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java index 4c8e950b0..b74cde281 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java @@ -68,6 +68,10 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, dir = initScriptsDirectory(shellControl, bringFlattened); } + if (dir == null) { + return Optional.empty(); + } + return Optional.ofNullable(shellControl.getShellDialect().addToPathVariableCommand(List.of(dir), true)); }