diff --git a/CHANGELOG.md b/CHANGELOG.md index 61130cb9435..adc97d7c507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We add auto url formatting when user paste link to URL field in entry editor. [#254](https://github.com/koppor/jabref/issues/254) - We added a minimal height for the entry editor so that it can no longer be hidden by accident. [#4279](https://github.com/JabRef/jabref/issues/4279) - We added a new keyboard shortcut so that the entry editor could be closed by Ctrl + E. [#4222] (https://github.com/JabRef/jabref/issues/4222) - +- We added an option in the preference dialog box, that allows user to pick the dark or light theme option. [#4130] (https://github.com/JabRef/jabref/issues/4130) diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index 60a1d626acf..b86cc83cbd2 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -75,11 +75,11 @@ public static synchronized KeyBindingRepository getKeyPrefs() { } // Background tasks - public static void startBackgroundTasks() { + public static void startBackgroundTasks() throws JabRefException { Globals.fileUpdateMonitor = new DefaultFileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); - themeLoader = new ThemeLoader(fileUpdateMonitor); + themeLoader = new ThemeLoader(fileUpdateMonitor, prefs); if (Globals.prefs.shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { startTelemetryClient(); diff --git a/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java b/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java index c13de2563c3..92c62d12864 100644 --- a/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java @@ -4,7 +4,9 @@ import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; @@ -16,12 +18,16 @@ class AppearancePrefsTab extends Pane implements PrefsTab { + public static final String BASE_CSS = "Base.css"; + public static final String DARK_CSS = "Dark.css"; private final JabRefPreferences prefs; private final CheckBox fontTweaksLAF; private final TextField fontSize; private final CheckBox overrideFonts; private final VBox container = new VBox(); private final DialogService dialogService; + private final RadioButton lightTheme; + private final RadioButton darkTheme; /** * Customization of appearance parameters. @@ -41,7 +47,18 @@ public AppearancePrefsTab(DialogService dialogService, JabRefPreferences prefs) fontSizeContainer.disableProperty().bind(overrideFonts.selectedProperty().not()); fontTweaksLAF = new CheckBox(Localization.lang("Tweak font rendering for entry editor on Linux")); - container.getChildren().addAll(overrideFonts, fontSizeContainer, fontTweaksLAF); + ToggleGroup themeGroup = new ToggleGroup(); + lightTheme = new RadioButton("Light theme"); + lightTheme.setToggleGroup(themeGroup); + darkTheme = new RadioButton("Dark theme"); + darkTheme.setToggleGroup(themeGroup); + + if (prefs.get(JabRefPreferences.FX_THEME).equals(BASE_CSS)) + lightTheme.setSelected(true); + else if (prefs.get(JabRefPreferences.FX_THEME).equals(DARK_CSS)) + darkTheme.setSelected(true); + + container.getChildren().addAll(overrideFonts, fontSizeContainer, fontTweaksLAF, lightTheme, darkTheme); } @@ -68,10 +85,21 @@ public void storeSettings() { int newFontSize = Integer.parseInt(fontSize.getText()); prefs.putInt(JabRefPreferences.MAIN_FONT_SIZE, newFontSize); + boolean isThemeChanged = false; + + if (lightTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(BASE_CSS)) { + prefs.put(JabRefPreferences.FX_THEME, BASE_CSS); + isThemeChanged = true; + } else if (darkTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(DARK_CSS)) { + prefs.put(JabRefPreferences.FX_THEME, DARK_CSS); + isThemeChanged = true; + } + boolean isRestartRequired = - oldFxTweakValue != fontTweaksLAF.isSelected() - || oldOverrideDefaultFontSize != overrideFonts.isSelected() - || oldFontSize != newFontSize; + (oldFxTweakValue != fontTweaksLAF.isSelected()) + || (oldOverrideDefaultFontSize != overrideFonts.isSelected()) + || (oldFontSize != newFontSize) + || isThemeChanged; if (isRestartRequired) { dialogService.showWarningDialogAndWait(Localization.lang("Settings"), Localization.lang("Some appearance settings you changed require to restart JabRef to come into effect.")); diff --git a/src/main/java/org/jabref/gui/util/ThemeLoader.java b/src/main/java/org/jabref/gui/util/ThemeLoader.java index 87eeb0a83e9..7a7004d3445 100644 --- a/src/main/java/org/jabref/gui/util/ThemeLoader.java +++ b/src/main/java/org/jabref/gui/util/ThemeLoader.java @@ -11,6 +11,7 @@ import javafx.scene.Parent; import javafx.scene.Scene; +import org.jabref.JabRefException; import org.jabref.gui.JabRefFrame; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileUpdateMonitor; @@ -21,28 +22,39 @@ /** * Installs the style file and provides live reloading. - * + *

* The live reloading has to be turned on by setting the -Djabref.theme.css property. * There two possible modes: - * (1) When only -Djabref.theme.css is specified, then the standard Base.css that is found will be watched - * and on changes in that file, the style-sheet will be reloaded and changes are immediately visible. - * (2) When a path to a css file is passed to -Djabref.theme.css, then the given style is loaded in addition to the base css file. - * Changes in the specified css file lead to an immediate redraw of the interface. - * + * (1) When only -Djabref.theme.css is specified, then the standard Base.css that is found will be watched + * and on changes in that file, the style-sheet will be reloaded and changes are immediately visible. + * (2) When a path to a css file is passed to -Djabref.theme.css, then the given style is loaded in addition to the base css file. + * Changes in the specified css file lead to an immediate redraw of the interface. + *

* When working from an IDE, this usually means that the Base.css is located in the build folder. * To use the css-file that is located in the sources directly, the full path can be given as value for the "VM option": * -Djabref.theme.css="/path/to/src/Base.css" - * */ public class ThemeLoader { private static final String DEFAULT_PATH_MAIN_CSS = JabRefFrame.class.getResource("Base.css").toExternalForm(); - private static final String CSS_SYSTEM_PROPERTY = System.getProperty("jabref.theme.css"); private static final Logger LOGGER = LoggerFactory.getLogger(ThemeLoader.class); + private String cssProperty = System.getProperty("jabref.theme.css"); private final FileUpdateMonitor fileUpdateMonitor; - public ThemeLoader(FileUpdateMonitor fileUpdateMonitor) { + public ThemeLoader(FileUpdateMonitor fileUpdateMonitor, JabRefPreferences jabRefPreferences) throws JabRefException { this.fileUpdateMonitor = Objects.requireNonNull(fileUpdateMonitor); + + if (StringUtil.isNullOrEmpty(cssProperty)) { + String cssFileName = jabRefPreferences.get(JabRefPreferences.FX_THEME); + if (cssFileName != null) { + try { + cssProperty = Paths.get(JabRefFrame.class.getResource(cssFileName).toURI()).toString(); + } catch (URISyntaxException e) { + LOGGER.warn("can't get css file URI"); + throw new JabRefException("can't set custom theme"); + } + } + } } /** @@ -52,8 +64,8 @@ public ThemeLoader(FileUpdateMonitor fileUpdateMonitor) { public void installBaseCss(Scene scene, JabRefPreferences preferences) { addAndWatchForChanges(scene, DEFAULT_PATH_MAIN_CSS, 0); - if (StringUtil.isNotBlank(CSS_SYSTEM_PROPERTY)) { - final Path path = Paths.get(CSS_SYSTEM_PROPERTY); + if (StringUtil.isNotBlank(cssProperty)) { + final Path path = Paths.get(cssProperty); if (Files.isReadable(path)) { String cssUrl = path.toUri().toString(); addAndWatchForChanges(scene, cssUrl, 1); @@ -69,7 +81,7 @@ private void addAndWatchForChanges(Scene scene, String cssUrl, int index) { try { // If -Djabref.theme.css is defined and the resources are not part of a .jar bundle, // we watch the file for changes and turn on live reloading - if (!cssUrl.startsWith("jar:") && CSS_SYSTEM_PROPERTY != null) { + if (!cssUrl.startsWith("jar:") && cssProperty != null) { Path cssFile = Paths.get(new URL(cssUrl).toURI()); LOGGER.info("Enabling live reloading of " + cssFile); fileUpdateMonitor.addListenerForFile(cssFile, () -> { diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 0c898045a68..ea96020a5fb 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -119,6 +119,7 @@ public class JabRefPreferences implements PreferencesService { public static final String EXTERNAL_FILE_TYPES = "externalFileTypes"; public static final String FONT_FAMILY = "fontFamily"; public static final String FX_FONT_RENDERING_TWEAK = "fxFontRenderingTweak"; + public static final String FX_THEME = "fxTheme"; public static final String LANGUAGE = "language"; public static final String NAMES_LAST_ONLY = "namesLastOnly"; public static final String ABBR_AUTHOR_NAMES = "abbrAuthorNames"; diff --git a/src/main/java/org/jabref/styletester/StyleTesterMain.java b/src/main/java/org/jabref/styletester/StyleTesterMain.java index 1bd2cc75d9c..68a7331b989 100644 --- a/src/main/java/org/jabref/styletester/StyleTesterMain.java +++ b/src/main/java/org/jabref/styletester/StyleTesterMain.java @@ -4,6 +4,7 @@ import javafx.scene.Scene; import javafx.stage.Stage; +import org.jabref.JabRefException; import org.jabref.JabRefExecutorService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.util.DefaultFileUpdateMonitor; @@ -20,7 +21,7 @@ public static void main(String[] args) { } @Override - public void start(Stage stage) { + public void start(Stage stage) throws JabRefException { StyleTesterView view = new StyleTesterView(); IconTheme.loadFonts(); @@ -29,7 +30,7 @@ public void start(Stage stage) { JabRefExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor"); Scene scene = new Scene(view.getContent()); - new ThemeLoader(fileUpdateMonitor).installBaseCss(scene, JabRefPreferences.getInstance()); + new ThemeLoader(fileUpdateMonitor, JabRefPreferences.getInstance()).installBaseCss(scene, JabRefPreferences.getInstance()); stage.setScene(scene); stage.show(); }