Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -472,9 +472,7 @@ extraJavaModuleInfo {
preserveExisting()
exports("com.sun.javafx.scene")
opens("com.sun.javafx.application", "org.testfx")
opens("com.sun.javafx.tk.quantum", "com.pixelduke.fxthemes")
opens("javafx.scene", "org.controlsfx.controls")
opens("javafx.stage", "com.pixelduke.fxthemes")
}

module("org.controlsfx:controlsfx", "org.controlsfx.controls") {
Expand Down
1 change: 0 additions & 1 deletion jabgui/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@
// region: other libraries (alphabetically)
// requires cuid;
requires com.dlsc.pdfviewfx;
requires com.pixelduke.fxthemes;
// requires com.sun.jna;
// requires dd.plist;
requires static io.github.eadr;
Expand Down
4 changes: 2 additions & 2 deletions jabgui/src/main/java/org/jabref/gui/WorkspacePreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ private WorkspacePreferences() {
Language.getLanguageFor(Locale.getDefault().getLanguage()), // Default language
false, // Default font size override
9, // Default font size
new Theme(Theme.BASE_CSS), // Default theme
false, // Default theme sync with OS
Theme.system(), // Default theme
true, // Default theme sync with OS
true, // Default open last edited
true, // Default show advanced hints
true, // Default confirm delete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ private WorkspacePreferences getWorkspacePreferencesFromBackingStore(WorkspacePr
getLanguage(),
getBoolean(OVERRIDE_DEFAULT_FONT_SIZE, defaults.shouldOverrideDefaultFontSize()),
getInt(MAIN_FONT_SIZE, defaults.getMainFontSize()),
new Theme(get(THEME, Theme.BASE_CSS)),
new Theme(get(THEME, Theme.SYSTEM)),
getBoolean(THEME_SYNC_OS, defaults.shouldThemeSyncOs()),
getBoolean(OPEN_LAST_EDITED, defaults.shouldOpenLastEdited()),
getBoolean(SHOW_ADVANCED_HINTS, defaults.shouldShowAdvancedHints()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public void initialize() {
validationVisualizer.initVisualization(viewModel.languageServerPortValidationStatus(), languageServerPort);
validationVisualizer.initVisualization(viewModel.fontSizeValidationStatus(), fontSize);
validationVisualizer.initVisualization(viewModel.customPathToThemeValidationStatus(), customThemePath);
validationVisualizer.initVisualization(viewModel.themeValidationStatus(), theme);
});

remoteServer.selectedProperty().bindBidirectional(viewModel.remoteServerProperty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javafx.beans.property.BooleanProperty;
Expand Down Expand Up @@ -65,7 +66,14 @@ public class GeneralTabViewModel implements PreferenceTabViewModel {
new ReadOnlyListWrapper<>(FXCollections.observableArrayList(ThemeTypes.values()));
private final ObjectProperty<ThemeTypes> selectedThemeProperty = new SimpleObjectProperty<>();

private final BooleanProperty themeSyncOsProperty = new SimpleBooleanProperty();
private final BooleanProperty themeSyncOsProperty = new SimpleBooleanProperty() {
@Override
protected void invalidated() {
if (themeSyncOsProperty.get()) {
selectedThemeProperty.set(null);
}
}
};

// init with empty string to avoid npe in accessing
private final StringProperty customPathToThemeProperty = new SimpleStringProperty("");
Expand Down Expand Up @@ -103,6 +111,7 @@ public class GeneralTabViewModel implements PreferenceTabViewModel {

private final Validator fontSizeValidator;
private final Validator customPathToThemeValidator;
private final Validator themeValidator;

private final List<String> restartWarning = new ArrayList<>();
private final BooleanProperty remoteServerProperty = new SimpleBooleanProperty();
Expand Down Expand Up @@ -157,6 +166,14 @@ public GeneralTabViewModel(DialogService dialogService,
Localization.lang("Visual theme"),
Localization.lang("Please specify a css theme file."))));

themeValidator = new FunctionBasedValidator<>(
selectedThemeProperty,
Objects::nonNull,
ValidationMessage.error("%s > %s %n %n %s".formatted(
Localization.lang("General"),
Localization.lang("Visual theme"),
Localization.lang("Please set a theme."))));

remotePortValidator = new FunctionBasedValidator<>(
remotePortProperty,
RemoteUtil::isStringUserPort,
Expand Down Expand Up @@ -194,17 +211,17 @@ public ValidationStatus languageServerPortValidationStatus() {
public void setValues() {
selectedLanguageProperty.setValue(workspacePreferences.getLanguage());

// The light theme is in fact the absence of any theme modifying 'base.css'. Another embedded theme like
// 'dark.css', stored in the classpath, can be introduced in {@link org.jabref.gui.theme.Theme}.
switch (workspacePreferences.getTheme().getType()) {
case DEFAULT ->
case LIGHT ->
selectedThemeProperty.setValue(ThemeTypes.LIGHT);
case EMBEDDED ->
case DARK ->
selectedThemeProperty.setValue(ThemeTypes.DARK);
case CUSTOM -> {
selectedThemeProperty.setValue(ThemeTypes.CUSTOM);
customPathToThemeProperty.setValue(workspacePreferences.getTheme().getName());
}
case SYSTEM ->
selectedThemeProperty.setValue(null);
}
themeSyncOsProperty.setValue(workspacePreferences.shouldThemeSyncOs());

Expand Down Expand Up @@ -250,15 +267,21 @@ public void storeSettings() {
workspacePreferences.setShouldOverrideDefaultFontSize(fontOverrideProperty.getValue());
workspacePreferences.setMainFontSize(Integer.parseInt(fontSizeProperty.getValue()));

switch (selectedThemeProperty.get()) {
case LIGHT ->
workspacePreferences.setTheme(Theme.light());
case DARK ->
workspacePreferences.setTheme(Theme.dark());
case CUSTOM ->
workspacePreferences.setTheme(Theme.custom(customPathToThemeProperty.getValue()));
boolean themeSyncOs = themeSyncOsProperty.getValue();
workspacePreferences.setThemeSyncOs(themeSyncOs);

if (themeSyncOs) {
workspacePreferences.setTheme(Theme.system());
} else {
switch (selectedThemeProperty.get()) {
case LIGHT ->
workspacePreferences.setTheme(Theme.light());
case DARK ->
workspacePreferences.setTheme(Theme.dark());
case CUSTOM ->
workspacePreferences.setTheme(Theme.custom(customPathToThemeProperty.getValue()));
}
}
workspacePreferences.setThemeSyncOs(themeSyncOsProperty.getValue());

workspacePreferences.setOpenLastEdited(openLastStartupProperty.getValue());
workspacePreferences.setShowAdvancedHints(showAdvancedHintsProperty.getValue());
Expand Down Expand Up @@ -337,6 +360,10 @@ public ValidationStatus customPathToThemeValidationStatus() {
return customPathToThemeValidator.getValidationStatus();
}

public ValidationStatus themeValidationStatus() {
return themeValidator.getValidationStatus();
}

@Override
public boolean validateSettings() {
CompositeValidator validator = new CompositeValidator();
Expand All @@ -361,6 +388,10 @@ public boolean validateSettings() {
validator.addValidators(customPathToThemeValidator);
}

if (!themeSyncOsProperty.get() && selectedThemeProperty.getValue() == null) {
validator.addValidators(themeValidator);
}

ValidationStatus validationStatus = validator.getValidationStatus();
if (!validationStatus.isValid()) {
validationStatus.getHighestMessage().ifPresent(message ->
Expand Down
59 changes: 36 additions & 23 deletions jabgui/src/main/java/org/jabref/gui/theme/Theme.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,70 @@

import org.jspecify.annotations.NonNull;

/// Represents one of three types of a css based Theme for JabRef:
/// Represents one of four types of a CSS-based Theme for JabRef:
///
/// The Default type of theme is the light theme (which is in fact the absence of any theme), the dark theme is currently
/// the only embedded theme and the custom themes, that can be created by loading a proper css file.
/// - System - Tightly coupled to the <code>shouldThemeSyncOs</code> setting,
/// meaning we do not want to override the theme at all
/// - Light - Ignore whatever the OS is set to and make JabRef use the light theme
/// - Dark - Ignore whatever the OS is set to and make JabRef use the dark theme
/// - Custom - CSS provided by the user
public class Theme {

public enum Type {
DEFAULT, EMBEDDED, CUSTOM
SYSTEM, LIGHT, DARK, CUSTOM
}

public static final String BASE_CSS = "Base.css";
public static final String EMBEDDED_DARK_CSS = "Dark.css";
public static final String SYSTEM = "";

private static final String BASE_CSS = "Base.css";

private static final String LIGHT = "light";
private static final String DARK = "dark";

private final Type type;
private final String name;
private final Optional<StyleSheet> additionalStylesheet;

public Theme(@NonNull String name) {
if (name.isEmpty() || BASE_CSS.equalsIgnoreCase(name)) {
if (SYSTEM.equalsIgnoreCase(name)) {
this.additionalStylesheet = Optional.empty();
this.type = Type.DEFAULT;
this.name = "";
} else if (EMBEDDED_DARK_CSS.equalsIgnoreCase(name)) {
this.additionalStylesheet = StyleSheet.create(EMBEDDED_DARK_CSS);
if (this.additionalStylesheet.isPresent()) {
this.type = Type.EMBEDDED;
this.name = EMBEDDED_DARK_CSS;
} else {
this.type = Type.DEFAULT;
this.name = "";
}
this.type = Type.SYSTEM;
this.name = name;
} else if (LIGHT.equalsIgnoreCase(name)) {
this.additionalStylesheet = Optional.empty();
this.type = Type.LIGHT;
this.name = name;
} else if (DARK.equalsIgnoreCase(name)) {
this.additionalStylesheet = Optional.empty();
this.type = Type.DARK;
this.name = name;
} else {
this.additionalStylesheet = StyleSheet.create(name);
if (this.additionalStylesheet.isPresent()) {
this.type = Type.CUSTOM;
this.name = name;
} else {
this.type = Type.DEFAULT;
this.name = "";
this.type = Type.SYSTEM;
}
this.name = name;
}
}

public static Theme light() {
return new Theme("");
return new Theme(LIGHT);
}

public static Theme dark() {
return new Theme(EMBEDDED_DARK_CSS);
return new Theme(DARK);
}

public static Theme custom(String name) {
return new Theme(name);
}

public static Theme system() {
return new Theme(SYSTEM);
}

/// @return the Theme type. Guaranteed to be non-null.
public Type getType() {
return type;
Expand Down Expand Up @@ -108,4 +117,8 @@ public String toString() {
", name='" + name + '\'' +
'}';
}

public static StyleSheet getBaseStyleSheet() {
return StyleSheet.create(BASE_CSS).orElseThrow();
}
}
Loading
Loading