From 447ec1202319a05b4f9ad278afeb2766d34a1e93 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 5 Jul 2023 04:17:29 +0000 Subject: [PATCH] Various fixes [stage] --- app/build.gradle | 2 +- app/gradle_scripts/fxtrayicon.gradle | 24 ---------- .../app/browser/BrowserFileListComp.java | 23 ++++++++- .../app/browser/BrowserFileListCompEntry.java | 48 +++++++++++-------- .../app/browser/BrowserStatusBarComp.java | 2 +- .../java/io/xpipe/app/prefs/AppPrefs.java | 4 +- .../xpipe/app/prefs/CustomFormRenderer.java | 4 +- .../io/xpipe/core/process/ShellDialect.java | 4 ++ dist/changelogs/1.3.0.md | 2 + .../browser/OpenDirectoryInNewTabAction.java | 8 ---- .../browser/OpenNativeFileDetailsAction.java | 14 ++++-- gradle/gradle_scripts/javafx.gradle | 1 + 12 files changed, 73 insertions(+), 63 deletions(-) delete mode 100644 app/gradle_scripts/fxtrayicon.gradle diff --git a/app/build.gradle b/app/build.gradle index c6fc4b25..9388da33 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,6 @@ apply from: "$projectDir/gradle_scripts/richtextfx.gradle" apply from: "$rootDir/gradle/gradle_scripts/commons.gradle" apply from: "$rootDir/gradle/gradle_scripts/prettytime.gradle" apply from: "$projectDir/gradle_scripts/sentry.gradle" -apply from: "$projectDir/gradle_scripts/fxtrayicon.gradle" apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle" apply from: "$projectDir/gradle_scripts/github-api.gradle" apply from: "$projectDir/gradle_scripts/flexmark.gradle" @@ -39,6 +38,7 @@ dependencies { compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.3' compileOnly 'org.junit.jupiter:junit-jupiter-params:5.9.3' + implementation 'com.dustinredmond.fxtrayicon:FXTrayIcon:4.0.1' implementation 'net.java.dev.jna:jna-jpms:5.13.0' implementation 'net.java.dev.jna:jna-platform-jpms:5.13.0' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.15.2" diff --git a/app/gradle_scripts/fxtrayicon.gradle b/app/gradle_scripts/fxtrayicon.gradle deleted file mode 100644 index 605b7707..00000000 --- a/app/gradle_scripts/fxtrayicon.gradle +++ /dev/null @@ -1,24 +0,0 @@ -dependencies { - implementation files("$buildDir/generated-modules/FXTrayIcon-3.1.2.jar") -} - -addDependenciesModuleInfo { - overwriteExistingFiles = true - jdepsExtraArgs = ['-q'] - outputDirectory = file("$buildDir/generated-modules") - modules { - module { - artifact 'com.dustinredmond.fxtrayicon:FXTrayIcon:3.1.2' - moduleInfoSource = ''' - module com.dustinredmond.fxtrayicon { - exports com.dustinredmond.fxtrayicon; - exports com.dustinredmond.fxtrayicon.annotations; - - requires transitive javafx.controls; - requires transitive javafx.base; - requires transitive java.desktop; - } - ''' - } - } -} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java index c482cc20..827f0c13 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java @@ -32,6 +32,7 @@ import javafx.scene.control.skin.TableViewSkin; import javafx.scene.control.skin.VirtualFlow; import javafx.scene.input.DragEvent; import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; @@ -127,6 +128,8 @@ final class BrowserFileListComp extends SimpleComp { table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); } + table.getSelectionModel().setCellSelectionEnabled(false); + table.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { var toSelect = new ArrayList<>(c.getList()); // Explicitly unselect synthetic entries since we can't use a custom selection model as that is bugged in @@ -192,7 +195,7 @@ final class BrowserFileListComp extends SimpleComp { } private void prepareTableEntries(TableView table) { - var emptyEntry = new BrowserFileListCompEntry(table, null, fileList); + var emptyEntry = new BrowserFileListCompEntry(table, table, null, fileList); table.setOnMouseClicked(e -> { emptyEntry.onMouseClick(e); }); @@ -215,6 +218,14 @@ final class BrowserFileListComp extends SimpleComp { emptyEntry.onDragDrop(event); }); + // Don't let the list view see this event + // otherwise it unselects everything as it doesn't understand shift clicks + table.addEventFilter(MouseEvent.MOUSE_CLICKED, t -> { + if (t.getButton() == MouseButton.PRIMARY && t.isShiftDown() && t.getClickCount() == 1) { + t.consume(); + } + }); + table.setRowFactory(param -> { TableRow row = new TableRow<>(); row.accessibleTextProperty() @@ -257,7 +268,15 @@ final class BrowserFileListComp extends SimpleComp { }) .augment(new SimpleCompStructure<>(row)); var listEntry = Bindings.createObjectBinding( - () -> new BrowserFileListCompEntry(row, row.getItem(), fileList), row.itemProperty()); + () -> new BrowserFileListCompEntry(table, row, row.getItem(), fileList), row.itemProperty()); + + // Don't let the list view see this event + // otherwise it unselects everything as it doesn't understand shift clicks + row.addEventFilter(MouseEvent.MOUSE_PRESSED, t -> { + if (t.getButton() == MouseButton.PRIMARY && t.isShiftDown()) { + listEntry.get().onMouseShiftClick(t); + } + }); row.itemProperty().addListener((observable, oldValue, newValue) -> { row.pseudoClassStateChanged(DRAG, false); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java index d99153af..3d9c09ee 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java @@ -9,6 +9,7 @@ import javafx.scene.input.*; import lombok.Getter; import java.io.File; +import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; @@ -17,6 +18,7 @@ public class BrowserFileListCompEntry { public static final Timer DROP_TIMER = new Timer("dnd", true); + private final TableView tv; private final Node row; private final BrowserEntry item; private final BrowserFileListModel model; @@ -24,46 +26,52 @@ public class BrowserFileListCompEntry { private Point2D lastOver = new Point2D(-1, -1); private TimerTask activeTask; - public BrowserFileListCompEntry(Node row, BrowserEntry item, BrowserFileListModel model) { + public BrowserFileListCompEntry(TableView tv, Node row, BrowserEntry item, BrowserFileListModel model) { + this.tv = tv; this.row = row; this.item = item; this.model = model; } - @SuppressWarnings("unchecked") public void onMouseClick(MouseEvent t) { - t.consume(); - if (item == null) { model.getSelection().clear(); + t.consume(); return; } if (t.getClickCount() == 2 && t.getButton() == MouseButton.PRIMARY) { model.onDoubleClick(item); - return; + t.consume(); } + t.consume(); + } + + public void onMouseShiftClick(MouseEvent t) { if (isSynthetic()) { return; } - if (t.getButton() == MouseButton.PRIMARY && t.isShiftDown()) { - var tv = ((TableView) - row.getParent().getParent().getParent().getParent()); - var all = tv.getItems(); - var min = tv.getSelectionModel().getSelectedIndices().stream() - .mapToInt(value -> value) - .min() - .orElse(1); - var max = tv.getSelectionModel().getSelectedIndices().stream() - .mapToInt(value -> value) - .max() - .orElse(all.size() - 1); - var end = tv.getSelectionModel().getFocusedIndex(); - var start = end > min ? min : max; - tv.getSelectionModel().selectRange(Math.min(start, end), Math.max(start, end) + 1); + var all = tv.getItems(); + var index = item != null ? all.indexOf(item) : all.size() - 1; + var min = Math.min(index, tv.getSelectionModel().getSelectedIndices().stream() + .mapToInt(value -> value) + .min() + .orElse(1)); + var max = Math.max(index, tv.getSelectionModel().getSelectedIndices().stream() + .mapToInt(value -> value) + .max() + .orElse(all.indexOf(item))); + + var toSelect = new ArrayList(); + for (int i = min; i <= max; i++) { + if (!model.getSelection().contains(model.getShown().getValue().get(i))) { + toSelect.add(model.getShown().getValue().get(i)); + } } + model.getSelection().addAll(toSelect); + t.consume(); } public boolean isSynthetic() { 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 430499ed..ffc6807b 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -73,7 +73,7 @@ public class BrowserStatusBarComp extends SimpleComp { } private void simulateEmptyCell(Region r) { - var emptyEntry = new BrowserFileListCompEntry(r, null, model.getFileList()); + var emptyEntry = new BrowserFileListCompEntry(null, r, null, model.getFileList()); r.setOnMouseClicked(e -> { emptyEntry.onMouseClick(e); }); diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 6309ff4f..fb904559 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -513,7 +513,6 @@ public class AppPrefs { var categories = new ArrayList<>(List.of( Category.of("about", Group.of(about)), - Category.of("troubleshoot", Group.of(troubleshoot)), Category.of( "system", Group.of( @@ -586,7 +585,8 @@ public class AppPrefs { Setting.of( "developerShowHiddenProviders", developerShowHiddenProvidersField, - developerShowHiddenProviders)))); + developerShowHiddenProviders)), + Category.of("troubleshoot", Group.of(troubleshoot)))); categories.get(categories.size() - 1).setVisibilityProperty(VisibilityProperty.of(developerMode())); diff --git a/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java index 9a4391ab..5c304b8b 100644 --- a/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java +++ b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java @@ -52,7 +52,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { AppFont.setSize(titleLabel, 2); // Set margin for all but first group titles to visually separate groups if (nextRow > 1) { - GridPane.setMargin(titleLabel, new Insets(SPACING * 3, 0, SPACING, 0)); + GridPane.setMargin(titleLabel, new Insets(SPACING * 5, 0, SPACING, 0)); } else { GridPane.setMargin(titleLabel, new Insets(SPACING, 0, SPACING, 0)); } @@ -124,7 +124,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { GridPane.setMargin(node, new Insets(SPACING, 0, 0, offset)); if (!((i == 0) && (nextRow > 0))) { - GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING * 3, 0, 0, offset)); + GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING * 6, 0, 0, offset)); } else { GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING, 0, 0, offset)); } diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java index d374cc13..77d7e0e3 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java @@ -25,6 +25,10 @@ public interface ShellDialect { .collect(Collectors.joining(" ")); } + default boolean isSupported() { + return true; + } + CommandControl queryVersion(ShellControl shellControl); CommandControl prepareTempDirectory(ShellControl shellControl, String directory); diff --git a/dist/changelogs/1.3.0.md b/dist/changelogs/1.3.0.md index 0657c7e1..74d27f73 100644 --- a/dist/changelogs/1.3.0.md +++ b/dist/changelogs/1.3.0.md @@ -2,3 +2,5 @@ - Completely rework connection management (Note that this change might remove some old connections that we're not compatible with the new system.) +- Add shift-click selection to file browser +- Many small miscellaneous fixes and improvements diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java index 0eacfb31..71356e67 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java @@ -5,9 +5,6 @@ import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.action.LeafAction; import io.xpipe.core.store.FileKind; import javafx.scene.Node; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; @@ -45,11 +42,6 @@ public class OpenDirectoryInNewTabAction implements LeafAction { return true; } - @Override - public KeyCombination getShortcut() { - return new KeyCodeCombination(KeyCode.ENTER); - } - @Override public String getName(OpenFileSystemModel model, List entries) { return "Open in new tab"; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java index 339a9b1f..a5623468 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java @@ -4,6 +4,7 @@ import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.action.LeafAction; import io.xpipe.core.impl.FileNames; +import io.xpipe.core.impl.LocalStore; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; @@ -12,6 +13,8 @@ import java.util.List; public class OpenNativeFileDetailsAction implements LeafAction { + private static ShellControl powershell; + @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { ShellControl sc = model.getFileSystem().getShell().get(); @@ -24,9 +27,14 @@ public class OpenNativeFileDetailsAction implements LeafAction { $shell = New-Object -ComObject Shell.Application; $shell.NameSpace('%s').ParseName('%s').InvokeVerb('Properties') """, FileNames.getParent(e), FileNames.getFileName(e)); - try (var sub = sc.enforcedDialect(ShellDialects.POWERSHELL).start()) { - sub.command(content).notComplex().execute(); + + // The Windows shell invoke verb functionality behaves kinda weirdly and only shows the window as long as the parent process is running. + // So let's keep one process running + if (powershell == null) { + powershell = new LocalStore().control().subShell(ShellDialects.POWERSHELL).start(); } + + powershell.command(content).notComplex().execute(); } case OsType.Linux linux -> { var dbus = String.format( @@ -62,7 +70,7 @@ public class OpenNativeFileDetailsAction implements LeafAction { @Override public boolean isApplicable(OpenFileSystemModel model, List entries) { var sc = model.getFileSystem().getShell(); - return model.isLocal() && !sc.get().getOsType().equals(OsType.WINDOWS); + return model.isLocal(); } @Override diff --git a/gradle/gradle_scripts/javafx.gradle b/gradle/gradle_scripts/javafx.gradle index 119e1ead..94eace86 100644 --- a/gradle/gradle_scripts/javafx.gradle +++ b/gradle/gradle_scripts/javafx.gradle @@ -24,4 +24,5 @@ dependencies { dep "org.openjfx:javafx-graphics:20.0.1:${platform}" dep "org.openjfx:javafx-media:20.0.1:${platform}" dep "org.openjfx:javafx-web:20.0.1:${platform}" + dep "org.openjfx:javafx-swing:20.0.1:${platform}" }