diff --git a/.github/stale.yml b/.github/stale.yml index 61494684a..e32981b00 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,11 +1,13 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 180 +daysUntilStale: 365 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 30 +daysUntilClose: 90 # Issues with these labels will never be considered stale exemptLabels: - type:security-issue # never close automatically - type:feature-request # never close automatically + - type:enhancement # never close automatically + - type:upstream-bug # never close automatically - state:awaiting-response # handled by different bot - state:blocked - state:confirmed diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 225bf1e46..7027c9ddf 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -61,10 +61,16 @@ jobs: --output runtime --module-path "${JAVA_HOME}/jmods" --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands --no-header-files --no-man-pages --strip-debug --compress=1 + - name: Prepare additional launcher + run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties + env: + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + REVISION_NUM: ${{ steps.versions.outputs.revNum }} - name: Run jpackage run: > ${JAVA_HOME}/bin/jpackage @@ -91,12 +97,12 @@ jobs: --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.showTrayIcon=false" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\"" + --add-launcher Cryptomator-gtk2=launcher-gtk2.properties --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | mv appdir/Cryptomator Cryptomator.AppDir cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ - envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg @@ -108,9 +114,6 @@ jobs: ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - env: - REVISION_NO: ${{ steps.versions.outputs.revNum }} - SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 run: | JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index c9fdb77f0..ecce97573 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -20,12 +20,14 @@ ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ --compress=1 # create app dir +envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties ${JAVA_HOME}/bin/jpackage \ --verbose \ --type app-image \ @@ -48,6 +50,7 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.showTrayIcon=false" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ + --add-launcher cryptomator-gtk2=launcher-gtk2.properties \ --resource-dir ../resources # transform AppDir diff --git a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh index 82c3e01ca..39579a122 100755 --- a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh +++ b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh @@ -15,26 +15,11 @@ elif command -v pacman &> /dev/null; then # don't forget arch GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?` fi -if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then - GTK_FLAG="-Djdk.gtk.version=2" -fi - # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 export LD_PRELOAD=lib/app/libjffi.so -# start Cryptomator -./lib/runtime/bin/java \ - -p "lib/app/mods" \ - -cp "lib/app/*" \ - -Dfile.encoding="utf-8" \ - -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \ - -Dcryptomator.pluginDir="~/.local/share/Cryptomator/plugins" \ - -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \ - -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json" \ - -Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" \ - -Dcryptomator.buildNumber="appimage-${REVISION_NO}" \ - -Dcryptomator.appVersion="${SEMVER_STR}" \ - $GTK_FLAG \ - -Xss5m \ - -Xmx256m \ - -m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator +if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then + bin/Cryptomator-gtk2 +else + bin/Cryptomator +fi \ No newline at end of file diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index b6292ceef..af126f815 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -19,6 +19,7 @@ override_dh_auto_build: jlink \ --output runtime \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/linux/launcher-gtk2.properties b/dist/linux/launcher-gtk2.properties new file mode 100644 index 000000000..bf4727fa9 --- /dev/null +++ b/dist/linux/launcher-gtk2.properties @@ -0,0 +1,12 @@ +java-options=-Xss5m \ + -Xmx256m \ + -Dfile.encoding=\"utf-8\" \ + -Dcryptomator.appVersion=\"${SEMVER_STR}\" \ + -Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \ + -Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\" \ + -Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\" \ + -Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\" \ + -Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\" \ + -Dcryptomator.showTrayIcon=false \ + -Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\" \ + -Djdk.gtk.version=2 \ No newline at end of file diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index 5c23804b7..2fd7c7b89 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -38,6 +38,7 @@ ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index aee2caa7b..883bd8f53 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -42,6 +42,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { --output runtime ` --module-path "$Env:JAVA_HOME/jmods" ` --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` + --strip-native-commands ` --no-header-files ` --no-man-pages ` --strip-debug ` diff --git a/src/main/java/org/cryptomator/common/settings/UiTheme.java b/src/main/java/org/cryptomator/common/settings/UiTheme.java index 62fe714e4..24e73dad6 100644 --- a/src/main/java/org/cryptomator/common/settings/UiTheme.java +++ b/src/main/java/org/cryptomator/common/settings/UiTheme.java @@ -3,9 +3,9 @@ package org.cryptomator.common.settings; import org.apache.commons.lang3.SystemUtils; public enum UiTheme { - LIGHT("preferences.general.theme.light"), // - DARK("preferences.general.theme.dark"), // - AUTOMATIC("preferences.general.theme.automatic"); + LIGHT("preferences.interface.theme.light"), // + DARK("preferences.interface.theme.dark"), // + AUTOMATIC("preferences.interface.theme.automatic"); public static UiTheme[] applicableValues() { if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) { diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 90f391ec6..3cd5ec727 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -21,15 +21,18 @@ import javax.inject.Inject; import javax.inject.Singleton; import javafx.application.Application; import javafx.stage.Stage; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.Executors; @Singleton public class Cryptomator { + private static final long STARTUP_TIME = System.currentTimeMillis(); // DaggerCryptomatorComponent gets generated by Dagger. // Run Maven and include target/generated-sources/annotations in your IDE. - private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create(); + private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME); private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); private final LoggerConfiguration logConfig; @@ -50,6 +53,20 @@ public class Cryptomator { } public static void main(String[] args) { + var printVersion = Optional.ofNullable(args) // + .stream() //Streams either one element (the args-array) or zero elements + .flatMap(Arrays::stream) // + .anyMatch(arg -> "-v".equals(arg) || "--version".equals(arg)); + + if (printVersion) { + var appVer = System.getProperty("cryptomator.appVersion", "SNAPSHOT"); + var buildNumber = System.getProperty("cryptomator.buildNumber", "SNAPSHOT"); + + //Reduce noise for parsers by using System.out directly + System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber); + return; + } + int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); LOG.info("Exit {}", exitCode); System.exit(exitCode); // end remaining non-daemon threads. @@ -63,6 +80,7 @@ public class Cryptomator { */ private int run(String[] args) { logConfig.init(); + LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME); LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); supportedLanguages.applyPreferred(); @@ -112,7 +130,7 @@ public class Cryptomator { @Override public void start(Stage primaryStage) { - LOG.info("JavaFX application started."); + LOG.info("JavaFX runtime started after {}ms", System.currentTimeMillis() - STARTUP_TIME); FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() // .fxApplication(this) // .primaryStage(primaryStage) // diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java index b43c0eca0..8c6443717 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java @@ -1,10 +1,12 @@ package org.cryptomator.launcher; +import dagger.BindsInstance; import dagger.Component; import org.cryptomator.common.CommonsModule; import org.cryptomator.logging.LoggerModule; import org.cryptomator.ui.fxapp.FxApplicationComponent; +import javax.inject.Named; import javax.inject.Singleton; @Singleton @@ -15,4 +17,9 @@ public interface CryptomatorComponent { FxApplicationComponent.Builder fxAppComponentBuilder(); + @Component.Factory + interface Factory { + CryptomatorComponent create(@BindsInstance @Named("startupTime") long startupTime); + } + } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 55f76d321..fd9326c3d 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -7,16 +7,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; import javafx.application.Platform; import javafx.stage.Stage; import javafx.stage.StageStyle; import java.awt.SystemTray; +import java.io.IOException; +import java.io.UncheckedIOException; @FxApplicationScoped public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); + private final long startupTime; private final Settings settings; private final AppLaunchEventHandler launchEventHandler; private final Lazy trayMenu; @@ -26,7 +30,8 @@ public class FxApplication { private final AutoUnlocker autoUnlocker; @Inject - FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { + FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { + this.startupTime = startupTime; this.settings = settings; this.launchEventHandler = launchEventHandler; this.trayMenu = trayMenu; @@ -61,6 +66,10 @@ public class FxApplication { stage.setIconified(true); } } + LOG.debug("Main window initialized after {}ms", System.currentTimeMillis() - startupTime); + }).exceptionally(error -> { + LOG.error("Failed to show main window", error); + return null; }); launchEventHandler.startHandlingLaunchEvents(); diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index 3ebdad4a4..f2b7ef3b7 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -1,37 +1,25 @@ package org.cryptomator.ui.preferences; -import com.google.common.base.Strings; import org.cryptomator.common.Environment; -import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.UiTheme; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; -import org.cryptomator.launcher.SupportedLanguages; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.application.Application; import javafx.beans.binding.Bindings; -import javafx.beans.property.ObjectProperty; -import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; -import javafx.geometry.NodeOrientation; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; -import javafx.scene.control.RadioButton; -import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.stage.Stage; import javafx.util.StringConverter; -import java.util.Locale; import java.util.Optional; -import java.util.ResourceBundle; import java.util.Set; @PreferencesScoped @@ -41,39 +29,23 @@ public class GeneralPreferencesController implements FxController { private final Stage window; private final Settings settings; - private final boolean trayMenuInitialized; - private final boolean trayMenuSupported; private final Optional autoStartProvider; - private final ObjectProperty selectedTabProperty; - private final LicenseHolder licenseHolder; - private final ResourceBundle resourceBundle; private final Application application; private final Environment environment; private final Set keychainAccessProviders; private final FxApplicationWindows appWindows; - public ChoiceBox themeChoiceBox; public ChoiceBox keychainBackendChoiceBox; - public CheckBox showMinimizeButtonCheckbox; - public CheckBox showTrayIconCheckbox; public CheckBox startHiddenCheckbox; - public ChoiceBox preferredLanguageChoiceBox; public CheckBox debugModeCheckbox; public CheckBox autoStartCheckbox; public ToggleGroup nodeOrientation; - public RadioButton nodeOrientationLtr; - public RadioButton nodeOrientationRtl; @Inject - GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) { + GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional autoStartProvider, Set keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) { this.window = window; this.settings = settings; - this.trayMenuInitialized = trayMenu.isInitialized(); - this.trayMenuSupported = trayMenu.isSupported(); this.autoStartProvider = autoStartProvider; this.keychainAccessProviders = keychainAccessProviders; - this.selectedTabProperty = selectedTabProperty; - this.licenseHolder = licenseHolder; - this.resourceBundle = resourceBundle; this.application = application; this.environment = environment; this.appWindows = appWindows; @@ -81,32 +53,12 @@ public class GeneralPreferencesController implements FxController { @FXML public void initialize() { - themeChoiceBox.getItems().addAll(UiTheme.applicableValues()); - if (!themeChoiceBox.getItems().contains(settings.theme().get())) { - settings.theme().set(UiTheme.LIGHT); - } - themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); - themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); - - showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton()); - - showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon()); - startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); - preferredLanguageChoiceBox.getItems().add(null); - preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS); - preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); - preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); - debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled())); - nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); - nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); - nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); - var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders); keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders); keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get())); @@ -114,29 +66,10 @@ public class GeneralPreferencesController implements FxController { Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter); } - - public boolean isTrayMenuInitialized() { - return trayMenuInitialized; - } - - public boolean isTrayMenuSupported() { - return trayMenuSupported; - } - public boolean isAutoStartSupported() { return autoStartProvider.isPresent(); } - private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { - if (nodeOrientationLtr.equals(newValue)) { - settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); - } else if (nodeOrientationRtl.equals(newValue)) { - settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); - } else { - LOG.warn("Unexpected toggle option {}", newValue); - } - } - @FXML public void toggleAutoStart() { autoStartProvider.ifPresent(autoStart -> { @@ -155,16 +88,6 @@ public class GeneralPreferencesController implements FxController { }); } - public LicenseHolder getLicenseHolder() { - return licenseHolder; - } - - - @FXML - public void showContributeTab() { - selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE); - } - @FXML public void showLogfileDirectory() { environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); @@ -172,53 +95,7 @@ public class GeneralPreferencesController implements FxController { /* Helper classes */ - private static class UiThemeConverter extends StringConverter { - - private final ResourceBundle resourceBundle; - - UiThemeConverter(ResourceBundle resourceBundle) { - this.resourceBundle = resourceBundle; - } - - @Override - public String toString(UiTheme impl) { - return resourceBundle.getString(impl.getDisplayName()); - } - - @Override - public UiTheme fromString(String string) { - throw new UnsupportedOperationException(); - } - - } - - private static class LanguageTagConverter extends StringConverter { - - private final ResourceBundle resourceBundle; - - LanguageTagConverter(ResourceBundle resourceBundle) { - this.resourceBundle = resourceBundle; - } - - @Override - public String toString(String tag) { - if (tag == null) { - return resourceBundle.getString("preferences.general.language.auto"); - } else { - var locale = Locale.forLanguageTag(tag); - var lang = locale.getDisplayLanguage(locale); - var region = locale.getDisplayCountry(locale); - return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")"); - } - } - - @Override - public String fromString(String displayLanguage) { - throw new UnsupportedOperationException(); - } - } - - private class KeychainProviderDisplayNameConverter extends StringConverter { + private static class KeychainProviderDisplayNameConverter extends StringConverter { @Override public String toString(KeychainAccessProvider provider) { diff --git a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java new file mode 100644 index 000000000..dd463b9e3 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java @@ -0,0 +1,156 @@ +package org.cryptomator.ui.preferences; + +import com.google.common.base.Strings; +import org.cryptomator.common.LicenseHolder; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.UiTheme; +import org.cryptomator.launcher.SupportedLanguages; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.traymenu.TrayMenuComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.geometry.NodeOrientation; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.util.StringConverter; +import java.util.Locale; +import java.util.ResourceBundle; + +@PreferencesScoped +public class InterfacePreferencesController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(InterfacePreferencesController.class); + + private final Settings settings; + private final boolean trayMenuInitialized; + private final boolean trayMenuSupported; + private final ObjectProperty selectedTabProperty; + private final LicenseHolder licenseHolder; + private final ResourceBundle resourceBundle; + public ChoiceBox themeChoiceBox; + public CheckBox showMinimizeButtonCheckbox; + public CheckBox showTrayIconCheckbox; + public ChoiceBox preferredLanguageChoiceBox; + public ToggleGroup nodeOrientation; + public RadioButton nodeOrientationLtr; + public RadioButton nodeOrientationRtl; + + @Inject + InterfacePreferencesController(Settings settings, TrayMenuComponent trayMenu, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle) { + this.settings = settings; + this.trayMenuInitialized = trayMenu.isInitialized(); + this.trayMenuSupported = trayMenu.isSupported(); + this.selectedTabProperty = selectedTabProperty; + this.licenseHolder = licenseHolder; + this.resourceBundle = resourceBundle; + } + + @FXML + public void initialize() { + themeChoiceBox.getItems().addAll(UiTheme.applicableValues()); + if (!themeChoiceBox.getItems().contains(settings.theme().get())) { + settings.theme().set(UiTheme.LIGHT); + } + themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); + themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); + + showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton()); + + showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon()); + + preferredLanguageChoiceBox.getItems().add(null); + preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS); + preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); + preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); + + nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); + nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); + nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); + } + + + public boolean isTrayMenuInitialized() { + return trayMenuInitialized; + } + + public boolean isTrayMenuSupported() { + return trayMenuSupported; + } + + private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (nodeOrientationLtr.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); + } else if (nodeOrientationRtl.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); + } else { + LOG.warn("Unexpected toggle option {}", newValue); + } + } + + public LicenseHolder getLicenseHolder() { + return licenseHolder; + } + + + @FXML + public void showContributeTab() { + selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE); + } + + /* Helper classes */ + + private static class UiThemeConverter extends StringConverter { + + private final ResourceBundle resourceBundle; + + UiThemeConverter(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + @Override + public String toString(UiTheme impl) { + return resourceBundle.getString(impl.getDisplayName()); + } + + @Override + public UiTheme fromString(String string) { + throw new UnsupportedOperationException(); + } + + } + + private static class LanguageTagConverter extends StringConverter { + + private final ResourceBundle resourceBundle; + + LanguageTagConverter(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + @Override + public String toString(String tag) { + if (tag == null) { + return resourceBundle.getString("preferences.interface.language.auto"); + } else { + var locale = Locale.forLanguageTag(tag); + var lang = locale.getDisplayLanguage(locale); + var region = locale.getDisplayCountry(locale); + return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")"); + } + } + + @Override + public String fromString(String displayLanguage) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java index 276794753..caaaf7d80 100644 --- a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java @@ -24,6 +24,7 @@ public class PreferencesController implements FxController { private final BooleanBinding updateAvailable; public TabPane tabPane; public Tab generalTab; + public Tab interfaceTab; public Tab volumeTab; public Tab updatesTab; public Tab contributeTab; @@ -50,10 +51,11 @@ public class PreferencesController implements FxController { private Tab getTabToSelect(SelectedPreferencesTab selectedTab) { return switch (selectedTab) { - case UPDATES -> updatesTab; - case VOLUME -> volumeTab; - case CONTRIBUTE -> contributeTab; case GENERAL -> generalTab; + case INTERFACE -> interfaceTab; + case VOLUME -> volumeTab; + case UPDATES -> updatesTab; + case CONTRIBUTE -> contributeTab; case ABOUT -> aboutTab; case ANY -> updateAvailable.get() ? updatesTab : generalTab; }; diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java index 189666591..858f8f1d8 100644 --- a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java +++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java @@ -64,6 +64,11 @@ abstract class PreferencesModule { @FxControllerKey(GeneralPreferencesController.class) abstract FxController bindGeneralPreferencesController(GeneralPreferencesController controller); + @Binds + @IntoMap + @FxControllerKey(InterfacePreferencesController.class) + abstract FxController bindInterfacePreferencesController(InterfacePreferencesController controller); + @Binds @IntoMap @FxControllerKey(UpdatesPreferencesController.class) diff --git a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java index 892d16a8c..00b519493 100644 --- a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java +++ b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java @@ -11,6 +11,11 @@ public enum SelectedPreferencesTab { */ GENERAL, + /** + * Show interface tab + */ + INTERFACE, + /** * Show volume tab */ diff --git a/src/main/resources/fxml/preferences.fxml b/src/main/resources/fxml/preferences.fxml index 4eaf3d45c..c96eb5395 100644 --- a/src/main/resources/fxml/preferences.fxml +++ b/src/main/resources/fxml/preferences.fxml @@ -9,7 +9,7 @@ fx:controller="org.cryptomator.ui.preferences.PreferencesController" minWidth="-Infinity" maxWidth="-Infinity" - prefWidth="500" + prefWidth="650" tabMinWidth="60" tabClosingPolicy="UNAVAILABLE" tabDragPolicy="FIXED"> @@ -22,6 +22,14 @@ + + + + + + + + diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml index cfa8ec010..ce2a31e4e 100644 --- a/src/main/resources/fxml/preferences_about.fxml +++ b/src/main/resources/fxml/preferences_about.fxml @@ -11,9 +11,9 @@ + spacing="24"> - + diff --git a/src/main/resources/fxml/preferences_contribute.fxml b/src/main/resources/fxml/preferences_contribute.fxml index bfe91d0eb..55d40b495 100644 --- a/src/main/resources/fxml/preferences_contribute.fxml +++ b/src/main/resources/fxml/preferences_contribute.fxml @@ -13,9 +13,9 @@ + spacing="24"> - + diff --git a/src/main/resources/fxml/preferences_general.fxml b/src/main/resources/fxml/preferences_general.fxml index 1e3be74c6..6d2b68447 100644 --- a/src/main/resources/fxml/preferences_general.fxml +++ b/src/main/resources/fxml/preferences_general.fxml @@ -5,54 +5,35 @@ - + + spacing="12"> - + - - - - - - - - - + - - - - - - - - - + - + + + + + + diff --git a/src/main/resources/fxml/preferences_interface.fxml b/src/main/resources/fxml/preferences_interface.fxml new file mode 100644 index 000000000..6cb9b7d73 --- /dev/null +++ b/src/main/resources/fxml/preferences_interface.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index 0b176dfb3..3156d1c3c 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -11,19 +11,19 @@ + spacing="12"> - + - +