diff --git a/app/build.gradle b/app/build.gradle index 481661aa..0170f75c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,7 @@ dependencies { implementation group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0" implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0" implementation group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: "12.2.0" - implementation group: 'com.dlsc.preferencesfx', name: 'preferencesfx-core', version: '11.15.0' + implementation name: 'preferencesfx-core-11.15.0' implementation group: 'com.dlsc.formsfx', name: 'formsfx-core', version: '11.6.0' implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.0' implementation 'io.xpipe:modulefs:0.1.4' @@ -92,6 +92,7 @@ List jvmRunArgs = [ "--add-opens", "java.base/java.lang=io.xpipe.core", "--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app", "--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.extension", + "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app', "-Xmx8g", "--enable-preview", // "-XX:+ExitOnOutOfMemoryError", diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/DataStoreSelectorComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/DataStoreSelectorComp.java index 1a341738..219d6e6c 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/DataStoreSelectorComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/DataStoreSelectorComp.java @@ -22,7 +22,7 @@ import lombok.experimental.FieldDefaults; @AllArgsConstructor public class DataStoreSelectorComp extends Comp> { - DataStoreProvider.Category category; + DataStoreProvider.DataCategory category; Property chosenStore; @Override @@ -30,7 +30,7 @@ public class DataStoreSelectorComp extends Comp> { var button = new JFXButton(); button.setGraphic(getGraphic()); button.setOnAction(e -> { - GuiDsStoreCreator.show("inProgress", null, null, category, entry -> { + GuiDsStoreCreator.show("inProgress", null, null, v -> v.getCategory().equals(category), entry -> { chosenStore.setValue(entry.getStore()); }); e.consume(); diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/DsDbStoreChooserComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/DsDbStoreChooserComp.java index 27b61545..f115a42c 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/DsDbStoreChooserComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/DsDbStoreChooserComp.java @@ -32,7 +32,7 @@ public class DsDbStoreChooserComp extends SimpleComp { var filter = Bindings.createObjectBinding( () -> (Predicate) e -> { if (provider.getValue() == null) { - return e.getProvider().getCategory() == DataStoreProvider.Category.DATABASE; + return e.getProvider().getCategory() == DataStoreProvider.DataCategory.DATABASE; } return provider.getValue().couldSupportStore(e.getStore()); @@ -42,7 +42,7 @@ public class DsDbStoreChooserComp extends SimpleComp { var connections = new TabPaneComp.Entry( I18n.observable("savedConnections"), "mdi2m-monitor", - NamedStoreChoiceComp.create(filter, input, DataStoreProvider.Category.DATABASE) + NamedStoreChoiceComp.create(filter, input, DataStoreProvider.DataCategory.DATABASE) .styleClass("store-local-file-chooser")); var pane = new TabPaneComp(new SimpleObjectProperty<>(connections), List.of(connections)); diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java index 960372df..6fa188d6 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java @@ -18,28 +18,23 @@ import lombok.AllArgsConstructor; import lombok.experimental.FieldDefaults; import java.util.List; +import java.util.function.Predicate; @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @AllArgsConstructor public class DsStoreProviderChoiceComp extends Comp>> { - DataStoreProvider.Category type; + Predicate filter; Property provider; private Region createDefaultNode() { - return switch (type) { - case STREAM -> JfxHelper.createNamedEntry( - I18n.get("selectStreamType"), I18n.get("selectStreamTypeDescription"), "file_icon.png"); - case SHELL -> JfxHelper.createNamedEntry( - I18n.get("selectShellType"), I18n.get("selectShellTypeDescription"), "machine_icon.png"); - case DATABASE -> JfxHelper.createNamedEntry( - I18n.get("selectDatabaseType"), I18n.get("selectDatabaseTypeDescription"), "db_icon.png"); - }; + return JfxHelper.createNamedEntry( + I18n.get("selectType"), I18n.get("selectTypeDescription"), "machine_icon.png"); } private List getProviders() { return DataStoreProviders.getAll().stream() - .filter(p -> p.getCategory() == type) + .filter(filter) .toList(); } diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/DsStreamStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/DsStreamStoreChoiceComp.java index 3c94ac28..9b710e16 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/DsStreamStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/DsStreamStoreChoiceComp.java @@ -114,14 +114,14 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable { var named = new TabPaneComp.Entry( I18n.observable("stored"), "mdrmz-storage", - NamedStoreChoiceComp.create(filter, namedStore, DataStoreProvider.Category.STREAM)); + NamedStoreChoiceComp.create(filter, namedStore, DataStoreProvider.DataCategory.STREAM)); var otherStore = new SimpleObjectProperty( localStore.get() == null && remoteStore.get() == null && !isNamedStore ? selected.getValue() : null); var other = new TabPaneComp.Entry( I18n.observable("other"), "mdrmz-web_asset", - new DataStoreSelectorComp(DataStoreProvider.Category.STREAM, otherStore)); + new DataStoreSelectorComp(DataStoreProvider.DataCategory.STREAM, otherStore)); var selectedTab = new SimpleObjectProperty(); if (localStore.get() != null) { diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java index dc2debcf..aa15698b 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java @@ -37,6 +37,7 @@ import lombok.experimental.FieldDefaults; import java.util.List; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Predicate; @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class GuiDsStoreCreator extends MultiStepComp.Step> { @@ -44,7 +45,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { MultiStepComp parent; Property provider; Property input; - DataStoreProvider.Category generalType; + Predicate filter; BooleanProperty busy = new SimpleBooleanProperty(); Property validator = new SimpleObjectProperty<>(new SimpleValidator()); Property messageProp = new SimpleStringProperty(); @@ -58,13 +59,13 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { MultiStepComp parent, Property provider, Property input, - DataStoreProvider.Category generalType, + Predicate filter, String initialName) { super(null); this.parent = parent; this.provider = provider; this.input = input; - this.generalType = generalType; + this.filter = filter; this.name = new SimpleStringProperty(initialName); this.input.addListener((c, o, n) -> { changedSinceError.setValue(true); @@ -88,7 +89,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { } public static void showEdit(DataStoreEntry e) { - show(e.getName(), e.getProvider(), e.getStore(), e.getProvider().getCategory(), newE -> { + show(e.getName(), e.getProvider(), e.getStore(), v -> true, newE -> { ThreadHelper.runAsync(() -> { e.applyChanges(newE); if (!DataStorage.get().getStores().contains(e)) { @@ -99,9 +100,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { }); } - public static void showCreation(DataStoreProvider.Category cat) { - - show(null, null, null, cat, e -> { + public static void showCreation(Predicate filter) { + show(null, null, null, filter, e -> { try { DataStorage.get().addStore(e); } catch (Exception ex) { @@ -114,13 +114,11 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { String initialName, DataStoreProvider provider, DataStore s, - DataStoreProvider.Category cat, + Predicate filter, Consumer con) { var prop = new SimpleObjectProperty(provider); var store = new SimpleObjectProperty(s); - var name = cat == DataStoreProvider.Category.SHELL - ? "addShellTitle" - : cat == DataStoreProvider.Category.DATABASE ? "addDatabaseTitle" : "addStreamTitle"; + var name = "addConnection"; Platform.runLater(() -> { var stage = AppWindowHelper.sideWindow( I18n.get(name), @@ -128,7 +126,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { return new MultiStepComp() { private final GuiDsStoreCreator creator = - new GuiDsStoreCreator(this, prop, store, cat, initialName); + new GuiDsStoreCreator(this, prop, store, filter, initialName); @Override protected List setup() { @@ -182,7 +180,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { @Override public CompStructure createBase() { var layout = new BorderPane(); - var providerChoice = new DsStoreProviderChoiceComp(generalType, provider); + var providerChoice = new DsStoreProviderChoiceComp(filter, provider); providerChoice.apply(GrowAugment.create(true, false)); SimpleChangeListener.apply(provider, n -> { diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java index 77c8d2d4..692786f1 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java @@ -41,7 +41,7 @@ import java.util.function.Predicate; public class NamedStoreChoiceComp extends SimpleComp implements Validatable { private final ObservableValue> filter; - private final DataStoreProvider.Category category; + private final DataStoreProvider.DataCategory category; private final Property selected; private final StringProperty filterString = new SimpleStringProperty(); @@ -53,7 +53,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable { public NamedStoreChoiceComp( ObservableValue> filter, Property selected, - DataStoreProvider.Category category) { + DataStoreProvider.DataCategory category) { this.filter = filter; this.selected = selected; this.category = category; @@ -63,7 +63,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable { public static NamedStoreChoiceComp create( ObservableValue> filter, Property selected, - DataStoreProvider.Category category) { + DataStoreProvider.DataCategory category) { return new NamedStoreChoiceComp( Bindings.createObjectBinding( () -> { @@ -142,7 +142,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable { var text = new LabelComp(I18n.observable("noMatchingStoreFound")) .apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS)); var addButton = new ButtonComp(I18n.observable("addStore"), null, () -> { - GuiDsStoreCreator.showCreation(category); + GuiDsStoreCreator.showCreation(v -> v.getCategory().equals(category)); }); var notice = new VerticalComp(List.of(text, addButton)) .apply(struc -> { diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java index a8167f16..a566d722 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java @@ -20,28 +20,35 @@ public class StoreCreationBarComp extends SimpleComp { @Override protected Region createSimple() { + var newOtherStore = new ButtonComp( + I18n.observable("addOther"), new FontIcon("mdi2c-card-plus-outline"), () -> { + GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.OTHER)); + }) + .shortcut(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)) + .apply(new FancyTooltipAugment<>("addOther")); + var newStreamStore = new ButtonComp( - I18n.observable("addStreamStore"), new FontIcon("mdi2c-card-plus-outline"), () -> { - GuiDsStoreCreator.showCreation(DataStoreProvider.Category.STREAM); + I18n.observable("addCommand"), new FontIcon("mdi2c-code-greater-than"), () -> { + GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.COMMAND)); }) - .shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN)) - .apply(new FancyTooltipAugment<>("addStreamStore")); + .shortcut(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)) + .apply(new FancyTooltipAugment<>("addCommand")); var newShellStore = new ButtonComp( - I18n.observable("addShellStore"), new FontIcon("mdi2h-home-plus-outline"), () -> { - GuiDsStoreCreator.showCreation(DataStoreProvider.Category.SHELL); + I18n.observable("addHost"), new FontIcon("mdi2h-home-plus-outline"), () -> { + GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.HOST)); }) - .shortcut(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN)) - .apply(new FancyTooltipAugment<>("addShellStore")); + .shortcut(new KeyCodeCombination(KeyCode.H, KeyCombination.SHORTCUT_DOWN)) + .apply(new FancyTooltipAugment<>("addHost")); var newDbStore = new ButtonComp( - I18n.observable("addDatabaseStore"), new FontIcon("mdi2d-database-plus-outline"), () -> { - GuiDsStoreCreator.showCreation(DataStoreProvider.Category.DATABASE); + I18n.observable("addDatabase"), new FontIcon("mdi2d-database-plus-outline"), () -> { + GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.DATABASE)); }) .shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN)) - .apply(new FancyTooltipAugment<>("addDatabaseStore")); + .apply(new FancyTooltipAugment<>("addDatabase")); - var box = new VerticalComp(List.of(newShellStore, newDbStore, newStreamStore)); + var box = new VerticalComp(List.of(newShellStore, newDbStore, newStreamStore, newOtherStore)); box.apply(s -> AppFont.medium(s.get())); var bar = box.createRegion(); bar.getStyleClass().add("bar"); diff --git a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java index 612fbe8a..c47e9440 100644 --- a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java +++ b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java @@ -254,7 +254,7 @@ public class AppExtensionManager { extendedLayer = ModuleLayer.defineModulesWithOneLoader(cf, extended, scl).layer(); } else { - extendedLayer = ModuleLayer.boot(); + extendedLayer = baseLayer; } addNativeLibrariesToPath(); diff --git a/app/src/main/java/io/xpipe/app/core/AppI18n.java b/app/src/main/java/io/xpipe/app/core/AppI18n.java index 2d282b67..d5aefb4a 100644 --- a/app/src/main/java/io/xpipe/app/core/AppI18n.java +++ b/app/src/main/java/io/xpipe/app/core/AppI18n.java @@ -135,6 +135,15 @@ public class AppI18n implements I18n { return key; } + public boolean containsKey(String s) { + var key = getKey(s); + if (translations == null) { + return false; + } + + return translations.containsKey(key); + } + public String getLocalised(String s, Object... vars) { var key = getKey(s); diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java index a86b19b4..0e967314 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java @@ -15,7 +15,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { - var categories = DataStoreProvider.Category.values(); + var categories = DataStoreProvider.DataCategory.values(); var all = DataStoreProviders.getAll(); var map = Arrays.stream(categories) .collect(Collectors.toMap(category -> getName(category), category -> all.stream() @@ -31,7 +31,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange return Response.builder().entries(map).build(); } - private String getName(DataStoreProvider.Category category) { + private String getName(DataStoreProvider.DataCategory category) { return category.name().substring(0, 1).toUpperCase() + category.name().substring(1).toLowerCase(); } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPreferencesFx.java b/app/src/main/java/io/xpipe/app/prefs/AppPreferencesFx.java index a682d96a..f0bdbcc2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPreferencesFx.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPreferencesFx.java @@ -1,5 +1,6 @@ package io.xpipe.app.prefs; +import com.dlsc.formsfx.model.structure.Form; import com.dlsc.formsfx.model.util.TranslationService; import com.dlsc.preferencesfx.PreferencesFxEvent; import com.dlsc.preferencesfx.history.History; @@ -11,6 +12,8 @@ import com.dlsc.preferencesfx.view.*; import javafx.beans.property.ObjectProperty; import javafx.event.EventHandler; import javafx.event.EventType; +import javafx.scene.control.ScrollPane; +import lombok.SneakyThrows; import java.util.List; @@ -56,10 +59,20 @@ public class AppPreferencesFx { public void setupControls() { undoRedoBox = new UndoRedoBox(preferencesFxModel.getHistory()); - breadCrumbView = new BreadCrumbView(preferencesFxModel, undoRedoBox); + breadCrumbView = new BreadCrumbView(preferencesFxModel, undoRedoBox) { + @Override + public void initializeParts() { + } + + @Override + public void layoutParts() { + } + }; breadCrumbPresenter = new BreadCrumbPresenter(preferencesFxModel, breadCrumbView); categoryController = new CategoryController(); + categoryController.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + categoryController.setFitToWidth(true); initializeCategoryViews(); // display initial category @@ -88,9 +101,24 @@ public class AppPreferencesFx { */ private void initializeCategoryViews() { preferencesFxModel.getFlatCategoriesLst().forEach(category -> { - CategoryView categoryView = new CategoryView(preferencesFxModel, category); + var categoryView = new CustomCategoryView(preferencesFxModel, category); CategoryPresenter categoryPresenter = - new CategoryPresenter(preferencesFxModel, category, categoryView, breadCrumbPresenter); + new CategoryPresenter(preferencesFxModel, category, categoryView, breadCrumbPresenter) { + @Override + @SneakyThrows + public void initializeViewParts() { + var formMethod = CategoryPresenter.class.getDeclaredMethod("createForm"); + formMethod.setAccessible(true); + + var formField = CategoryPresenter.class.getDeclaredField("form"); + formField.setAccessible(true); + formField.set(this, formMethod.invoke(this)); + categoryView.initializeFormRenderer((Form) formField.get(this)); + + this.addI18nListener(); + this.addInstantPersistenceListener(); + } + }; categoryController.addView(category, categoryView, categoryPresenter); }); } 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 f1746760..08373dbf 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -402,17 +402,22 @@ public class AppPrefs { Category.of( "system", Group.of( + "appBehaviour", Setting.of( "externalStartupBehaviour", externalStartupBehaviourControl, externalStartupBehaviour), - Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour), + Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)), + Group.of( + "updates", Setting.of("automaticallyUpdate", automaticallyUpdateField, automaticallyUpdate) .applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty( XPipeDistributionType.get().supportsUpdate()))), Setting.of("updateToPrereleases", updateToPrereleasesField, updateToPrereleases) .applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty( - XPipeDistributionType.get().supportsUpdate()))), + XPipeDistributionType.get().supportsUpdate())))), + Group.of( + "advanced", Setting.of("storageDirectory", storageDirectoryControl, internalStorageDirectory), Setting.of("logLevel", logLevelField, internalLogLevel), Setting.of("developerMode", developerModeField, internalDeveloperMode))), @@ -423,14 +428,13 @@ public class AppPrefs { Setting.of("language", languageControl, languageInternal), Setting.of("theme", themeControl, themeInternal), Setting.of("useSystemFont", useSystemFontInternal), - Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax), - Setting.of("fontSize", fontSizeInternal, fontSizeMin, fontSizeMax)), + Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax)), Group.of("windowOptions", Setting.of("saveWindowLocation", saveWindowLocationInternal))), Category.of( "integrations", Group.of( "editor", - Setting.of("defaultProgram", externalEditorControl, externalEditor), + Setting.of("editorProgram", externalEditorControl, externalEditor), Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand) .applyVisibility(VisibilityProperty.of( externalEditor.isEqualTo(ExternalEditorType.CUSTOM))), diff --git a/app/src/main/java/io/xpipe/app/prefs/CustomCategoryView.java b/app/src/main/java/io/xpipe/app/prefs/CustomCategoryView.java new file mode 100644 index 00000000..f7f40466 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/prefs/CustomCategoryView.java @@ -0,0 +1,19 @@ +package io.xpipe.app.prefs; + +import com.dlsc.formsfx.model.structure.Form; +import com.dlsc.preferencesfx.model.Category; +import com.dlsc.preferencesfx.model.PreferencesFxModel; +import com.dlsc.preferencesfx.view.CategoryView; + +public class CustomCategoryView extends CategoryView { + + public CustomCategoryView(PreferencesFxModel model, Category categoryModel) { + super(model, categoryModel); + } + + public void initializeFormRenderer(Form form) { + getChildren().clear(); + var preferencesFormRenderer = new CustomFormRenderer(form); + getChildren().add(preferencesFormRenderer); + } +} diff --git a/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java new file mode 100644 index 00000000..d2da7558 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java @@ -0,0 +1,123 @@ +package io.xpipe.app.prefs; + +import com.dlsc.formsfx.model.structure.Element; +import com.dlsc.formsfx.model.structure.Field; +import com.dlsc.formsfx.model.structure.Form; +import com.dlsc.formsfx.model.structure.NodeElement; +import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl; +import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxFormRenderer; +import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxGroup; +import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxGroupRenderer; +import com.dlsc.preferencesfx.util.PreferencesFxUtils; +import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppI18n; +import io.xpipe.extension.I18n; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; + +import java.util.List; +import java.util.stream.Collectors; + +public class CustomFormRenderer extends PreferencesFxFormRenderer { + + public static final double SPACING = 8.0; + + public CustomFormRenderer(Form form) { + super(form); + } + + @Override + public void initializeParts() { + groups = form.getGroups().stream() + .map(group -> new PreferencesFxGroupRenderer((PreferencesFxGroup) group, this) { + + @Override + public void initializeParts() { + super.initializeParts(); + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void layoutParts() { + StringBuilder styleClass = new StringBuilder("group"); + + // if there are no rows yet, getRowCount returns -1, in this case the next row is 0 + int nextRow = PreferencesFxUtils.getRowCount(grid) + 1; + + // Only when the preferencesGroup has a title + if (preferencesGroup.getTitle() != null) { + grid.add(titleLabel, 0, nextRow++, 2, 1); + titleLabel.getStyleClass().add("group-title"); + 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, 0, 0)); + } + } + + List elements = preferencesGroup.getElements().stream() + .map(Element.class::cast) + .toList(); + styleClass.append("-setting"); + + int rowAmount = nextRow; + for (int i = 0; i < elements.size(); i++) { + // add to GridPane + Element element = elements.get(i); + if (element instanceof Field f) { + var label = f.getLabel(); + var descriptionKey = label != null ? label + "Description" : null; + + SimpleControl c = (SimpleControl) ((Field) element).getRenderer(); + c.setField((Field) element); + AppFont.header(c.getFieldLabel()); + c.getFieldLabel().setPrefHeight(AppFont.getPixelSize(1)); + c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1)); + grid.add(c.getFieldLabel(), 0, i + rowAmount, 2, 1); + + var descriptionLabel = new Label(); + descriptionLabel.setWrapText(true); + descriptionLabel.disableProperty().bind(c.getFieldLabel().disabledProperty()); + descriptionLabel.opacityProperty().bind(c.getFieldLabel().opacityProperty()); + descriptionLabel.managedProperty().bind(c.getFieldLabel().managedProperty()); + descriptionLabel.visibleProperty().bind(c.getFieldLabel().visibleProperty()); + descriptionLabel.setMaxHeight(USE_PREF_SIZE); + if (AppI18n.get().containsKey(descriptionKey)) { + rowAmount++; + descriptionLabel.textProperty().bind(I18n.observable(descriptionKey)); + grid.add(descriptionLabel, 0, i + rowAmount, 2, 1); + } + + rowAmount++; + grid.add(c.getNode(), 0, i + rowAmount, 1, 1); + + if (i == elements.size() - 1) { + // additional styling for the last setting + styleClass.append("-last"); + } + + var offset = preferencesGroup.getTitle() != null ? 15 : 0; + + GridPane.setMargin(descriptionLabel, new Insets(SPACING, 0, 0, offset)); + GridPane.setMargin(c.getNode(), new Insets(SPACING, 0, 0, offset)); + + if (!((i == 0) && (nextRow > 0))) { + GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING * 3, 0, 0, offset)); + } else { + GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING, 0, 0, offset)); + } + + c.getFieldLabel().getStyleClass().add(styleClass.toString() + "-label"); + c.getNode().getStyleClass().add(styleClass.toString() + "-node"); + } + + if (element instanceof NodeElement nodeElement) { + grid.add(nodeElement.getNode(), 0, i + rowAmount, GridPane.REMAINING, 1); + } + } + } + }) + .collect(Collectors.toList()); + } +} diff --git a/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java b/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java index c29669f1..c62f8f16 100644 --- a/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java +++ b/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java @@ -80,7 +80,7 @@ public class XPipeDaemonProvider implements XPipeDaemon { public & Validatable> T namedStoreChooser( ObservableValue> filter, Property selected, - DataStoreProvider.Category category) { + DataStoreProvider.DataCategory category) { return (T) new NamedStoreChoiceComp(filter, selected, category); } diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/dscreation_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/dscreation_en.properties index 4fb09a38..7c59a502 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/dscreation_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/dscreation_en.properties @@ -34,12 +34,14 @@ none=None save=Save clean=Clean refresh=Refresh -addDatabaseStore=Add Database ... -addDatabaseTitle=Add Database Store -addStreamStore=Add Stream ... +addDatabase=Add Database ... +addHost=Add Host ... +addCommand=Add Command ... +addOther=Add Other ... addStreamTitle=Add Stream Store -selectQueryType=Query Type -selectQueryTypeDescription=Select Query Type +addConnection=Add Connection +selectType=Select Type +selectTypeDescription=Select connection type selectDatabaseType=Database Type selectDatabaseTypeDescription=Select Type of the Database selectShellType=Shell Type diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties index 25491f8d..172d7b81 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties @@ -2,18 +2,26 @@ appearance=Appearance integrations=Integrations uiOptions=UI Options theme=Theme -defaultProgram=Default Program +editorProgram=Default Program +editorProgramDescription=The default text editor to use when editing any kind of text data. useSystemFont=Use system font +updates=Updates +advanced=Advanced +useSystemFontDescription=In case you're using a custom font on your system, you can opt to use it instead of the default X-Pipe font. tooltipDelay=Tooltip delay +tooltipDelayDescription=The amount of milliseconds to wait until a tooltip is displayed. fontSize=Font size windowOptions=Window Options -saveWindowLocation=Save window location on exit +saveWindowLocation=Save window location +saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts. startupShutdown=Startup / Shutdown system=System updateToPrereleases=Update to prereleases +updateToPrereleasesDescription=When enabled, the update check will also look for available prereleases in addition to full releases. storage=Storage runOnStartup=Run on startup closeBehaviour=Close behaviour +closeBehaviourDescription=Controls how X-Pipe should proceed upon closing its main window. language=Language lightTheme=Light Theme darkTheme=Dark Theme @@ -23,6 +31,7 @@ minimizeToTray=Minimize to tray closeBehaviourAlertTitle=Set closing behaviour closeBehaviourAlertTitleHeader=Select what should happen when closing the window. externalStartupBehaviour=External startup behaviour +externalStartupBehaviourDescription=Controls the behavior of the desktop application when X-Pipe is started from for example the CLI or an API. clearCachesAlertTitle=Clean Cache clearCachesAlertTitleHeader=Do you want to clean all X-Pipe caches? clearCachesAlertTitleContent=Note that this will delete all the data that is stored to improve the user experience, for example file usage histories. @@ -37,19 +46,34 @@ notAnAbsolutePath=Not an absolute path notADirectory=Not a directory notAnEmptyDirectory=Not an empty directory automaticallyUpdate=Automatically update +automaticallyUpdateDescription=When enabled, new releases are automatically downloaded in the background while X-Pipe is running and installed on the next launch. sendAnonymousErrorReports=Send anonymous error reports sendUsageStatistics=Send anonymous usage statistics storageDirectory=Storage directory +storageDirectoryDescription=The location where X-Pipe should store all connection and data source information. logLevel=Log level +appBehaviour=Application behaviour +logLevelDescription=The log level that should be used when writing log files. developerMode=Developer mode +developerModeDescription=When enabled, you will have access to a variety of additional options that are useful for development. editor=Editor custom=Custom customEditorCommand=Custom editor command +customEditorCommandDescription=The command to use to open the custom editor. The placeholder string $file will be replaced by the quoted actual file name when called. If you don't specify a placeholder string, the actual filename will be appended. editorReloadTimeout=Editor reload timeout +editorReloadTimeoutDescription=The amount of milliseconds to wait before reading a file after it has been updated. This avoids issues in cases where your editor is slow at writing or releasing file locks. notepad++=Notepad++ notepad++Windows=Notepad++ notepad++Linux=Notepad++ notepad=Notepad developer=Developer developerDisableUpdateVersionCheck=Disable Update Version Check +developerDisableUpdateVersionCheckDescription=Controls whether the update checker will ignore the version number when looking for an update. +developerDisableGuiRestrictions=Disable GUI restrictions +developerDisableGuiRestrictionsDescription=Controls wether some disabled actions can still be executed from the user interface. +developerShowHiddenEntries=Show hidden entries +developerShowHiddenEntriesDescription=When enabled, hidden and internal data sources will be shown. +developerShowHiddenProviders=Show hidden providers +developerShowHiddenProvidersDescription=Controls whether hidden and internal connection and data source providers will be shown in the creation dialog. developerDisableConnectorInstallationVersionCheck=Disable Connector Version Check +developerDisableConnectorInstallationVersionCheckDescription=Controls whether the update checker will ignore the version number when inspecting the version of an X-Pipe connector installed on a remote machine. diff --git a/dist/changelogs/0.4.30.md b/dist/changelogs/0.4.30.md new file mode 100644 index 00000000..40bbdc62 --- /dev/null +++ b/dist/changelogs/0.4.30.md @@ -0,0 +1,2 @@ +- Improve settings menu layout +- Improve connection creation sections \ No newline at end of file diff --git a/ext/base/src/main/java/io/xpipe/ext/base/InMemoryStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/InMemoryStoreProvider.java index f61d3efc..e1df0419 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/InMemoryStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/InMemoryStoreProvider.java @@ -71,8 +71,8 @@ public class InMemoryStoreProvider implements DataStoreProvider { } @Override - public Category getCategory() { - return Category.STREAM; + public DataCategory getCategory() { + return DataCategory.STREAM; } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java b/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java index 89dcd25b..c3f23bf7 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java @@ -30,7 +30,7 @@ public class RawFileOutputTarget implements DataSourceTarget { new SimpleObjectProperty<>(store -> store instanceof StreamDataStore && (store.getFlow().hasOutput())), target, - DataStoreProvider.Category.STREAM); + DataStoreProvider.DataCategory.STREAM); storeChoice .apply(GrowAugment.create(true, true)) .apply(struc -> GridPane.setVgrow(struc.get(), Priority.ALWAYS)); diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java index 75e2d841..73fc351d 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java @@ -26,19 +26,30 @@ public interface DataStoreProvider { } } - default Category getCategory() { + default DataCategory getCategory() { var c = getStoreClasses().get(0); if (StreamDataStore.class.isAssignableFrom(c)) { - return Category.STREAM; + return DataCategory.STREAM; } if (FileSystemStore.class.isAssignableFrom(c) || ShellStore.class.isAssignableFrom(c)) { - return Category.SHELL; + return DataCategory.SHELL; } throw new ExtensionException("Provider " + getId() + " has no set category"); } + default DisplayCategory getDisplayCategory() { + var category = getCategory(); + if (category.equals(DataCategory.SHELL)) { + return DisplayCategory.HOST; + } + if (category.equals(DataCategory.DATABASE)) { + return DisplayCategory.DATABASE; + } + return DisplayCategory.OTHER; + } + default DataStore getParent(DataStore store) { return null; } @@ -105,9 +116,16 @@ public interface DataStoreProvider { return true; } - enum Category { + enum DataCategory { STREAM, SHELL, DATABASE; } + + enum DisplayCategory { + HOST, + DATABASE, + COMMAND, + OTHER; + } } diff --git a/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java b/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java index 7ce2d484..7469bf63 100644 --- a/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java +++ b/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java @@ -47,7 +47,7 @@ public interface XPipeDaemon { & Validatable> T namedStoreChooser( ObservableValue> filter, Property selected, - DataStoreProvider.Category category); + DataStoreProvider.DataCategory category); & Validatable> T namedSourceChooser( ObservableValue>> filter, diff --git a/gradle/gradle_scripts/preferencesfx-core-11.15.0.jar b/gradle/gradle_scripts/preferencesfx-core-11.15.0.jar new file mode 100644 index 00000000..636a1c77 Binary files /dev/null and b/gradle/gradle_scripts/preferencesfx-core-11.15.0.jar differ diff --git a/version b/version index c1a2129a..93b432f5 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.4.29 \ No newline at end of file +0.4.30 \ No newline at end of file