diff --git a/CHANGELOG.md b/CHANGELOG.md index 52485dda5ce..7af8626986d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -493,6 +493,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We integrated predatory journal checking as part of the Integrity Checker based on the [check-bib-for-predatory](https://github.com/CfKu/check-bib-for-predatory). [koppor#348](https://github.com/koppor/jabref/issues/348) - We added a 'More options' section in the main table right click menu opening the preferences dialog. [#9432](https://github.com/JabRef/jabref/issues/9432) - When creating a new group, it inherits the icon of the parent group. [#10521](https://github.com/JabRef/jabref/pull/10521) +- We added quick settings for welcome tab. [#12664](https://github.com/JabRef/jabref/issues/12664) ### Changed diff --git a/jabgui/src/main/java/module-info.java b/jabgui/src/main/java/module-info.java index 9c7cf4505d8..28c09bbdfda 100644 --- a/jabgui/src/main/java/module-info.java +++ b/jabgui/src/main/java/module-info.java @@ -188,5 +188,6 @@ requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; requires com.dlsc.pdfviewfx; + requires org.jetbrains.annotations; // endregion } diff --git a/jabgui/src/main/java/org/jabref/gui/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/WelcomeTab.java deleted file mode 100644 index a48c7990598..00000000000 --- a/jabgui/src/main/java/org/jabref/gui/WelcomeTab.java +++ /dev/null @@ -1,294 +0,0 @@ -package org.jabref.gui; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; - -import javafx.collections.ListChangeListener; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Hyperlink; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; -import javafx.scene.control.Tab; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; - -import org.jabref.gui.actions.StandardActions; -import org.jabref.gui.edit.OpenBrowserAction; -import org.jabref.gui.frame.FileHistoryMenu; -import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.importer.NewDatabaseAction; -import org.jabref.gui.importer.actions.OpenDatabaseAction; -import org.jabref.gui.preferences.GuiPreferences; -import org.jabref.gui.undo.CountingUndoManager; -import org.jabref.gui.util.URLs; -import org.jabref.logic.ai.AiService; -import org.jabref.logic.importer.Importer; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.BuildInfo; -import org.jabref.logic.util.TaskExecutor; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.util.FileUpdateMonitor; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WelcomeTab extends Tab { - - private static final Logger LOGGER = LoggerFactory.getLogger(WelcomeTab.class); - - private final VBox recentLibrariesBox; - private final LibraryTabContainer tabContainer; - private final GuiPreferences preferences; - private final AiService aiService; - private final DialogService dialogService; - private final StateManager stateManager; - private final FileUpdateMonitor fileUpdateMonitor; - private final BibEntryTypesManager entryTypesManager; - private final CountingUndoManager undoManager; - private final ClipBoardManager clipBoardManager; - private final TaskExecutor taskExecutor; - private final FileHistoryMenu fileHistoryMenu; - private final BuildInfo buildInfo; - - public WelcomeTab(LibraryTabContainer tabContainer, - GuiPreferences preferences, - AiService aiService, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - FileHistoryMenu fileHistoryMenu, - BuildInfo buildInfo) { - - super(Localization.lang("Welcome")); - setClosable(true); - - this.tabContainer = tabContainer; - this.preferences = preferences; - this.aiService = aiService; - this.dialogService = dialogService; - this.stateManager = stateManager; - this.fileUpdateMonitor = fileUpdateMonitor; - this.entryTypesManager = entryTypesManager; - this.undoManager = undoManager; - this.clipBoardManager = clipBoardManager; - this.taskExecutor = taskExecutor; - this.fileHistoryMenu = fileHistoryMenu; - this.buildInfo = buildInfo; - - this.recentLibrariesBox = new VBox(10); - - VBox welcomeBox = createWelcomeBox(); - VBox startBox = createWelcomeStartBox(); - VBox recentBox = createWelcomeRecentBox(); - - VBox welcomePageContainer = new VBox(10); - welcomePageContainer.setAlignment(Pos.CENTER); - welcomePageContainer.getChildren().addAll(welcomeBox, startBox, recentBox); - - HBox welcomeMainContainer = new HBox(10); - welcomeMainContainer.setAlignment(Pos.CENTER); - welcomeMainContainer.setPadding(new Insets(10, 10, 10, 50)); - - welcomeMainContainer.getChildren().add(welcomePageContainer); - - BorderPane rootLayout = new BorderPane(); - rootLayout.setCenter(welcomeMainContainer); - rootLayout.setBottom(createFooter()); - - VBox container = new VBox(); - container.getChildren().add(rootLayout); - VBox.setVgrow(rootLayout, Priority.ALWAYS); - setContent(container); - } - - private VBox createWelcomeBox() { - Label welcomeLabel = new Label(Localization.lang("Welcome to JabRef")); - welcomeLabel.getStyleClass().add("welcome-label"); - - Label descriptionLabel = new Label(Localization.lang("Stay on top of your literature")); - descriptionLabel.getStyleClass().add("welcome-description-label"); - - return createVBoxContainer(welcomeLabel, descriptionLabel); - } - - private VBox createWelcomeStartBox() { - Label startLabel = new Label(Localization.lang("Start")); - startLabel.getStyleClass().add("welcome-header-label"); - - Hyperlink newLibraryLink = new Hyperlink(Localization.lang("New empty library")); - newLibraryLink.getStyleClass().add("welcome-hyperlink"); - newLibraryLink.setOnAction(e -> new NewDatabaseAction(tabContainer, preferences).execute()); - - Hyperlink openLibraryLink = new Hyperlink(Localization.lang("Open library")); - openLibraryLink.getStyleClass().add("welcome-hyperlink"); - openLibraryLink.setOnAction(e -> new OpenDatabaseAction(tabContainer, preferences, aiService, dialogService, - stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, - taskExecutor).execute()); - - Hyperlink openExampleLibraryLink = new Hyperlink(Localization.lang("New example library")); - openExampleLibraryLink.getStyleClass().add("welcome-hyperlink"); - openExampleLibraryLink.setOnAction(e -> { - try (InputStream in = WelcomeTab.class.getClassLoader().getResourceAsStream("Chocolate.bib")) { - if (in == null) { - LOGGER.warn("Example library file not found."); - return; - } - Reader reader = Importer.getReader(in); - BibtexParser bibtexParser = new BibtexParser(preferences.getImportFormatPreferences(), fileUpdateMonitor); - ParserResult result = bibtexParser.parse(reader); - BibDatabaseContext databaseContext = result.getDatabaseContext(); - LibraryTab libraryTab = LibraryTab.createLibraryTab(databaseContext, tabContainer, dialogService, aiService, - preferences, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor); - tabContainer.addTab(libraryTab, true); - } catch (IOException ex) { - LOGGER.error("Failed to load example library", ex); - } - }); - - return createVBoxContainer(startLabel, newLibraryLink, openExampleLibraryLink, openLibraryLink); - } - - private VBox createWelcomeRecentBox() { - Label recentLabel = new Label(Localization.lang("Recent")); - recentLabel.getStyleClass().add("welcome-header-label"); - - recentLibrariesBox.setAlignment(Pos.TOP_LEFT); - updateWelcomeRecentLibraries(); - - fileHistoryMenu.getItems().addListener((ListChangeListener) _ -> updateWelcomeRecentLibraries()); - - return createVBoxContainer(recentLabel, recentLibrariesBox); - } - - private void updateWelcomeRecentLibraries() { - if (fileHistoryMenu.getItems().isEmpty()) { - displayNoRecentLibrariesMessage(); - return; - } - - recentLibrariesBox.getChildren().clear(); - fileHistoryMenu.disableProperty().unbind(); - fileHistoryMenu.setDisable(false); - - for (MenuItem item : fileHistoryMenu.getItems()) { - Hyperlink recentLibraryLink = new Hyperlink(item.getText()); - recentLibraryLink.getStyleClass().add("welcome-hyperlink"); - recentLibraryLink.setOnAction(item.getOnAction()); - recentLibrariesBox.getChildren().add(recentLibraryLink); - } - } - - private void displayNoRecentLibrariesMessage() { - recentLibrariesBox.getChildren().clear(); - Label noRecentLibrariesLabel = new Label(Localization.lang("No recent libraries")); - noRecentLibrariesLabel.getStyleClass().add("welcome-no-recent-label"); - recentLibrariesBox.getChildren().add(noRecentLibrariesLabel); - - fileHistoryMenu.disableProperty().unbind(); - fileHistoryMenu.setDisable(true); - } - - private VBox createVBoxContainer(Node... nodes) { - VBox box = new VBox(10); - box.setAlignment(Pos.TOP_LEFT); - box.getChildren().addAll(nodes); - return box; - } - - private VBox createFooter() { - // Heading for the footer area - Label communityLabel = createFooterLabel(Localization.lang("Community")); - - HBox iconLinksContainer = createIconLinksContainer(); - HBox textLinksContainer = createTextLinksContainer(); - HBox versionContainer = createVersionContainer(); - - VBox footerBox = new VBox(10); - footerBox.setAlignment(Pos.CENTER); - footerBox.getChildren().addAll(communityLabel, iconLinksContainer, textLinksContainer, versionContainer); - footerBox.setPadding(new Insets(10, 0, 10, 0)); - footerBox.getStyleClass().add("welcome-footer-container"); - - return footerBox; - } - - private Label createFooterLabel(String text) { - Label label = new Label(text); - label.getStyleClass().add("welcome-footer-label"); - return label; - } - - private HBox createIconLinksContainer() { - HBox container = new HBox(10); - container.setAlignment(Pos.CENTER); - - Hyperlink onlineHelpLink = createFooterLink(Localization.lang("Online help"), StandardActions.HELP, IconTheme.JabRefIcons.HELP); - Hyperlink forumLink = createFooterLink(Localization.lang("Community forum"), StandardActions.OPEN_FORUM, IconTheme.JabRefIcons.FORUM); - Hyperlink mastodonLink = createFooterLink(Localization.lang("Mastodon"), StandardActions.OPEN_MASTODON, IconTheme.JabRefIcons.MASTODON); - Hyperlink linkedInLink = createFooterLink(Localization.lang("LinkedIn"), StandardActions.OPEN_LINKEDIN, IconTheme.JabRefIcons.LINKEDIN); - Hyperlink donationLink = createFooterLink(Localization.lang("Donation"), StandardActions.DONATE, IconTheme.JabRefIcons.DONATE); - - container.getChildren().addAll(onlineHelpLink, forumLink, mastodonLink, linkedInLink, donationLink); - return container; - } - - private HBox createTextLinksContainer() { - HBox container = new HBox(10); - container.setAlignment(Pos.CENTER); - - Hyperlink devVersionLink = createFooterLink(Localization.lang("Download development version"), StandardActions.OPEN_DEV_VERSION_LINK, null); - Hyperlink changelogLink = createFooterLink(Localization.lang("CHANGELOG"), StandardActions.OPEN_CHANGELOG, null); - - container.getChildren().addAll(devVersionLink, changelogLink); - return container; - } - - private Hyperlink createFooterLink(String text, StandardActions action, IconTheme.JabRefIcons icon) { - Hyperlink link = new Hyperlink(text); - link.getStyleClass().add("welcome-footer-link"); - - String url = switch (action) { - case HELP -> URLs.HELP_URL; - case OPEN_FORUM -> URLs.FORUM_URL; - case OPEN_MASTODON -> URLs.MASTODON_URL; - case OPEN_LINKEDIN -> URLs.LINKEDIN_URL; - case DONATE -> URLs.DONATE_URL; - case OPEN_DEV_VERSION_LINK -> URLs.DEV_VERSION_LINK_URL; - case OPEN_CHANGELOG -> URLs.CHANGELOG_URL; - default -> null; - }; - - if (url != null) { - link.setOnAction(e -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); - } - - if (icon != null) { - link.setGraphic(icon.getGraphicNode()); - } - - return link; - } - - private HBox createVersionContainer() { - HBox container = new HBox(10); - container.setAlignment(Pos.CENTER); - - Label versionLabel = new Label(Localization.lang("Current JabRef version: %0", buildInfo.version)); - versionLabel.getStyleClass().add("welcome-footer-version"); - - container.getChildren().add(versionLabel); - return container; - } -} diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 4280e9b5ff4..0cecc7fc2b9 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -32,7 +32,6 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; -import org.jabref.gui.WelcomeTab; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; @@ -53,6 +52,7 @@ import org.jabref.gui.undo.RedoAction; import org.jabref.gui.undo.UndoAction; import org.jabref.gui.util.BindingsHelper; +import org.jabref.gui.welcome.WelcomeTab; import org.jabref.logic.UiCommand; import org.jabref.logic.ai.AiService; import org.jabref.logic.journals.JournalAbbreviationRepository; diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java b/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java new file mode 100644 index 00000000000..76f17ff1387 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java @@ -0,0 +1,19 @@ +package org.jabref.gui.welcome; + +import javafx.scene.control.Button; + +import org.jabref.gui.icon.IconTheme; + +import org.jspecify.annotations.Nullable; + +public class QuickSettingsButton extends Button { + public QuickSettingsButton(String text, IconTheme.@Nullable JabRefIcons icon, Runnable action) { + super(text); + if (icon != null) { + setGraphic(icon.getGraphicNode()); + } + getStyleClass().add("quick-settings-button"); + setMaxWidth(Double.MAX_VALUE); + setOnAction(_ -> action.run()); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/ThemeWireFrameComponent.java b/jabgui/src/main/java/org/jabref/gui/welcome/ThemeWireFrameComponent.java new file mode 100644 index 00000000000..fb72b28c0e8 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/welcome/ThemeWireFrameComponent.java @@ -0,0 +1,55 @@ +package org.jabref.gui.welcome; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.layout.VBox; + +import com.airhacks.afterburner.views.ViewLoader; + +public class ThemeWireFrameComponent extends VBox { + + private final StringProperty themeType = new SimpleStringProperty(); + + public ThemeWireFrameComponent() { + ViewLoader.view(this) + .root(this) + .load(); + + themeType.addListener((_, _, newValue) -> { + if (newValue != null) { + updateTheme(); + } + }); + } + + public ThemeWireFrameComponent(String themeType) { + this(); + setThemeType(themeType); + } + + public void setThemeType(String themeType) { + this.themeType.set(themeType); + } + + @FXML + private void initialize() { + if (themeType.get() != null) { + updateTheme(); + } + } + + private void updateTheme() { + String theme = themeType.get(); + if (theme == null) { + return; + } + + getStyleClass().removeIf(styleClass -> + styleClass.startsWith("wireframe-light") || + styleClass.startsWith("wireframe-dark") || + styleClass.startsWith("wireframe-custom")); + + getStyleClass().add("wireframe-" + theme); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java new file mode 100644 index 00000000000..24773bb14ed --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -0,0 +1,764 @@ +package org.jabref.gui.welcome; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javafx.collections.ListChangeListener; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Tab; +import javafx.scene.control.TextField; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.LibraryTabContainer; +import org.jabref.gui.StateManager; +import org.jabref.gui.WorkspacePreferences; +import org.jabref.gui.actions.StandardActions; +import org.jabref.gui.edit.OpenBrowserAction; +import org.jabref.gui.frame.FileHistoryMenu; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIconView; +import org.jabref.gui.importer.NewDatabaseAction; +import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.push.PushToApplication; +import org.jabref.gui.push.PushToApplicationPreferences; +import org.jabref.gui.push.PushToApplications; +import org.jabref.gui.theme.Theme; +import org.jabref.gui.theme.ThemeTypes; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.URLs; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.importer.Importer; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WelcomeTab extends Tab { + + private static final Logger LOGGER = LoggerFactory.getLogger(WelcomeTab.class); + + private final VBox recentLibrariesBox; + private final LibraryTabContainer tabContainer; + private final GuiPreferences preferences; + private final AiService aiService; + private final DialogService dialogService; + private final StateManager stateManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + private final CountingUndoManager undoManager; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; + private final FileHistoryMenu fileHistoryMenu; + private final BuildInfo buildInfo; + + public WelcomeTab(LibraryTabContainer tabContainer, + GuiPreferences preferences, + AiService aiService, + DialogService dialogService, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor, + FileHistoryMenu fileHistoryMenu, + BuildInfo buildInfo) { + + super(Localization.lang("Welcome")); + setClosable(true); + + this.tabContainer = tabContainer; + this.preferences = preferences; + this.aiService = aiService; + this.dialogService = dialogService; + this.stateManager = stateManager; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + this.undoManager = undoManager; + this.clipBoardManager = clipBoardManager; + this.taskExecutor = taskExecutor; + this.fileHistoryMenu = fileHistoryMenu; + this.buildInfo = buildInfo; + + this.recentLibrariesBox = new VBox(); + recentLibrariesBox.getStyleClass().add("welcome-recent-libraries"); + + VBox topTitles = createTopTitles(); + HBox columnsContainer = createColumnsContainer(); + + VBox mainContainer = new VBox(); + mainContainer.getStyleClass().add("welcome-main-container"); + mainContainer.getChildren().addAll(topTitles, columnsContainer); + + VBox container = new VBox(); + container.getChildren().add(mainContainer); + VBox.setVgrow(mainContainer, Priority.ALWAYS); + container.setAlignment(Pos.CENTER); + setContent(container); + } + + private VBox createTopTitles() { + Label welcomeLabel = new Label(Localization.lang("Welcome to JabRef")); + welcomeLabel.getStyleClass().add("welcome-label"); + + Label descriptionLabel = new Label(Localization.lang("Stay on top of your literature")); + descriptionLabel.getStyleClass().add("welcome-description-label"); + + VBox topTitles = new VBox(); + topTitles.getStyleClass().add("welcome-top-titles"); + topTitles.getChildren().addAll(welcomeLabel, descriptionLabel); + + return topTitles; + } + + private HBox createColumnsContainer() { + VBox leftColumn = createLeftColumn(); + VBox rightColumn = createRightColumn(); + + HBox columnsContainer = new HBox(); + columnsContainer.getStyleClass().add("welcome-columns-container"); + + leftColumn.getStyleClass().add("welcome-left-column"); + rightColumn.getStyleClass().add("welcome-right-column"); + + HBox.setHgrow(leftColumn, Priority.ALWAYS); + HBox.setHgrow(rightColumn, Priority.ALWAYS); + columnsContainer.getChildren().addAll(leftColumn, rightColumn); + + return columnsContainer; + } + + private VBox createLeftColumn() { + VBox startBox = createWelcomeStartBox(); + VBox recentBox = createWelcomeRecentBox(); + + VBox leftColumn = new VBox(); + leftColumn.getStyleClass().add("welcome-content-column"); + leftColumn.getChildren().addAll(startBox, recentBox); + + return leftColumn; + } + + private VBox createRightColumn() { + VBox quickSettingsBox = createQuickSettingsBox(); + VBox communityBox = createCommunityBox(); + + VBox rightColumn = new VBox(); + rightColumn.getStyleClass().add("welcome-content-column"); + rightColumn.getChildren().addAll(quickSettingsBox, communityBox); + + return rightColumn; + } + + private VBox createQuickSettingsBox() { + Label header = new Label(Localization.lang("Quick Settings")); + header.getStyleClass().add("welcome-header-label"); + + VBox actions = new VBox(); + actions.getStyleClass().add("quick-settings-content"); + + QuickSettingsButton mainFileDirButton = new QuickSettingsButton( + Localization.lang("Main File Directory"), + IconTheme.JabRefIcons.FOLDER, + this::showMainFileDirectoryDialog + ); + + QuickSettingsButton themeButton = new QuickSettingsButton( + Localization.lang("Visual Theme"), + IconTheme.JabRefIcons.PREFERENCES, + this::showThemeDialog + ); + + QuickSettingsButton largeLibraryButton = new QuickSettingsButton( + Localization.lang("Optimize performance for large libraries"), + IconTheme.JabRefIcons.SELECTORS, + this::showLargeLibraryOptimizationDialog + ); + + QuickSettingsButton pushApplicationButton = new QuickSettingsButton( + Localization.lang("Configure Push to Application"), + IconTheme.JabRefIcons.APPLICATION_GENERIC, + this::showPushApplicationConfigurationDialog + ); + + actions.getChildren().addAll(mainFileDirButton, themeButton, largeLibraryButton, pushApplicationButton); + + return createVBoxContainer(header, actions); + } + + private VBox createCommunityBox() { + Label header = new Label(Localization.lang("Community")); + header.getStyleClass().add("welcome-header-label"); + + HBox iconLinksContainer = createIconLinksContainer(); + HBox textLinksContainer = createTextLinksContainer(); + HBox versionContainer = createVersionContainer(); + + VBox container = new VBox(); + container.getStyleClass().add("welcome-community-content"); + container.getChildren().addAll(iconLinksContainer, textLinksContainer, versionContainer); + + return createVBoxContainer(header, container); + } + + private void showMainFileDirectoryDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Main File Directory")); + dialog.setHeaderText(Localization.lang("Configure Main File Directory")); + + GridPane grid = new GridPane(); + grid.getStyleClass().add("quick-settings-dialog-container"); + + TextField pathField = new TextField(); + pathField.setPromptText(Localization.lang("Main File Directory")); + FilePreferences filePreferences = preferences.getFilePreferences(); + pathField.setText(filePreferences.getMainFileDirectory() + .map(Path::toString).orElse("")); + + Button browseButton = new Button(); + browseButton.setGraphic(IconTheme.JabRefIcons.OPEN.getGraphicNode()); + browseButton.getStyleClass().addAll("icon-button", "narrow"); + browseButton.setOnAction(_ -> { + DirectoryDialogConfiguration dirConfig = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); + dialogService.showDirectorySelectionDialog(dirConfig) + .ifPresent(selectedDir -> pathField.setText(selectedDir.toString())); + }); + + grid.add(new Label(Localization.lang("Main File Directory") + ":"), 0, 0); + grid.add(pathField, 1, 0); + grid.add(browseButton, 2, 0); + + dialog.getDialogPane().setContent(grid); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isPresent() && result.get() == ButtonType.OK) { + filePreferences.setMainFileDirectory(pathField.getText()); + filePreferences.setStoreFilesRelativeToBibFile(false); + } + } + + private void showThemeDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Visual Theme")); + dialog.setHeaderText(Localization.lang("Configure Visual Theme")); + + VBox mainContainer = new VBox(); + mainContainer.getStyleClass().add("theme-selection-container"); + mainContainer.setSpacing(12); + + ToggleGroup themeGroup = new ToggleGroup(); + HBox radioContainer = new HBox(); + radioContainer.setSpacing(8); + + WorkspacePreferences workspacePreferences = preferences.getWorkspacePreferences(); + Theme currentTheme = workspacePreferences.getTheme(); + + RadioButton lightRadio = new RadioButton(ThemeTypes.LIGHT.getDisplayName()); + lightRadio.setToggleGroup(themeGroup); + lightRadio.setUserData(ThemeTypes.LIGHT); + VBox lightBox = createThemeOption(lightRadio, new ThemeWireFrameComponent("light")); + radioContainer.getChildren().add(lightBox); + + RadioButton darkRadio = new RadioButton(ThemeTypes.DARK.getDisplayName()); + darkRadio.setToggleGroup(themeGroup); + darkRadio.setUserData(ThemeTypes.DARK); + VBox darkBox = createThemeOption(darkRadio, new ThemeWireFrameComponent("dark")); + radioContainer.getChildren().add(darkBox); + + RadioButton customRadio = new RadioButton(ThemeTypes.CUSTOM.getDisplayName()); + customRadio.setToggleGroup(themeGroup); + customRadio.setUserData(ThemeTypes.CUSTOM); + VBox customBox = createThemeOption(customRadio, new ThemeWireFrameComponent("custom")); + radioContainer.getChildren().add(customBox); + + switch (currentTheme.getType()) { + case DEFAULT -> lightRadio.setSelected(true); + case EMBEDDED -> darkRadio.setSelected(true); + case CUSTOM -> customRadio.setSelected(true); + } + + TextField customThemePath = new TextField(); + customThemePath.setPromptText(Localization.lang("Path to custom theme file")); + customThemePath.setText(currentTheme.getType() == Theme.Type.CUSTOM ? currentTheme.getName() : ""); + + Button browseButton = new Button(); + browseButton.setGraphic(new JabRefIconView(IconTheme.JabRefIcons.OPEN)); + browseButton.setTooltip(new Tooltip(Localization.lang("Browse"))); + browseButton.getStyleClass().addAll("icon-button", "narrow"); + browseButton.setPrefHeight(20.0); + browseButton.setPrefWidth(20.0); + + HBox customThemePathBox = new HBox(4.0, customThemePath, browseButton); + customThemePathBox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(customThemePath, Priority.ALWAYS); + + mainContainer.getChildren().add(radioContainer); + + boolean isCustomTheme = customRadio.isSelected(); + if (isCustomTheme) { + mainContainer.getChildren().add(customThemePathBox); + } + + themeGroup.selectedToggleProperty().addListener((_, _, newValue) -> { + boolean isCustom = newValue != null && newValue.getUserData() == ThemeTypes.CUSTOM; + boolean isCurrentlyVisible = mainContainer.getChildren().contains(customThemePathBox); + + if (isCustom && !isCurrentlyVisible) { + mainContainer.getChildren().add(customThemePathBox); + dialog.getDialogPane().getScene().getWindow().sizeToScene(); + } else if (!isCustom && isCurrentlyVisible) { + mainContainer.getChildren().remove(customThemePathBox); + dialog.getDialogPane().getScene().getWindow().sizeToScene(); + } + }); + + browseButton.setOnAction(_ -> { + String fileDir = customThemePath.getText().isEmpty() ? + preferences.getInternalPreferences().getLastPreferencesExportPath().toString() : + customThemePath.getText(); + + FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .addExtensionFilter(StandardFileType.CSS) + .withDefaultExtension(StandardFileType.CSS) + .withInitialDirectory(fileDir) + .build(); + + dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> + customThemePath.setText(file.toAbsolutePath().toString())); + }); + + dialog.getDialogPane().setContent(mainContainer); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isPresent() && result.get() == ButtonType.OK) { + Toggle selectedToggle = themeGroup.getSelectedToggle(); + if (selectedToggle != null) { + ThemeTypes selectedTheme = (ThemeTypes) selectedToggle.getUserData(); + Theme newTheme = switch (selectedTheme) { + case LIGHT -> Theme.light(); + case DARK -> Theme.dark(); + case CUSTOM -> { + String customPath = customThemePath.getText().trim(); + if (customPath.isEmpty()) { + dialogService.showErrorDialogAndWait( + Localization.lang("Error"), + Localization.lang("Please specify a custom theme file path.")); + yield null; + } + yield Theme.custom(customPath); + } + }; + if (newTheme != null) { + workspacePreferences.setTheme(newTheme); + } + } + } + } + + private void showLargeLibraryOptimizationDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Optimize performance for large libraries")); + dialog.setHeaderText(Localization.lang("Configure JabRef settings to optimize performance when working with large libraries")); + + VBox mainContainer = new VBox(); + mainContainer.getStyleClass().add("quick-settings-dialog-container"); + + Label explanationLabel = new Label(Localization.lang("Select which performance optimizations to apply:")); + explanationLabel.setWrapText(true); + explanationLabel.setMaxWidth(400); + + CheckBox disableFulltextIndexing = new CheckBox(Localization.lang("Disable fulltext indexing of linked files")); + disableFulltextIndexing.setSelected(true); + + CheckBox disableCreationDate = new CheckBox(Localization.lang("Disable adding creation date to new entries")); + disableCreationDate.setSelected(true); + + CheckBox disableModificationDate = new CheckBox(Localization.lang("Disable adding modification date to entries")); + disableModificationDate.setSelected(true); + + CheckBox disableAutosave = new CheckBox(Localization.lang("Disable autosave for local libraries")); + disableAutosave.setSelected(true); + + CheckBox disableGroupCount = new CheckBox(Localization.lang("Disable group entry count display")); + disableGroupCount.setSelected(true); + + VBox checkboxContainer = new VBox(); + checkboxContainer.setSpacing(8); + checkboxContainer.getChildren().addAll( + disableFulltextIndexing, + disableCreationDate, + disableModificationDate, + disableAutosave, + disableGroupCount + ); + + Hyperlink learnMoreLink = new Hyperlink(Localization.lang("Learn more about optimizing JabRef for large libraries")); + learnMoreLink.setOnAction(_ -> new OpenBrowserAction("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues", + dialogService, preferences.getExternalApplicationsPreferences()).execute()); + + mainContainer.getChildren().addAll(explanationLabel, checkboxContainer, learnMoreLink); + + dialog.getDialogPane().setContent(mainContainer); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isPresent() && result.get() == ButtonType.OK) { + optimizeForLargeLibraries(disableFulltextIndexing.isSelected(), disableCreationDate.isSelected(), disableModificationDate.isSelected(), disableAutosave.isSelected(), disableGroupCount.isSelected()); + } + } + + private void optimizeForLargeLibraries(boolean disableFulltextIndexing, + boolean disableCreationDate, + boolean disableModificationDate, + boolean disableAutosave, + boolean disableGroupCount) { + if (disableFulltextIndexing) { + preferences.getFilePreferences().setFulltextIndexLinkedFiles(false); + } + if (disableCreationDate) { + preferences.getTimestampPreferences().setAddCreationDate(false); + } + if (disableModificationDate) { + preferences.getTimestampPreferences().setAddModificationDate(false); + } + if (disableAutosave) { + preferences.getLibraryPreferences().setAutoSave(false); + } + if (disableGroupCount) { + preferences.getGroupsPreferences().setDisplayGroupCount(false); + } + } + + private void showPushApplicationConfigurationDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Configure Push to Application")); + dialog.setHeaderText(Localization.lang("Select your preferred text editor or LaTeX application")); + + VBox mainContainer = new VBox(); + mainContainer.setSpacing(16); + mainContainer.getStyleClass().add("quick-settings-dialog-container"); + + Label explanationLabel = new Label(Localization.lang("Detected applications are highlighted. Click to select and configure.")); + explanationLabel.setWrapText(true); + explanationLabel.setMaxWidth(400); + + ListView applicationsList = new ListView<>(); + applicationsList.setPrefHeight(200); + applicationsList.setPrefWidth(400); + + List allApplications = PushToApplications.getAllApplications(dialogService, preferences); + List detectedApplications = detectAvailableApplications(allApplications); + + List sortedApplications = new ArrayList<>(detectedApplications); + allApplications.stream() + .filter(app -> !detectedApplications.contains(app)) + .forEach(sortedApplications::add); + + applicationsList.getItems().addAll(sortedApplications); + applicationsList.setCellFactory(_ -> new PushApplicationListCell(detectedApplications)); + + PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); + String currentAppName = pushToApplicationPreferences.getActiveApplicationName(); + if (!currentAppName.isEmpty()) { + sortedApplications.stream() + .filter(app -> app.getDisplayName().equals(currentAppName)) + .findFirst() + .ifPresent(applicationsList.getSelectionModel()::select); + } + + mainContainer.getChildren().addAll(explanationLabel, applicationsList); + + dialog.getDialogPane().setContent(mainContainer); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isPresent() && result.get() == ButtonType.OK) { + PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); + if (selectedApp != null) { + pushToApplicationPreferences.setActiveApplicationName(selectedApp.getDisplayName()); + } + } + } + + private List detectAvailableApplications(List allApplications) { + return allApplications.stream().filter(this::isApplicationAvailable).toList(); + } + + private boolean isApplicationAvailable(PushToApplication application) { + String appName = application.getDisplayName().toLowerCase(); + + // TODO: How to best hardcode these names? + String[] possibleNames = switch (appName) { + case "emacs" -> new String[] {"emacs", "emacsclient"}; + case "lyx/kile" -> new String[] {"lyx", "kile"}; + case "texmaker" -> new String[] {"texmaker"}; + case "texstudio" -> new String[] {"texstudio"}; + case "texworks" -> new String[] {"texworks"}; + case "vim" -> new String[] {"vim", "nvim", "gvim"}; + case "winedt" -> new String[] {"winedt"}; + case "sublime text" -> new String[] {"subl", "sublime_text"}; + case "texshop" -> new String[] {"texshop"}; + case "vscode" -> new String[] {"code", "code-insiders"}; + default -> new String[] {appName.replace(" ", "").toLowerCase()}; + }; + + for (String executable : possibleNames) { + if (isExecutableInPath(executable)) { + return true; + } + } + + return false; + } + + private boolean isExecutableInPath(String executable) { + try { + ProcessBuilder pb = new ProcessBuilder("which", executable); + Process process = pb.start(); + return process.waitFor() == 0; + } catch (IOException | InterruptedException e) { + try { + ProcessBuilder pb = new ProcessBuilder("where", executable); + Process process = pb.start(); + return process.waitFor() == 0; + } catch (IOException | InterruptedException ex) { + return false; + } + } + } + + private static class PushApplicationListCell extends ListCell { + private final List detectedApplications; + + public PushApplicationListCell(List detectedApplications) { + this.detectedApplications = detectedApplications; + } + + @Override + protected void updateItem(PushToApplication application, boolean empty) { + super.updateItem(application, empty); + + if (empty || application == null) { + setText(null); + setGraphic(null); + getStyleClass().removeAll("detected-application"); + } else { + setText(application.getDisplayName()); + setGraphic(application.getApplicationIcon().getGraphicNode()); + + if (detectedApplications.contains(application)) { + if (!getStyleClass().contains("detected-application")) { + getStyleClass().add("detected-application"); + } + } else { + getStyleClass().removeAll("detected-application"); + } + } + } + } + + private VBox createThemeOption(RadioButton radio, Node wireframe) { + VBox container = new VBox(); + container.setSpacing(12); + container.setAlignment(Pos.CENTER_LEFT); + container.getStyleClass().add("theme-option"); + container.getChildren().addAll(radio, wireframe); + return container; + } + + private VBox createWelcomeStartBox() { + Label header = new Label(Localization.lang("Start")); + header.getStyleClass().add("welcome-header-label"); + + Hyperlink newLibraryLink = createActionLink(Localization.lang("New empty library"), + () -> new NewDatabaseAction(tabContainer, preferences).execute()); + + Hyperlink openLibraryLink = createActionLink(Localization.lang("Open library"), + () -> new OpenDatabaseAction(tabContainer, preferences, aiService, dialogService, + stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, + taskExecutor).execute()); + + Hyperlink openExampleLibraryLink = createActionLink(Localization.lang("New example library"), + this::openExampleLibrary); + + VBox container = new VBox(); + container.getStyleClass().add("welcome-links-content"); + container.getChildren().addAll(newLibraryLink, openExampleLibraryLink, openLibraryLink); + + return createVBoxContainer(header, container); + } + + private VBox createWelcomeRecentBox() { + Label header = new Label(Localization.lang("Recent")); + header.getStyleClass().add("welcome-header-label"); + + updateWelcomeRecentLibraries(); + fileHistoryMenu.getItems().addListener((ListChangeListener) _ -> updateWelcomeRecentLibraries()); + + return createVBoxContainer(header, recentLibrariesBox); + } + + private Hyperlink createActionLink(String text, Runnable action) { + Hyperlink link = new Hyperlink(text); + link.getStyleClass().add("welcome-hyperlink"); + link.setOnAction(_ -> action.run()); + return link; + } + + private void openExampleLibrary() { + try (InputStream in = WelcomeTab.class.getClassLoader().getResourceAsStream("Chocolate.bib")) { + if (in == null) { + LOGGER.warn("Example library file not found."); + return; + } + Reader reader = Importer.getReader(in); + BibtexParser bibtexParser = new BibtexParser(preferences.getImportFormatPreferences(), fileUpdateMonitor); + ParserResult result = bibtexParser.parse(reader); + BibDatabaseContext databaseContext = result.getDatabaseContext(); + LibraryTab libraryTab = LibraryTab.createLibraryTab(databaseContext, tabContainer, dialogService, aiService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor); + tabContainer.addTab(libraryTab, true); + } catch (IOException ex) { + LOGGER.error("Failed to load example library", ex); + } + } + + private void updateWelcomeRecentLibraries() { + if (fileHistoryMenu.getItems().isEmpty()) { + displayNoRecentLibrariesMessage(); + return; + } + + recentLibrariesBox.getChildren().clear(); + recentLibrariesBox.getStyleClass().add("welcome-links-content"); + fileHistoryMenu.disableProperty().unbind(); + fileHistoryMenu.setDisable(false); + + for (MenuItem item : fileHistoryMenu.getItems()) { + Hyperlink recentLibraryLink = new Hyperlink(item.getText()); + recentLibraryLink.getStyleClass().add("welcome-hyperlink"); + recentLibraryLink.setOnAction(item.getOnAction()); + recentLibrariesBox.getChildren().add(recentLibraryLink); + } + } + + private void displayNoRecentLibrariesMessage() { + recentLibrariesBox.getChildren().clear(); + Label noRecentLibrariesLabel = new Label(Localization.lang("No recent libraries")); + noRecentLibrariesLabel.getStyleClass().add("welcome-no-recent-label"); + recentLibrariesBox.getChildren().add(noRecentLibrariesLabel); + + fileHistoryMenu.disableProperty().unbind(); + fileHistoryMenu.setDisable(true); + } + + private VBox createVBoxContainer(Node... nodes) { + VBox box = new VBox(); + box.getStyleClass().add("welcome-section"); + box.getChildren().addAll(nodes); + return box; + } + + private HBox createIconLinksContainer() { + HBox container = new HBox(); + container.getStyleClass().add("welcome-community-icons"); + + Hyperlink onlineHelpLink = createFooterLink(Localization.lang("Online help"), StandardActions.HELP, IconTheme.JabRefIcons.HELP); + Hyperlink forumLink = createFooterLink(Localization.lang("Community forum"), StandardActions.OPEN_FORUM, IconTheme.JabRefIcons.FORUM); + Hyperlink mastodonLink = createFooterLink(Localization.lang("Mastodon"), StandardActions.OPEN_MASTODON, IconTheme.JabRefIcons.MASTODON); + Hyperlink linkedInLink = createFooterLink(Localization.lang("LinkedIn"), StandardActions.OPEN_LINKEDIN, IconTheme.JabRefIcons.LINKEDIN); + Hyperlink donationLink = createFooterLink(Localization.lang("Donation"), StandardActions.DONATE, IconTheme.JabRefIcons.DONATE); + + container.getChildren().addAll(onlineHelpLink, forumLink, mastodonLink, linkedInLink, donationLink); + return container; + } + + private HBox createTextLinksContainer() { + HBox container = new HBox(); + container.getStyleClass().add("welcome-community-links"); + + Hyperlink devVersionLink = createFooterLink(Localization.lang("Download development version"), StandardActions.OPEN_DEV_VERSION_LINK, null); + Hyperlink changelogLink = createFooterLink(Localization.lang("CHANGELOG"), StandardActions.OPEN_CHANGELOG, null); + + container.getChildren().addAll(devVersionLink, changelogLink); + return container; + } + + private Hyperlink createFooterLink(String text, StandardActions action, IconTheme.JabRefIcons icon) { + Hyperlink link = new Hyperlink(text); + link.getStyleClass().add("welcome-community-link"); + + String url = switch (action) { + case HELP -> URLs.HELP_URL; + case OPEN_FORUM -> URLs.FORUM_URL; + case OPEN_MASTODON -> URLs.MASTODON_URL; + case OPEN_LINKEDIN -> URLs.LINKEDIN_URL; + case DONATE -> URLs.DONATE_URL; + case OPEN_DEV_VERSION_LINK -> URLs.DEV_VERSION_LINK_URL; + case OPEN_CHANGELOG -> URLs.CHANGELOG_URL; + default -> null; + }; + + if (url != null) { + link.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); + } + + if (icon != null) { + link.setGraphic(icon.getGraphicNode()); + } + + return link; + } + + private HBox createVersionContainer() { + HBox container = new HBox(); + container.getStyleClass().add("welcome-community-version"); + + Label versionLabel = new Label(Localization.lang("Current JabRef version: %0", buildInfo.version)); + versionLabel.getStyleClass().add("welcome-community-version-text"); + + container.getChildren().add(versionLabel); + return container; + } +} diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 640c743c0e8..5f39298798f 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -3,7 +3,7 @@ -jr-row-odd-background: -fx-control-inner-background-alt; -jr-row-even-background: -fx-control-inner-background; /* - On light theme, the text is hard to see when it's on top of the accent color. This is an alternative lighter accent color + On me, the text is hard to see when it's on top of the accent color. This is an alternative lighter accent color for better text visibility. */ -jr-accent-alt: derive(-jr-accent, 15%); @@ -1571,35 +1571,45 @@ We want to have a look that matches our icons in the tool-bar */ .main-table .table-row-cell:matching-search-and-groups { -fx-background-color: -jr-match-1-even; } + .main-table .table-row-cell:matching-search-and-groups > .table-cell { -fx-text-fill: -jr-match-1-text-color; } + .main-table .table-row-cell:matching-search-and-groups:focused > .table-cell { -fx-text-fill: -fx-focused-text-base-color; } + .main-table .table-row-cell:matching-search-and-groups:focused:hover > .table-cell { -fx-text-fill: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-search-and-groups:hover > .table-cell { -fx-text-fill: -jr-hover-text; } + .main-table .table-row-cell:matching-search-and-groups > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-match-1-text-color; } + .main-table .table-row-cell:matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-hover-text; } + .main-table .table-row-cell:matching-search-and-groups:odd { -fx-background-color: -jr-match-1-odd; } + .main-table .table-row-cell:matching-search-and-groups:selected, .main-table .table-row-cell:matching-search-and-groups:focused, .main-table .table-row-cell:matching-search-and-groups:focused:hover { -fx-background-color: -jr-selected; } + .main-table .table-row-cell:matching-search-and-groups:hover { -fx-background-color: -jr-hover; } @@ -1607,36 +1617,46 @@ We want to have a look that matches our icons in the tool-bar */ .main-table .table-row-cell:matching-search-not-groups { -fx-background-color: -jr-match-2-even; } + .main-table .table-row-cell:matching-search-not-groups > .table-cell { -fx-text-fill: -jr-match-2-text-color; } + .main-table .table-row-cell:matching-search-not-groups:focused > .table-cell { -fx-text-fill: -fx-focused-text-base-color; } + .main-table .table-row-cell:matching-search-not-groups:focused:hover > .table-cell { -fx-text-fill: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-search-not-groups:focused:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-search-not-groups:hover > .table-cell { -fx-text-fill: -jr-hover-text; } + .main-table .table-row-cell:matching-search-not-groups > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-match-2-text-color; } + .main-table .table-row-cell:matching-search-not-groups:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-hover-text; } + .main-table .table-row-cell:matching-search-not-groups:odd { -fx-background-color: -jr-match-2-odd; } + .main-table .table-row-cell:matching-search-not-groups:selected, .main-table .table-row-cell:matching-search-not-groups:focused, .main-table .table-row-cell:matching-search-not-groups:focused:hover, .main-table .table-row-cell:matching-search-not-groups:hover { -fx-background-color: -jr-selected; } + .main-table .table-row-cell:matching-search-not-groups:hover { -fx-background-color: -jr-hover; } @@ -1644,36 +1664,46 @@ We want to have a look that matches our icons in the tool-bar */ .main-table .table-row-cell:matching-groups-not-search { -fx-background-color: -jr-match-3-even; } + .main-table .table-row-cell:matching-groups-not-search > .table-cell { -fx-text-fill: -jr-match-3-text-color; } + .main-table .table-row-cell:matching-groups-not-search:focused > .table-cell { -fx-text-fill: -fx-focused-text-base-color; } + .main-table .table-row-cell:matching-groups-not-search:focused:hover > .table-cell { -fx-text-fill: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-groups-not-search:focused:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:matching-groups-not-search:hover > .table-cell { -fx-text-fill: -jr-hover-text; } + .main-table .table-row-cell:matching-groups-not-search > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-match-3-text-color; } + .main-table .table-row-cell:matching-groups-not-search:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-hover-text; } + .main-table .table-row-cell:matching-groups-not-search:odd { -fx-background-color: -jr-match-3-odd; } + .main-table .table-row-cell:matching-groups-not-search:selected, .main-table .table-row-cell:matching-groups-not-search:focused, .main-table .table-row-cell:matching-groups-not-search:focused:hover, .main-table .table-row-cell:matching-groups-not-search:hover { -fx-background-color: -jr-selected; } + .main-table .table-row-cell:matching-groups-not-search:hover { -fx-background-color: -jr-hover; } @@ -1681,35 +1711,45 @@ We want to have a look that matches our icons in the tool-bar */ .main-table .table-row-cell:not-matching-search-and-groups { -fx-background-color: -jr-match-4-even; } + .main-table .table-row-cell:not-matching-search-and-groups > .table-cell { -fx-text-fill: -jr-match-4-text-color; } + .main-table .table-row-cell:not-matching-search-and-groups:focused > .table-cell { -fx-text-fill: -fx-focused-text-base-color; } + .main-table .table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell { -fx-text-fill: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-maintable-focused-hover-text; } + .main-table .table-row-cell:not-matching-search-and-groups:hover > .table-cell { -fx-text-fill: -jr-hover-text; } + .main-table .table-row-cell:not-matching-search-and-groups > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-match-4-text-color; } + .main-table.table-row-cell:not-matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { -fx-icon-color: -jr-hover-text; } + .main-table .table-row-cell:not-matching-search-and-groups:odd { -fx-background-color: -jr-match-4-odd; } + .main-table .table-row-cell:not-matching-search-and-groups:selected, .main-table .table-row-cell:not-matching-search-and-groups:focused, .main-table .table-row-cell:not-matching-search-and-groups:focused:hover { -fx-background-color: -jr-selected; } + .main-table .table-row-cell:not-matching-search-and-groups:hover { -fx-background-color: -jr-hover; } @@ -1768,35 +1808,126 @@ We want to have a look that matches our icons in the tool-bar */ -fx-text-fill: -jr-theme; } -.welcome-footer-container { - -fx-padding: 15px; + +/* Welcome Tab Layout Styles */ +.welcome-main-container { + -fx-max-width: 768px; + -fx-spacing: 28px; -fx-alignment: center; - -fx-spacing: 15px; } -.welcome-footer-label { - -fx-font-size: 2.25em; - -fx-text-fill: -jr-theme-text; - -fx-font-family: "Arial"; +.welcome-top-titles { + -fx-spacing: 10px; + -fx-alignment: left; + -fx-padding: 0 0 20px 0; } -.welcome-footer-link { - -fx-font-size: 1.2em; +.welcome-columns-container { + -fx-spacing: 40px; + -fx-alignment: top-center; +} + +.welcome-left-column, +.welcome-right-column { + -fx-min-width: 0; + -fx-pref-width: 50%; + -fx-max-width: 600px; +} + +.welcome-content-column { + -fx-spacing: 25px; + -fx-alignment: top-left; +} + +.welcome-section { + -fx-spacing: 12px; + -fx-alignment: top-left; +} + +.welcome-links-content { + -fx-spacing: 8px; + -fx-alignment: top-left; +} + +/* Quick Settings */ + +.quick-settings-content { + -fx-spacing: 8px; + -fx-alignment: top-center; +} + +.quick-settings-button { + -fx-background-color: derive(-jr-base, 10%); + -fx-border-color: -jr-gray-1; + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-background-radius: 4px; + -fx-padding: 12px 16px; + -fx-alignment: center-left; + -fx-graphic-text-gap: 8px; + -fx-font-size: 1.0em; + -fx-text-fill: -fx-text-base-color; + -fx-cursor: hand; +} + +.quick-settings-button:hover { + -fx-background-color: -jr-hover; + -fx-border-color: -jr-accent; +} + +.quick-settings-button .glyph-icon, +.quick-settings-button .ikonli-font-icon { + -fx-icon-color: -jr-theme-text; + -fx-fill: -jr-theme-text; + -fx-font-size: 1.1em; +} + +.quick-settings-dialog-container { + -fx-hgap: 8px; + -fx-vgap: 8px; + -fx-padding: 16px; + -fx-spacing: 16px; +} + +/* Community Section */ +.welcome-community-content { + -fx-spacing: 12px; + -fx-alignment: top-left; +} + +.welcome-community-icons { + -fx-spacing: 12px; + -fx-alignment: center-left; +} + +.welcome-community-links { + -fx-spacing: 15px; + -fx-alignment: center-left; +} + +.welcome-community-link { + -fx-font-size: 1.0em; -fx-text-fill: -jr-theme; -fx-font-family: "Arial"; } -.welcome-footer-link:hover { +.welcome-community-link:hover { -fx-underline: true; -fx-text-fill: derive(-jr-theme, -20%); } -.welcome-footer-version { - -fx-font-size: 1.2em; - -fx-text-fill: -jr-theme; +.welcome-community-version { + -fx-alignment: center-left; + -fx-padding: 5px 0 0 0; +} + +.welcome-community-version-text { + -fx-font-size: 0.9em; + -fx-text-fill: -jr-gray-2; -fx-font-family: "Arial"; } + /* AboutDialog */ #aboutDialog .about-heading { -fx-font-size: 30; @@ -1984,7 +2115,7 @@ We want to have a look that matches our icons in the tool-bar */ #entryEditor #bibtexSourceCodeArea .search { -rtfx-background-color: #ffff00; - -fx-fill: #7800A9 ; + -fx-fill: #7800A9; -fx-font-size: 1.2em; -fx-font-weight: bolder; } @@ -2454,7 +2585,7 @@ journalInfo .grid-cell-b { -fx-border-width: 2.5; } -.three-way-merge .styled-text-area .text{ +.three-way-merge .styled-text-area .text { -fx-fill: -fx-text-background-color; } @@ -2474,7 +2605,7 @@ journalInfo .grid-cell-b { -fx-background-color: -jr-menu-background; } -.three-way-merge .merge-header-cell .label{ +.three-way-merge .merge-header-cell .label { -fx-font-weight: bold; -fx-padding: 1, 0, 1, 0; } @@ -2482,7 +2613,7 @@ journalInfo .grid-cell-b { .three-way-merge .field-name .glyph-icon, .three-way-merge .field-name .ikonli-font-icon { -fx-icon-size: 17; - -fx-icon-color: -jr-theme-text; + -fx-icon-color: -jr-theme-text; } /* Miscellaneous */ @@ -2496,7 +2627,9 @@ journalInfo .grid-cell-b { } #styleSelectDialog .currentStyleNameLabel { - -fx-font-size: 1em; -fx-font-weight: bold; -fx-text-fill: -jr-theme; + -fx-font-size: 1em; + -fx-font-weight: bold; + -fx-text-fill: -jr-theme; } .exampleQuestionStyle { @@ -2519,9 +2652,241 @@ journalInfo .grid-cell-b { -fx-background-color: transparent; } -.text-button-blue{ +.text-button-blue { -fx-background-color: transparent; -fx-text-fill: -jr-theme; -fx-font-size: 1.25em; -fx-border-color: transparent; } + +/* Theme Selection Wireframes */ +.theme-selection-container { + -fx-padding: 10px; + +} + +.theme-option { + -fx-padding: 8px; + -fx-border-radius: 4px; + -fx-background-radius: 4px; +} + +.theme-option:hover { + -fx-background-color: rgba(0, 0, 0, 0.05); +} + +/* Wireframe base styles */ +.wireframe-container { + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-background-radius: 4px; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 2, 0, 0, 1); +} + +.wireframe-menubar { + -fx-padding: 3px 1px; +} + +.wireframe-menu-item { + -fx-border-radius: 1px; + -fx-background-radius: 1px; +} + +.wireframe-toolbar { + -fx-padding: 2px; + -fx-spacing: 6px; +} + +.wireframe-tool-item { + -fx-border-radius: 1px; + -fx-background-radius: 1px; +} + +.wireframe-search { + -fx-border-width: 0.5px; + -fx-border-radius: 2px; + -fx-background-radius: 2px; +} + +.wireframe-search-field { + -fx-border-radius: 1px; + -fx-background-radius: 1px; +} + +.wireframe-sidebar { + -fx-padding: 4px 2px; +} + +.wireframe-sidebar-item { + -fx-border-radius: 1px; + -fx-background-radius: 1px; +} + +.wireframe-welcome-tab-area { + -fx-padding: 4px; + -fx-alignment: center; +} + +.wireframe-welcome-tab-item { + -fx-border-radius: 1px; + -fx-background-radius: 1px; +} + +/* Wireframe light colors */ +.wireframe-light .wireframe-container { + -fx-border-color: #ccc; +} + +.wireframe-light .wireframe-menubar { + -fx-background-color: #f9f9f9; +} + +.wireframe-light .wireframe-menu-item { + -fx-background-color: -jr-gray-1; +} + +.wireframe-light .wireframe-toolbar { + -fx-background-color: #f9f9f9; +} + +.wireframe-light .wireframe-tool-item { + -fx-background-color: #50618f; +} + +.wireframe-light .wireframe-search { + -fx-background-color: #ffffff; + -fx-border-color: #dddddd; +} + +.wireframe-light .wireframe-search-field { + -fx-background-color: #f8f8f8; +} + +.wireframe-light .wireframe-sidebar { + -fx-background-color: #dddddd; +} + +.wireframe-light .wireframe-sidebar-item { + -fx-background-color: #50618f; +} + +.wireframe-light .wireframe-welcome-tab-area { + -fx-background-color: #f3f3f3; +} + +.wireframe-light .wireframe-welcome-tab-item { + -fx-background-color: #dddddd; +} + +/* Wireframe dark colors */ +.wireframe-dark .wireframe-container { + -fx-border-color: #424758; +} + +.wireframe-dark .wireframe-menubar { + -fx-background-color: #141824; +} + +.wireframe-dark .wireframe-menu-item { + -fx-background-color: #424758; +} + +.wireframe-dark .wireframe-toolbar { + -fx-background-color: #141824; +} + +.wireframe-dark .wireframe-tool-item { + -fx-background-color: #2c9490; +} + +.wireframe-dark .wireframe-search { + -fx-background-color: #2c2e3b; + -fx-border-color: #424758; +} + +.wireframe-dark .wireframe-search-field { + -fx-background-color: #424758; +} + +.wireframe-dark .wireframe-sidebar { + -fx-background-color: #212330; +} + +.wireframe-dark .wireframe-sidebar-item { + -fx-background-color: #2c9490; +} + +.wireframe-dark .wireframe-welcome-tab-area { + -fx-background-color: #272b38; +} + +.wireframe-dark .wireframe-welcome-tab-item { + -fx-background-color: #7d8591; +} + +/* Wireframe custom theme colors */ +.wireframe-custom .wireframe-container { + -fx-border-color: #50618F; +} + +.wireframe-custom .wireframe-menubar { + -fx-background-color: #f5ffe5; +} + +.wireframe-custom .wireframe-menu-item { + -fx-background-color: #346963; +} + +.wireframe-custom .wireframe-toolbar { + -fx-background-color: #f5ffe5; +} + +.wireframe-custom .wireframe-tool-item { + -fx-background-color: #2E838C; +} + +.wireframe-custom .wireframe-search { + -fx-background-color: #ffffff; + -fx-border-color: #2E838C; +} + +.wireframe-custom .wireframe-search-field { + -fx-background-color: #f0f3ff; +} + +.wireframe-custom .wireframe-sidebar { + -fx-background-color: #e1ebd1; +} + +.wireframe-custom .wireframe-sidebar-item { + -fx-background-color: #2E838C; +} + +.wireframe-custom .wireframe-welcome-tab-area { + -fx-background-color: #E8F2D8; +} + +.wireframe-custom .wireframe-welcome-tab-item { + -fx-background-color: #2E838C; +} + +/* Push Application Configuration */ +.detected-application { + -fx-background-color: derive(-jr-accent, 80%); + -fx-border-color: -jr-accent; + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-background-radius: 4px; + -fx-font-weight: bold; +} + +.detected-application:selected { + -fx-background-color: -jr-accent; + -fx-text-fill: white; +} + +.detected-application .glyph-icon, +.detected-application .ikonli-font-icon { + -fx-icon-color: -jr-theme-text; + -fx-fill: -jr-theme-text; +} diff --git a/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml b/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml new file mode 100644 index 00000000000..1ed3189783e --- /dev/null +++ b/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 131266ce455..2a95601aa98 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -2978,3 +2978,32 @@ File\ '%0'\ already\ exists.\ Use\ -f\ or\ --force\ to\ overwrite.=File '%0' alr Pseudonymizing\ library\ '%0'...=Pseudonymizing library '%0'... Invalid\ output\ file\ type\ provided.=Invalid output file type provided. Saved\ %0.=Saved %0. + +# Quick Settings +Quick\ Settings=Quick Settings +Main\ File\ Directory=Main File Directory +Visual\ Theme=Visual Theme +Configure\ Main\ File\ Directory=Configure Main File Directory +Configure\ Visual\ Theme=Configure Visual Theme +Custom\ theme\ path=Custom theme path +Path\ to\ custom\ theme\ file=Path to custom theme file +Please\ specify\ a\ custom\ theme\ file\ path.=Please specify a custom theme file path. + +Optimize\ performance\ for\ large\ libraries=Optimize performance for large libraries +Configure\ JabRef\ settings\ to\ optimize\ performance\ when\ working\ with\ large\ libraries=Configure JabRef settings to optimize performance when working with large libraries +Optimizing\ performance\ settings\ for\ large\ libraries...=Optimizing performance settings for large libraries... +Performance\ settings\ optimized\ for\ large\ libraries=Performance settings optimized for large libraries +Learn\ more\ about\ optimizing\ JabRef\ for\ large\ libraries=Learn more about optimizing JabRef for large libraries + +Select\ which\ performance\ optimizations\ to\ apply\:=Select which performance optimizations to apply: +Disable\ fulltext\ indexing\ of\ linked\ files=Disable fulltext indexing of linked files +Disable\ adding\ creation\ date\ to\ new\ entries=Disable adding creation date to new entries +Disable\ adding\ modification\ date\ to\ entries=Disable adding modification date to entries +Disable\ autosave\ for\ local\ libraries=Disable autosave for local libraries +Disable\ group\ entry\ count\ display=Disable group entry count display + +Configure\ Push\ to\ Application=Configure Push to Application +Select\ your\ preferred\ text\ editor\ or\ LaTeX\ application=Select your preferred text editor or LaTeX application +Detected\ applications\ are\ highlighted.\ Click\ to\ select\ and\ configure.=Detected applications are highlighted. Click to select and configure. +Push\ Application=Push Application +Push\ application\ set\ to=Push application set to