From 22e382218aaf8166e788ad7c1328a376661ecb32 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Sun, 29 Jun 2025 00:14:47 -0400 Subject: [PATCH 01/21] Initial integration of QuickSettings --- .../main/java/org/jabref/gui/WelcomeTab.java | 294 ---------- .../org/jabref/gui/frame/JabRefFrame.java | 2 +- .../gui/welcome/QuickSettingsButton.java | 19 + .../org/jabref/gui/welcome/WelcomeTab.java | 519 ++++++++++++++++++ .../main/resources/org/jabref/gui/Base.css | 116 +++- .../main/resources/l10n/JabRef_en.properties | 10 + 6 files changed, 652 insertions(+), 308 deletions(-) delete mode 100644 jabgui/src/main/java/org/jabref/gui/WelcomeTab.java create mode 100644 jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java create mode 100644 jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java 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..d7fac2caa34 --- /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(e -> action.run()); + } +} 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..4f0ee3ae4a9 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -0,0 +1,519 @@ +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.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.ComboBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TextField; +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.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 + ); + + actions.getChildren().addAll(mainFileDirButton, themeButton); + + 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-grid"); + + 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")); + + GridPane grid = new GridPane(); + grid.getStyleClass().add("quick-settings-dialog-grid"); + + ComboBox themeCombo = new ComboBox<>(); + themeCombo.getItems().addAll(ThemeTypes.values()); + + ListCell themeTypesListCell = new ListCell<>() { + @Override + protected void updateItem(ThemeTypes item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + setText(item.getDisplayName()); + } + } + }; + themeCombo.setCellFactory(_ -> themeTypesListCell); + themeCombo.setButtonCell(themeTypesListCell); + + WorkspacePreferences workspacePreferences = preferences.getWorkspacePreferences(); + Theme currentTheme = workspacePreferences.getTheme(); + switch (currentTheme.getType()) { + case DEFAULT -> themeCombo.setValue(ThemeTypes.LIGHT); + case EMBEDDED -> themeCombo.setValue(ThemeTypes.DARK); + case CUSTOM -> themeCombo.setValue(ThemeTypes.CUSTOM); + } + + 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 customThemeBox = new HBox(4.0, customThemePath, browseButton); + customThemeBox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(customThemePath, Priority.ALWAYS); + + boolean isCustomTheme = themeCombo.getValue() == ThemeTypes.CUSTOM; + customThemePath.setDisable(!isCustomTheme); + browseButton.setDisable(!isCustomTheme); + + themeCombo.valueProperty().addListener((_, _, newValue) -> { + boolean isCustom = newValue == ThemeTypes.CUSTOM; + customThemePath.setDisable(!isCustom); + browseButton.setDisable(!isCustom); + }); + + 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())); + }); + + grid.add(new Label(Localization.lang("Visual Theme") + ":"), 0, 0); + grid.add(themeCombo, 1, 0); + grid.add(new Label(Localization.lang("Custom theme path") + ":"), 0, 1); + grid.add(customThemeBox, 1, 1); + + dialog.getDialogPane().setContent(grid); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isPresent() && result.get() == ButtonType.OK) { + ThemeTypes selectedTheme = themeCombo.getValue(); + if (selectedTheme != null) { + 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 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..7ac106af772 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -1768,35 +1768,125 @@ 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: 1024px; + -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-grid { + -fx-hgap: 10px; + -fx-vgap: 10px; + -fx-padding: 20px; +} + +/* 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; diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 131266ce455..10be22525ba 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -2978,3 +2978,13 @@ 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. From 7958489d525f5d243f4009c77c70b976c5201b17 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Sun, 29 Jun 2025 10:05:23 -0400 Subject: [PATCH 02/21] Improve the look and feel of theme quick settings --- jabgui/src/main/java/module-info.java | 1 + .../gui/welcome/ThemeWireFrameComponent.java | 55 ++++ .../org/jabref/gui/welcome/WelcomeTab.java | 103 ++++--- .../main/resources/org/jabref/gui/Base.css | 269 +++++++++++++++++- .../gui/welcome/ThemeWireFrameComponent.fxml | 212 ++++++++++++++ 5 files changed, 592 insertions(+), 48 deletions(-) create mode 100644 jabgui/src/main/java/org/jabref/gui/welcome/ThemeWireFrameComponent.java create mode 100644 jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml diff --git a/jabgui/src/main/java/module-info.java b/jabgui/src/main/java/module-info.java index 0a529f52082..7968419c8bd 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/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 index 4f0ee3ae4a9..336838000bb 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -11,14 +11,15 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; -import javafx.scene.control.ComboBox; import javafx.scene.control.Dialog; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; -import javafx.scene.control.ListCell; 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; @@ -261,32 +262,39 @@ private void showThemeDialog() { dialog.setTitle(Localization.lang("Visual Theme")); dialog.setHeaderText(Localization.lang("Configure Visual Theme")); - GridPane grid = new GridPane(); - grid.getStyleClass().add("quick-settings-dialog-grid"); + VBox mainContainer = new VBox(); + mainContainer.getStyleClass().add("theme-selection-container"); + mainContainer.setSpacing(12); - ComboBox themeCombo = new ComboBox<>(); - themeCombo.getItems().addAll(ThemeTypes.values()); - - ListCell themeTypesListCell = new ListCell<>() { - @Override - protected void updateItem(ThemeTypes item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null) { - setText(null); - } else { - setText(item.getDisplayName()); - } - } - }; - themeCombo.setCellFactory(_ -> themeTypesListCell); - themeCombo.setButtonCell(themeTypesListCell); + 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 -> themeCombo.setValue(ThemeTypes.LIGHT); - case EMBEDDED -> themeCombo.setValue(ThemeTypes.DARK); - case CUSTOM -> themeCombo.setValue(ThemeTypes.CUSTOM); + case DEFAULT -> lightRadio.setSelected(true); + case EMBEDDED -> darkRadio.setSelected(true); + case CUSTOM -> customRadio.setSelected(true); } TextField customThemePath = new TextField(); @@ -300,18 +308,28 @@ protected void updateItem(ThemeTypes item, boolean empty) { browseButton.setPrefHeight(20.0); browseButton.setPrefWidth(20.0); - HBox customThemeBox = new HBox(4.0, customThemePath, browseButton); - customThemeBox.setAlignment(Pos.CENTER_LEFT); + HBox customThemePathBox = new HBox(4.0, customThemePath, browseButton); + customThemePathBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(customThemePath, Priority.ALWAYS); - boolean isCustomTheme = themeCombo.getValue() == ThemeTypes.CUSTOM; - customThemePath.setDisable(!isCustomTheme); - browseButton.setDisable(!isCustomTheme); + mainContainer.getChildren().add(radioContainer); - themeCombo.valueProperty().addListener((_, _, newValue) -> { - boolean isCustom = newValue == ThemeTypes.CUSTOM; - customThemePath.setDisable(!isCustom); - browseButton.setDisable(!isCustom); + 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(_ -> { @@ -329,18 +347,14 @@ protected void updateItem(ThemeTypes item, boolean empty) { customThemePath.setText(file.toAbsolutePath().toString())); }); - grid.add(new Label(Localization.lang("Visual Theme") + ":"), 0, 0); - grid.add(themeCombo, 1, 0); - grid.add(new Label(Localization.lang("Custom theme path") + ":"), 0, 1); - grid.add(customThemeBox, 1, 1); - - dialog.getDialogPane().setContent(grid); + dialog.getDialogPane().setContent(mainContainer); dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isPresent() && result.get() == ButtonType.OK) { - ThemeTypes selectedTheme = themeCombo.getValue(); - if (selectedTheme != null) { + Toggle selectedToggle = themeGroup.getSelectedToggle(); + if (selectedToggle != null) { + ThemeTypes selectedTheme = (ThemeTypes) selectedToggle.getUserData(); Theme newTheme = switch (selectedTheme) { case LIGHT -> Theme.light(); case DARK -> Theme.dark(); @@ -362,6 +376,15 @@ protected void updateItem(ThemeTypes item, boolean empty) { } } + 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"); diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 7ac106af772..5e8a9957a34 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; } @@ -1771,7 +1811,7 @@ We want to have a look that matches our icons in the tool-bar */ /* Welcome Tab Layout Styles */ .welcome-main-container { - -fx-max-width: 1024px; + -fx-max-width: 768px; -fx-spacing: 28px; -fx-alignment: center; } @@ -2074,7 +2114,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; } @@ -2544,7 +2584,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; } @@ -2564,7 +2604,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; } @@ -2572,7 +2612,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 */ @@ -2586,7 +2626,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 { @@ -2609,9 +2651,220 @@ 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; +} 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 189d030f3e9097938b8a693bef461d7f7c7e6808 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Sun, 29 Jun 2025 10:29:00 -0400 Subject: [PATCH 03/21] Remove the unnecessary parameter e from the QuickSettingsButton --- .../main/java/org/jabref/gui/welcome/QuickSettingsButton.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java b/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java index d7fac2caa34..76f17ff1387 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java @@ -14,6 +14,6 @@ public QuickSettingsButton(String text, IconTheme.@Nullable JabRefIcons icon, Ru } getStyleClass().add("quick-settings-button"); setMaxWidth(Double.MAX_VALUE); - setOnAction(e -> action.run()); + setOnAction(_ -> action.run()); } } From aeb258e7d52885583db6406db4d223b783b0175c Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Tue, 1 Jul 2025 06:52:13 -0400 Subject: [PATCH 04/21] Add performance optimization and external application detection settings to the welcome tab --- .../org/jabref/gui/welcome/WelcomeTab.java | 238 +++++++++++++++++- .../main/resources/org/jabref/gui/Base.css | 30 ++- .../main/resources/l10n/JabRef_en.properties | 19 ++ 3 files changed, 281 insertions(+), 6 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 336838000bb..5c3fe8be6ba 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -4,6 +4,8 @@ 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; @@ -11,9 +13,12 @@ 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; @@ -40,6 +45,9 @@ 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; @@ -198,7 +206,19 @@ private VBox createQuickSettingsBox() { this::showThemeDialog ); - actions.getChildren().addAll(mainFileDirButton, themeButton); + 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); } @@ -224,7 +244,7 @@ private void showMainFileDirectoryDialog() { dialog.setHeaderText(Localization.lang("Configure Main File Directory")); GridPane grid = new GridPane(); - grid.getStyleClass().add("quick-settings-dialog-grid"); + grid.getStyleClass().add("quick-settings-dialog-container"); TextField pathField = new TextField(); pathField.setPromptText(Localization.lang("Main File Directory")); @@ -376,6 +396,220 @@ private void showThemeDialog() { } } + 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() + ); + dialogService.showInformationDialogAndWait( + Localization.lang("Performance Settings"), + Localization.lang("Performance settings optimized for large libraries")); + } + } + + 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 pushPrefs = preferences.getPushToApplicationPreferences(); + String currentAppName = pushPrefs.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) { + pushPrefs.setActiveApplicationName(selectedApp.getDisplayName()); + dialogService.showInformationDialogAndWait( + Localization.lang("Push Application"), + Localization.lang("Push application set to") + ": " + 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); diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 5e8a9957a34..5f39298798f 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -1882,10 +1882,11 @@ We want to have a look that matches our icons in the tool-bar */ -fx-font-size: 1.1em; } -.quick-settings-dialog-grid { - -fx-hgap: 10px; - -fx-vgap: 10px; - -fx-padding: 20px; +.quick-settings-dialog-container { + -fx-hgap: 8px; + -fx-vgap: 8px; + -fx-padding: 16px; + -fx-spacing: 16px; } /* Community Section */ @@ -2868,3 +2869,24 @@ journalInfo .grid-cell-b { .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/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 10be22525ba..2a95601aa98 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -2988,3 +2988,22 @@ 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 From 47af7fab1ee80fc433ce32a29f195f13f9884346 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Tue, 1 Jul 2025 07:02:50 -0400 Subject: [PATCH 05/21] Remove the confirmation dialog after quick settings --- .../org/jabref/gui/welcome/WelcomeTab.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 5c3fe8be6ba..24773bb14ed 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -444,16 +444,7 @@ private void showLargeLibraryOptimizationDialog() { Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isPresent() && result.get() == ButtonType.OK) { - optimizeForLargeLibraries( - disableFulltextIndexing.isSelected(), - disableCreationDate.isSelected(), - disableModificationDate.isSelected(), - disableAutosave.isSelected(), - disableGroupCount.isSelected() - ); - dialogService.showInformationDialogAndWait( - Localization.lang("Performance Settings"), - Localization.lang("Performance settings optimized for large libraries")); + optimizeForLargeLibraries(disableFulltextIndexing.isSelected(), disableCreationDate.isSelected(), disableModificationDate.isSelected(), disableAutosave.isSelected(), disableGroupCount.isSelected()); } } @@ -507,8 +498,8 @@ private void showPushApplicationConfigurationDialog() { applicationsList.getItems().addAll(sortedApplications); applicationsList.setCellFactory(_ -> new PushApplicationListCell(detectedApplications)); - PushToApplicationPreferences pushPrefs = preferences.getPushToApplicationPreferences(); - String currentAppName = pushPrefs.getActiveApplicationName(); + PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); + String currentAppName = pushToApplicationPreferences.getActiveApplicationName(); if (!currentAppName.isEmpty()) { sortedApplications.stream() .filter(app -> app.getDisplayName().equals(currentAppName)) @@ -525,10 +516,7 @@ private void showPushApplicationConfigurationDialog() { if (result.isPresent() && result.get() == ButtonType.OK) { PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); if (selectedApp != null) { - pushPrefs.setActiveApplicationName(selectedApp.getDisplayName()); - dialogService.showInformationDialogAndWait( - Localization.lang("Push Application"), - Localization.lang("Push application set to") + ": " + selectedApp.getDisplayName()); + pushToApplicationPreferences.setActiveApplicationName(selectedApp.getDisplayName()); } } } From 7be327ced5698e741384aac565c4d0196acb8220 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Tue, 1 Jul 2025 07:04:18 -0400 Subject: [PATCH 06/21] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 0b34b0417cbae8250d43af6254b7d25f09b6b5ba Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:07:49 -0400 Subject: [PATCH 07/21] Add online service configuration and citation key removal --- .../jabref/gui/actions/StandardActions.java | 1 + .../java/org/jabref/gui/frame/MainMenu.java | 1 + .../org/jabref/gui/welcome/WelcomeTab.java | 352 +++++++++++++----- .../main/resources/org/jabref/gui/Base.css | 135 ++++--- .../main/resources/l10n/JabRef_en.properties | 19 + 5 files changed, 360 insertions(+), 148 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index 446511c7857..dc47269b79f 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -181,6 +181,7 @@ public enum StandardActions implements Action { OPEN_FACEBOOK("Facebook", Localization.lang("Opens JabRef's Facebook page"), IconTheme.JabRefIcons.FACEBOOK), OPEN_LINKEDIN("LinkedIn", Localization.lang("Opens JabRef's LinkedIn page"), IconTheme.JabRefIcons.LINKEDIN), OPEN_MASTODON("Mastodon", Localization.lang("Opens JabRef's Mastodon page"), IconTheme.JabRefIcons.MASTODON), + OPEN_PRIVACY_POLICY("Privacy Policy", Localization.lang("Opens JabRef's privacy policy"), IconTheme.JabRefIcons.BOOK), OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), diff --git a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java index 38ff808982c..0d776b80801 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -377,6 +377,7 @@ private void createMenu() { factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(preferences, dialogService, taskExecutor)), factory.createSubMenu(StandardActions.WEB_MENU, factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction(URLs.WEBPAGE_URL, dialogService, preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_PRIVACY_POLICY, new OpenBrowserAction(URLs.PRIVACY_POLICY_URL, dialogService, preferences.getExternalApplicationsPreferences())), factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction(URLs.BLOG_URL, dialogService, preferences.getExternalApplicationsPreferences())), factory.createMenuItem(StandardActions.OPEN_LINKEDIN, new OpenBrowserAction(URLs.LINKEDIN_URL, dialogService, preferences.getExternalApplicationsPreferences())), factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction(URLs.FACEBOOK_URL, dialogService, preferences.getExternalApplicationsPreferences())), diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 24773bb14ed..5e4cb8a7436 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -21,12 +21,13 @@ import javafx.scene.control.ListView; import javafx.scene.control.MenuItem; import javafx.scene.control.RadioButton; +import javafx.scene.control.ScrollPane; 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.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -44,10 +45,13 @@ import org.jabref.gui.icon.JabRefIconView; import org.jabref.gui.importer.NewDatabaseAction; import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.maintable.ColumnPreferences; +import org.jabref.gui.maintable.MainTableColumnModel; 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.slr.StudyCatalogItem; import org.jabref.gui.theme.Theme; import org.jabref.gui.theme.ThemeTypes; import org.jabref.gui.undo.CountingUndoManager; @@ -58,6 +62,9 @@ import org.jabref.logic.ai.AiService; import org.jabref.logic.importer.Importer; import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fetcher.CompositeSearchBasedFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BuildInfo; @@ -65,8 +72,10 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.InternalField; import org.jabref.model.util.FileUpdateMonitor; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -218,7 +227,19 @@ private VBox createQuickSettingsBox() { this::showPushApplicationConfigurationDialog ); - actions.getChildren().addAll(mainFileDirButton, themeButton, largeLibraryButton, pushApplicationButton); + QuickSettingsButton onlineServicesButton = new QuickSettingsButton( + Localization.lang("Configure Online Services"), + IconTheme.JabRefIcons.WWW, + this::showOnlineServicesConfigurationDialog + ); + + QuickSettingsButton entryTableButton = new QuickSettingsButton( + Localization.lang("Entry Table Display"), + IconTheme.JabRefIcons.TOGGLE_GROUPS, + this::showEntryTableConfigurationDialog + ); + + actions.getChildren().addAll(mainFileDirButton, themeButton, largeLibraryButton, pushApplicationButton, onlineServicesButton, entryTableButton); return createVBoxContainer(header, actions); } @@ -227,7 +248,7 @@ private VBox createCommunityBox() { Label header = new Label(Localization.lang("Community")); header.getStyleClass().add("welcome-header-label"); - HBox iconLinksContainer = createIconLinksContainer(); + FlowPane iconLinksContainer = createIconLinksContainer(); HBox textLinksContainer = createTextLinksContainer(); HBox versionContainer = createVersionContainer(); @@ -243,9 +264,6 @@ private void showMainFileDirectoryDialog() { 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(); @@ -263,18 +281,24 @@ private void showMainFileDirectoryDialog() { .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); + VBox content = new VBox( + new HBox( + new Label(Localization.lang("Main File Directory") + ":"), + pathField, + browseButton + ) + ); + content.getStyleClass().add("quick-settings-dialog-container"); + dialog.getDialogPane().setContent(content); 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); + if (result.isEmpty() || result.get() != ButtonType.OK) { + return; } + + filePreferences.setMainFileDirectory(pathField.getText()); + filePreferences.setStoreFilesRelativeToBibFile(false); } private void showThemeDialog() { @@ -282,13 +306,11 @@ private void showThemeDialog() { 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); + VBox content = new VBox(); + content.getStyleClass().add("quick-settings-dialog-container"); ToggleGroup themeGroup = new ToggleGroup(); HBox radioContainer = new HBox(); - radioContainer.setSpacing(8); WorkspacePreferences workspacePreferences = preferences.getWorkspacePreferences(); Theme currentTheme = workspacePreferences.getTheme(); @@ -332,22 +354,22 @@ private void showThemeDialog() { customThemePathBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(customThemePath, Priority.ALWAYS); - mainContainer.getChildren().add(radioContainer); + content.getChildren().add(radioContainer); boolean isCustomTheme = customRadio.isSelected(); if (isCustomTheme) { - mainContainer.getChildren().add(customThemePathBox); + content.getChildren().add(customThemePathBox); } themeGroup.selectedToggleProperty().addListener((_, _, newValue) -> { boolean isCustom = newValue != null && newValue.getUserData() == ThemeTypes.CUSTOM; - boolean isCurrentlyVisible = mainContainer.getChildren().contains(customThemePathBox); + boolean isCurrentlyVisible = content.getChildren().contains(customThemePathBox); if (isCustom && !isCurrentlyVisible) { - mainContainer.getChildren().add(customThemePathBox); + content.getChildren().add(customThemePathBox); dialog.getDialogPane().getScene().getWindow().sizeToScene(); } else if (!isCustom && isCurrentlyVisible) { - mainContainer.getChildren().remove(customThemePathBox); + content.getChildren().remove(customThemePathBox); dialog.getDialogPane().getScene().getWindow().sizeToScene(); } }); @@ -367,31 +389,32 @@ private void showThemeDialog() { customThemePath.setText(file.toAbsolutePath().toString())); }); - dialog.getDialogPane().setContent(mainContainer); + dialog.getDialogPane().setContent(content); 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 (result.isEmpty() || result.get() != ButtonType.OK) { + return; + } + + 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; } - }; - if (newTheme != null) { - workspacePreferences.setTheme(newTheme); + yield Theme.custom(customPath); } + }; + if (newTheme != null) { + workspacePreferences.setTheme(newTheme); } } } @@ -401,12 +424,11 @@ private void showLargeLibraryOptimizationDialog() { 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 performanceOptimizationLabel = new Label(Localization.lang("Select which performance optimizations to apply:")); + performanceOptimizationLabel.setWrapText(true); + performanceOptimizationLabel.setMaxWidth(400); - Label explanationLabel = new Label(Localization.lang("Select which performance optimizations to apply:")); - explanationLabel.setWrapText(true); - explanationLabel.setMaxWidth(400); + HBox performanceOptimizationHeader = new HBox(performanceOptimizationLabel, makeHelpButton("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues")); CheckBox disableFulltextIndexing = new CheckBox(Localization.lang("Disable fulltext indexing of linked files")); disableFulltextIndexing.setSelected(true); @@ -423,49 +445,35 @@ private void showLargeLibraryOptimizationDialog() { CheckBox disableGroupCount = new CheckBox(Localization.lang("Disable group entry count display")); disableGroupCount.setSelected(true); - VBox checkboxContainer = new VBox(); - checkboxContainer.setSpacing(8); - checkboxContainer.getChildren().addAll( + VBox content = new VBox( + performanceOptimizationHeader, 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); + content.getStyleClass().add("quick-settings-dialog-container"); + dialog.getDialogPane().setContent(content); 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()); + if (result.isEmpty() || result.get() != ButtonType.OK) { + return; } - } - - private void optimizeForLargeLibraries(boolean disableFulltextIndexing, - boolean disableCreationDate, - boolean disableModificationDate, - boolean disableAutosave, - boolean disableGroupCount) { - if (disableFulltextIndexing) { + if (disableFulltextIndexing.isSelected()) { preferences.getFilePreferences().setFulltextIndexLinkedFiles(false); } - if (disableCreationDate) { + if (disableCreationDate.isSelected()) { preferences.getTimestampPreferences().setAddCreationDate(false); } - if (disableModificationDate) { + if (disableModificationDate.isSelected()) { preferences.getTimestampPreferences().setAddModificationDate(false); } - if (disableAutosave) { + if (disableAutosave.isSelected()) { preferences.getLibraryPreferences().setAutoSave(false); } - if (disableGroupCount) { + if (disableGroupCount.isSelected()) { preferences.getGroupsPreferences().setDisplayGroupCount(false); } } @@ -475,17 +483,15 @@ private void showPushApplicationConfigurationDialog() { 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"); + VBox content = new VBox(); + content.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); + applicationsList.getStyleClass().add("applications-list"); List allApplications = PushToApplications.getAllApplications(dialogService, preferences); List detectedApplications = detectAvailableApplications(allApplications); @@ -507,17 +513,17 @@ private void showPushApplicationConfigurationDialog() { .ifPresent(applicationsList.getSelectionModel()::select); } - mainContainer.getChildren().addAll(explanationLabel, applicationsList); + content.getChildren().addAll(explanationLabel, applicationsList); - dialog.getDialogPane().setContent(mainContainer); + dialog.getDialogPane().setContent(content); 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()); - } + if (result.isEmpty() || result.get() == ButtonType.CANCEL) { + return; + } + PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); + if (selectedApp != null) { + pushToApplicationPreferences.setActiveApplicationName(selectedApp.getDisplayName()); } } @@ -573,6 +579,7 @@ private static class PushApplicationListCell extends ListCell public PushApplicationListCell(List detectedApplications) { this.detectedApplications = detectedApplications; + this.getStyleClass().add("application-item"); } @Override @@ -583,19 +590,164 @@ protected void updateItem(PushToApplication application, boolean empty) { setText(null); setGraphic(null); getStyleClass().removeAll("detected-application"); - } else { - setText(application.getDisplayName()); - setGraphic(application.getApplicationIcon().getGraphicNode()); + return; + } - if (detectedApplications.contains(application)) { - if (!getStyleClass().contains("detected-application")) { - getStyleClass().add("detected-application"); - } - } else { - getStyleClass().removeAll("detected-application"); + 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 void showOnlineServicesConfigurationDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Configure Online Services")); + dialog.setHeaderText(Localization.lang("Quick configuration for online services and web search")); + + CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for updates on startup")); + versionCheckBox.setSelected(preferences.getInternalPreferences().isVersionCheckEnabled()); + + CheckBox webSearchBox = new CheckBox(Localization.lang("Enable web search functionality")); + webSearchBox.setSelected(preferences.getImporterPreferences().areImporterEnabled()); + + CheckBox grobidCheckBox = new CheckBox(Localization.lang("Enable Grobid service for metadata extraction")); + grobidCheckBox.setSelected(preferences.getGrobidPreferences().isGrobidEnabled()); + + HBox grobidUrl = new HBox(); + Label grobidUrlLabel = new Label(Localization.lang("Grobid URL") + ":"); + TextField grobidUrlField = new TextField(preferences.getGrobidPreferences().getGrobidURL()); + HBox.setHgrow(grobidUrlField, Priority.ALWAYS); + grobidUrl.getChildren().addAll( + grobidUrlLabel, + grobidUrlField, + makeHelpButton("https://docs.jabref.org/collect/newentryfromplaintext#grobid") + ); + + grobidUrl.visibleProperty().bind(grobidCheckBox.selectedProperty()); + grobidUrl.managedProperty().bind(grobidCheckBox.selectedProperty()); + + Label fetchersLabel = new Label(Localization.lang("Online Fetchers") + ":"); + HBox fetchersHeader = new HBox(); + fetchersHeader.getChildren().addAll( + fetchersLabel, + makeHelpButton("https://docs.jabref.org/collect/import-using-online-bibliographic-database") + ); + + // From WebSearchTabViewModel. + List availableFetchers = WebFetchers + .getSearchBasedFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences()) + .stream() + .map(SearchBasedFetcher::getName) + .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) + .map(name -> { + boolean enabled = preferences.getImporterPreferences().getCatalogs().contains(name); + return new StudyCatalogItem(name, enabled); + }) + .toList(); + + VBox fetchersContainer = new VBox(); + fetchersContainer.getStyleClass().add("fetchers-container"); + List fetcherCheckBoxes = new ArrayList<>(); + + for (StudyCatalogItem fetcher : availableFetchers) { + CheckBox fetcherCheckBox = new CheckBox(fetcher.getName()); + fetcherCheckBox.setSelected(fetcher.isEnabled()); + fetcherCheckBoxes.add(fetcherCheckBox); + fetchersContainer.getChildren().add(fetcherCheckBox); + } + + ScrollPane fetchersScrollPane = new ScrollPane(fetchersContainer); + fetchersScrollPane.setFitToWidth(true); + fetchersScrollPane.setMaxHeight(288); + fetchersScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + fetchersScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + + VBox content = new VBox( + versionCheckBox, + webSearchBox, + grobidCheckBox, + grobidUrl, + fetchersHeader, + fetchersScrollPane + ); + content.getStyleClass().add("quick-settings-dialog-container"); + dialog.getDialogPane().setContent(content); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isEmpty() || result.get() != ButtonType.OK) { + return; + } + + preferences.getInternalPreferences().setVersionCheckEnabled(versionCheckBox.isSelected()); + preferences.getImporterPreferences().setImporterEnabled(webSearchBox.isSelected()); + preferences.getGrobidPreferences().setGrobidEnabled(grobidCheckBox.isSelected()); + preferences.getGrobidPreferences().setGrobidURL(grobidUrlField.getText()); + + List enabledFetchers = new ArrayList<>(); + for (int i = 0; i < fetcherCheckBoxes.size(); i++) { + if (fetcherCheckBoxes.get(i).isSelected()) { + enabledFetchers.add(availableFetchers.get(i).getName()); } } + preferences.getImporterPreferences().setCatalogs(enabledFetchers); + } + + private @NotNull Button makeHelpButton(String url) { + Button grobidHelpButton = new Button(); + grobidHelpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); + grobidHelpButton.getStyleClass().add("help-button"); + grobidHelpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); + return grobidHelpButton; + } + + private void showEntryTableConfigurationDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang("Entry Table Display")); + dialog.setHeaderText(Localization.lang("Configure entry table display settings")); + + CheckBox showCitationKeyBox = new CheckBox(Localization.lang("Show citation key column in entry table")); + + ColumnPreferences columnPreferences = preferences.getMainTablePreferences() + .getColumnPreferences(); + boolean isCitationKeyVisible = columnPreferences + .getColumns() + .stream() + .anyMatch(column -> column.getType() == MainTableColumnModel.Type.NORMALFIELD + && InternalField.KEY_FIELD.getName().equals(column.getQualifier())); + + showCitationKeyBox.setSelected(isCitationKeyVisible); + + VBox content = new VBox(showCitationKeyBox); + content.getStyleClass().add("quick-settings-dialog-container"); + dialog.getDialogPane().setContent(content); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Optional result = dialogService.showCustomDialogAndWait(dialog); + if (result.isEmpty() || result.get() != ButtonType.OK) { + return; + } + + boolean shouldShow = showCitationKeyBox.isSelected(); + + if (shouldShow && !isCitationKeyVisible) { + MainTableColumnModel citationKeyColumn = new MainTableColumnModel( + MainTableColumnModel.Type.NORMALFIELD, + InternalField.KEY_FIELD.getName() + ); + columnPreferences.getColumns().addFirst(citationKeyColumn); + } else if (!shouldShow && isCitationKeyVisible) { + columnPreferences.getColumns().removeIf(column -> + column.getType() == MainTableColumnModel.Type.NORMALFIELD + && InternalField.KEY_FIELD.getName().equals(column.getQualifier())); + } } private VBox createThemeOption(RadioButton radio, Node wireframe) { @@ -700,17 +852,18 @@ private VBox createVBoxContainer(Node... nodes) { return box; } - private HBox createIconLinksContainer() { - HBox container = new HBox(); + private FlowPane createIconLinksContainer() { + FlowPane container = new FlowPane(); container.getStyleClass().add("welcome-community-icons"); Hyperlink onlineHelpLink = createFooterLink(Localization.lang("Online help"), StandardActions.HELP, IconTheme.JabRefIcons.HELP); + Hyperlink privacyPolicyLink = createFooterLink(Localization.lang("Privacy policy"), StandardActions.OPEN_PRIVACY_POLICY, IconTheme.JabRefIcons.BOOK); 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); + container.getChildren().addAll(onlineHelpLink, privacyPolicyLink, forumLink, mastodonLink, linkedInLink, donationLink); return container; } @@ -737,6 +890,7 @@ private Hyperlink createFooterLink(String text, StandardActions action, IconThem case DONATE -> URLs.DONATE_URL; case OPEN_DEV_VERSION_LINK -> URLs.DEV_VERSION_LINK_URL; case OPEN_CHANGELOG -> URLs.CHANGELOG_URL; + case OPEN_PRIVACY_POLICY -> URLs.PRIVACY_POLICY_URL; default -> null; }; diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 5f39298798f..8e1f46cbbf0 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -1849,54 +1849,17 @@ We want to have a look that matches our icons in the tool-bar */ -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; -} +/* Welcome Community */ -.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-hgap: 12px; + -fx-vgap: 8px; -fx-alignment: center-left; } @@ -2659,12 +2622,56 @@ journalInfo .grid-cell-b { -fx-border-color: transparent; } -/* Theme Selection Wireframes */ -.theme-selection-container { - -fx-padding: 10px; +/* 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: 8px 12px; + -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-padding: 16px; + -fx-spacing: 16px; +} + +.quick-settings-dialog-container > HBox { + -fx-alignment: center-left; + -fx-spacing: 8px; +} + +.quick-settings-dialog-container > ScrollPane { + -fx-border-width: 1px; + -fx-border-color: derive(-fx-base, -20%); + -fx-border-radius: 4px; +} + +/* Quick Settings: Theme Selection */ .theme-option { -fx-padding: 8px; -fx-border-radius: 4px; @@ -2870,17 +2877,24 @@ journalInfo .grid-cell-b { -fx-background-color: #2E838C; } -/* Push Application Configuration */ +/* Quick Settings: Push Application Configuration */ +.applications-list { + -fx-pref-height: 200px; + -fx-pref-width: 400px; +} + +.application-item { + -fx-padding: 8px; + -fx-border-radius: 4px; + -fx-background-color: transparent; +} + .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 { +.application-item:filled:selected{ -fx-background-color: -jr-accent; -fx-text-fill: white; } @@ -2890,3 +2904,26 @@ journalInfo .grid-cell-b { -fx-icon-color: -jr-theme-text; -fx-fill: -jr-theme-text; } + +/* Quick Settings: Help Button */ +.help-button { + -fx-min-width: 28px; + -fx-max-width: 28px; + -fx-min-height: 28px; + -fx-max-height: 28px; + -fx-padding: 4px; + -fx-background-radius: 4px; + -fx-border-radius: 4px; +} + +.help-button .glyph-icon, +.help-button .ikonli-font-icon { + -fx-icon-size: 16px; + -fx-icon-fill:-jr-theme-accent; +} + +/* Quick Settings: Online Fetchers */ +.fetchers-container { + -fx-padding: 12px; + -fx-spacing: 12px; +} diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 2a95601aa98..3f06d08d241 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1343,6 +1343,7 @@ Mastodon=Mastodon Opens\ JabRef's\ Facebook\ page=Opens JabRef's Facebook page Opens\ JabRef's\ blog=Opens JabRef's blog Opens\ JabRef's\ website=Opens JabRef's website +Opens\ JabRef's\ privacy\ policy=Opens JabRef's privacy policy Could\ not\ open\ browser.=Could not open browser. Please\ open\ %0\ manually.=Please open %0 manually. @@ -3007,3 +3008,21 @@ Select\ your\ preferred\ text\ editor\ or\ LaTeX\ application=Select your prefer 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 + +Configure\\ Online\\ Services=Configure Online Services +Configure\\ entry\\ table\\ display\\ settings=Configure entry table display settings +Quick\\ configuration\\ for\\ online\\ services\\ and\\ web\\ search=Quick configuration for online services and web search +Check\\ for\\ updates\\ on\\ startup=Check for updates on startup +Enable\\ web\\ search\\ functionality=Enable web search functionality +Enable\\ Grobid\\ service\\ for\\ metadata\\ extraction=Enable Grobid service for metadata extraction +Grobid\\ URL=Grobid URL +Configure\\ online\\ fetchers=Configure online fetchers +Entry\\ Table\\ Display=Entry Table Display +Show\\ citation\\ key\\ column\\ in\\ entry\\ table=Show citation key column in entry table +Online\\ Services=Online Services +Online\\ Fetchers=Online Fetchers +Web\\ Search\\ and\\ Import=Web Search and Import +Learn\\ more\\ about\\ importing\\ from\\ online\\ databases=Learn more about importing from online databases +Learn\\ more\\ about\\ Grobid\\ service=Learn more about Grobid service + +Use\\ Preferences\\ Web\\ search\\ to\\ configure\\ individual\\ fetchers=Use Preferences Web search to configure individual fetchers From 2259dfed7d123c1fd77e1f1ead26586bbc7a8c49 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:26:38 -0400 Subject: [PATCH 08/21] Refactor and get consistent language --- .../gui/welcome/QuickSettingsButton.java | 19 -- .../org/jabref/gui/welcome/WelcomeTab.java | 226 +++++++++--------- .../main/resources/org/jabref/gui/Base.css | 1 + .../main/resources/l10n/JabRef_en.properties | 86 ++++--- 4 files changed, 150 insertions(+), 182 deletions(-) delete mode 100644 jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java b/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java deleted file mode 100644 index 76f17ff1387..00000000000 --- a/jabgui/src/main/java/org/jabref/gui/welcome/QuickSettingsButton.java +++ /dev/null @@ -1,19 +0,0 @@ -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/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 5e4cb8a7436..80b410245a0 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -143,6 +143,38 @@ public WelcomeTab(LibraryTabContainer tabContainer, setContent(container); } + private Optional createQuickSettingsDialog(String titleKey, String headerKey, Node... children) { + Dialog dialog = new Dialog<>(); + dialog.setTitle(Localization.lang(titleKey)); + dialog.setHeaderText(Localization.lang(headerKey)); + + VBox content = new VBox(); + content.getStyleClass().add("quick-settings-dialog-container"); + content.getChildren().addAll(children); + + dialog.getDialogPane().setContent(content); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + return dialogService.showCustomDialogAndWait(dialog); + } + + private @NotNull Button createQuickSettingsButton(String text, IconTheme.JabRefIcons icon, Runnable action) { + Button button = new Button(text); + button.setGraphic(icon.getGraphicNode()); + button.getStyleClass().add("quick-settings-button"); + button.setMaxWidth(Double.MAX_VALUE); + button.setOnAction(_ -> action.run()); + return button; + } + + private @NotNull Button createHelpButton(String url) { + Button helpButton = new Button(); + helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); + helpButton.getStyleClass().add("help-button"); + helpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); + return helpButton; + } + private VBox createTopTitles() { Label welcomeLabel = new Label(Localization.lang("Welcome to JabRef")); welcomeLabel.getStyleClass().add("welcome-label"); @@ -203,38 +235,38 @@ private VBox createQuickSettingsBox() { VBox actions = new VBox(); actions.getStyleClass().add("quick-settings-content"); - QuickSettingsButton mainFileDirButton = new QuickSettingsButton( - Localization.lang("Main File Directory"), + Button mainFileDirButton = createQuickSettingsButton( + Localization.lang("Set main file directory"), IconTheme.JabRefIcons.FOLDER, this::showMainFileDirectoryDialog ); - QuickSettingsButton themeButton = new QuickSettingsButton( - Localization.lang("Visual Theme"), + Button themeButton = createQuickSettingsButton( + Localization.lang("Change visual theme"), IconTheme.JabRefIcons.PREFERENCES, this::showThemeDialog ); - QuickSettingsButton largeLibraryButton = new QuickSettingsButton( - Localization.lang("Optimize performance for large libraries"), + Button largeLibraryButton = createQuickSettingsButton( + Localization.lang("Optimize for large libraries"), IconTheme.JabRefIcons.SELECTORS, this::showLargeLibraryOptimizationDialog ); - QuickSettingsButton pushApplicationButton = new QuickSettingsButton( - Localization.lang("Configure Push to Application"), + Button pushApplicationButton = createQuickSettingsButton( + Localization.lang("Configure push to applications"), IconTheme.JabRefIcons.APPLICATION_GENERIC, this::showPushApplicationConfigurationDialog ); - QuickSettingsButton onlineServicesButton = new QuickSettingsButton( - Localization.lang("Configure Online Services"), + Button onlineServicesButton = createQuickSettingsButton( + Localization.lang("Configure web search services"), IconTheme.JabRefIcons.WWW, this::showOnlineServicesConfigurationDialog ); - QuickSettingsButton entryTableButton = new QuickSettingsButton( - Localization.lang("Entry Table Display"), + Button entryTableButton = createQuickSettingsButton( + Localization.lang("Customize entry table"), IconTheme.JabRefIcons.TOGGLE_GROUPS, this::showEntryTableConfigurationDialog ); @@ -260,12 +292,8 @@ private VBox createCommunityBox() { } private void showMainFileDirectoryDialog() { - Dialog dialog = new Dialog<>(); - dialog.setTitle(Localization.lang("Main File Directory")); - dialog.setHeaderText(Localization.lang("Configure Main File Directory")); - TextField pathField = new TextField(); - pathField.setPromptText(Localization.lang("Main File Directory")); + pathField.setPromptText(Localization.lang("Main file directory path")); FilePreferences filePreferences = preferences.getFilePreferences(); pathField.setText(filePreferences.getMainFileDirectory() .map(Path::toString).orElse("")); @@ -281,18 +309,18 @@ private void showMainFileDirectoryDialog() { .ifPresent(selectedDir -> pathField.setText(selectedDir.toString())); }); - VBox content = new VBox( - new HBox( - new Label(Localization.lang("Main File Directory") + ":"), - pathField, - browseButton - ) + HBox pathContainer = new HBox( + new Label(Localization.lang("Directory path") + ":"), + pathField, + browseButton + ); + + Optional result = createQuickSettingsDialog( + "Set main file directory", + "Choose the default directory for storing attached files", + pathContainer ); - content.getStyleClass().add("quick-settings-dialog-container"); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isEmpty() || result.get() != ButtonType.OK) { return; } @@ -302,13 +330,6 @@ private void showMainFileDirectoryDialog() { } private void showThemeDialog() { - Dialog dialog = new Dialog<>(); - dialog.setTitle(Localization.lang("Visual Theme")); - dialog.setHeaderText(Localization.lang("Configure Visual Theme")); - - VBox content = new VBox(); - content.getStyleClass().add("quick-settings-dialog-container"); - ToggleGroup themeGroup = new ToggleGroup(); HBox radioContainer = new HBox(); @@ -340,7 +361,7 @@ private void showThemeDialog() { } TextField customThemePath = new TextField(); - customThemePath.setPromptText(Localization.lang("Path to custom theme file")); + customThemePath.setPromptText(Localization.lang("Custom theme file path")); customThemePath.setText(currentTheme.getType() == Theme.Type.CUSTOM ? currentTheme.getName() : ""); Button browseButton = new Button(); @@ -354,24 +375,11 @@ private void showThemeDialog() { customThemePathBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(customThemePath, Priority.ALWAYS); - content.getChildren().add(radioContainer); - - boolean isCustomTheme = customRadio.isSelected(); - if (isCustomTheme) { - content.getChildren().add(customThemePathBox); - } - themeGroup.selectedToggleProperty().addListener((_, _, newValue) -> { boolean isCustom = newValue != null && newValue.getUserData() == ThemeTypes.CUSTOM; - boolean isCurrentlyVisible = content.getChildren().contains(customThemePathBox); - - if (isCustom && !isCurrentlyVisible) { - content.getChildren().add(customThemePathBox); - dialog.getDialogPane().getScene().getWindow().sizeToScene(); - } else if (!isCustom && isCurrentlyVisible) { - content.getChildren().remove(customThemePathBox); - dialog.getDialogPane().getScene().getWindow().sizeToScene(); - } + customThemePathBox.setManaged(isCustom); + customThemePathBox.setVisible(isCustom); + customThemePathBox.getScene().getWindow().sizeToScene(); }); browseButton.setOnAction(_ -> { @@ -389,9 +397,17 @@ private void showThemeDialog() { customThemePath.setText(file.toAbsolutePath().toString())); }); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - Optional result = dialogService.showCustomDialogAndWait(dialog); + if (currentTheme.getType() != Theme.Type.CUSTOM) { + customThemePathBox.setVisible(false); + customThemePathBox.setManaged(false); + } + + Optional result = createQuickSettingsDialog( + "Change visual theme", + "Select your preferred theme for the application", + radioContainer, + customThemePathBox + ); if (result.isEmpty() || result.get() != ButtonType.OK) { return; } @@ -407,7 +423,7 @@ private void showThemeDialog() { if (customPath.isEmpty()) { dialogService.showErrorDialogAndWait( Localization.lang("Error"), - Localization.lang("Please specify a custom theme file path.")); + Localization.lang("Specify a custom theme file path")); yield null; } yield Theme.custom(customPath); @@ -420,32 +436,33 @@ private void showThemeDialog() { } 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")); - - Label performanceOptimizationLabel = new Label(Localization.lang("Select which performance optimizations to apply:")); + Label performanceOptimizationLabel = new Label(Localization.lang("Select performance optimizations to apply")); performanceOptimizationLabel.setWrapText(true); performanceOptimizationLabel.setMaxWidth(400); - HBox performanceOptimizationHeader = new HBox(performanceOptimizationLabel, makeHelpButton("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues")); + HBox performanceOptimizationHeader = new HBox( + performanceOptimizationLabel, + createHelpButton("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues") + ); - CheckBox disableFulltextIndexing = new CheckBox(Localization.lang("Disable fulltext indexing of linked files")); + CheckBox disableFulltextIndexing = new CheckBox(Localization.lang("Disable fulltext indexing")); disableFulltextIndexing.setSelected(true); - CheckBox disableCreationDate = new CheckBox(Localization.lang("Disable adding creation date to new entries")); + CheckBox disableCreationDate = new CheckBox(Localization.lang("Disable creation date timestamps")); disableCreationDate.setSelected(true); - CheckBox disableModificationDate = new CheckBox(Localization.lang("Disable adding modification date to entries")); + CheckBox disableModificationDate = new CheckBox(Localization.lang("Disable modification date timestamps")); disableModificationDate.setSelected(true); - CheckBox disableAutosave = new CheckBox(Localization.lang("Disable autosave for local libraries")); + CheckBox disableAutosave = new CheckBox(Localization.lang("Disable automatic saving")); disableAutosave.setSelected(true); - CheckBox disableGroupCount = new CheckBox(Localization.lang("Disable group entry count display")); + CheckBox disableGroupCount = new CheckBox(Localization.lang("Disable group entry counts")); disableGroupCount.setSelected(true); - VBox content = new VBox( + Optional result = createQuickSettingsDialog( + "Optimize for large libraries", + "Improve performance when working with libraries containing many entries", performanceOptimizationHeader, disableFulltextIndexing, disableCreationDate, @@ -453,11 +470,7 @@ private void showLargeLibraryOptimizationDialog() { disableAutosave, disableGroupCount ); - content.getStyleClass().add("quick-settings-dialog-container"); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isEmpty() || result.get() != ButtonType.OK) { return; } @@ -479,14 +492,7 @@ private void showLargeLibraryOptimizationDialog() { } 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 content = new VBox(); - content.getStyleClass().add("quick-settings-dialog-container"); - - Label explanationLabel = new Label(Localization.lang("Detected applications are highlighted. Click to select and configure.")); + Label explanationLabel = new Label(Localization.lang("Detected applications are highlighted")); explanationLabel.setWrapText(true); explanationLabel.setMaxWidth(400); @@ -513,11 +519,13 @@ private void showPushApplicationConfigurationDialog() { .ifPresent(applicationsList.getSelectionModel()::select); } - content.getChildren().addAll(explanationLabel, applicationsList); + Optional result = createQuickSettingsDialog( + "Configure push to applications", + "Select your text editor or LaTeX application for pushing citations", + explanationLabel, + applicationsList + ); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isEmpty() || result.get() == ButtonType.CANCEL) { return; } @@ -534,7 +542,6 @@ private List detectAvailableApplications(List new String[] {"emacs", "emacsclient"}; case "lyx/kile" -> new String[] {"lyx", "kile"}; @@ -607,40 +614,35 @@ protected void updateItem(PushToApplication application, boolean empty) { } private void showOnlineServicesConfigurationDialog() { - Dialog dialog = new Dialog<>(); - dialog.setTitle(Localization.lang("Configure Online Services")); - dialog.setHeaderText(Localization.lang("Quick configuration for online services and web search")); - - CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for updates on startup")); + CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for updates at startup")); versionCheckBox.setSelected(preferences.getInternalPreferences().isVersionCheckEnabled()); - CheckBox webSearchBox = new CheckBox(Localization.lang("Enable web search functionality")); + CheckBox webSearchBox = new CheckBox(Localization.lang("Enable web search")); webSearchBox.setSelected(preferences.getImporterPreferences().areImporterEnabled()); - CheckBox grobidCheckBox = new CheckBox(Localization.lang("Enable Grobid service for metadata extraction")); + CheckBox grobidCheckBox = new CheckBox(Localization.lang("Enable metadata extraction service")); grobidCheckBox.setSelected(preferences.getGrobidPreferences().isGrobidEnabled()); HBox grobidUrl = new HBox(); - Label grobidUrlLabel = new Label(Localization.lang("Grobid URL") + ":"); + Label grobidUrlLabel = new Label(Localization.lang("Service URL") + ":"); TextField grobidUrlField = new TextField(preferences.getGrobidPreferences().getGrobidURL()); HBox.setHgrow(grobidUrlField, Priority.ALWAYS); grobidUrl.getChildren().addAll( grobidUrlLabel, grobidUrlField, - makeHelpButton("https://docs.jabref.org/collect/newentryfromplaintext#grobid") + createHelpButton("https://docs.jabref.org/collect/newentryfromplaintext#grobid") ); grobidUrl.visibleProperty().bind(grobidCheckBox.selectedProperty()); grobidUrl.managedProperty().bind(grobidCheckBox.selectedProperty()); - Label fetchersLabel = new Label(Localization.lang("Online Fetchers") + ":"); + Label fetchersLabel = new Label(Localization.lang("Web search databases") + ":"); HBox fetchersHeader = new HBox(); fetchersHeader.getChildren().addAll( fetchersLabel, - makeHelpButton("https://docs.jabref.org/collect/import-using-online-bibliographic-database") + createHelpButton("https://docs.jabref.org/collect/import-using-online-bibliographic-database") ); - // From WebSearchTabViewModel. List availableFetchers = WebFetchers .getSearchBasedFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences()) .stream() @@ -669,7 +671,9 @@ private void showOnlineServicesConfigurationDialog() { fetchersScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); fetchersScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); - VBox content = new VBox( + Optional result = createQuickSettingsDialog( + "Configure web search services", + "Enable and configure online databases and services for importing entries", versionCheckBox, webSearchBox, grobidCheckBox, @@ -677,11 +681,7 @@ private void showOnlineServicesConfigurationDialog() { fetchersHeader, fetchersScrollPane ); - content.getStyleClass().add("quick-settings-dialog-container"); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isEmpty() || result.get() != ButtonType.OK) { return; } @@ -700,20 +700,8 @@ private void showOnlineServicesConfigurationDialog() { preferences.getImporterPreferences().setCatalogs(enabledFetchers); } - private @NotNull Button makeHelpButton(String url) { - Button grobidHelpButton = new Button(); - grobidHelpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); - grobidHelpButton.getStyleClass().add("help-button"); - grobidHelpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); - return grobidHelpButton; - } - private void showEntryTableConfigurationDialog() { - Dialog dialog = new Dialog<>(); - dialog.setTitle(Localization.lang("Entry Table Display")); - dialog.setHeaderText(Localization.lang("Configure entry table display settings")); - - CheckBox showCitationKeyBox = new CheckBox(Localization.lang("Show citation key column in entry table")); + CheckBox showCitationKeyBox = new CheckBox(Localization.lang("Show citation key column")); ColumnPreferences columnPreferences = preferences.getMainTablePreferences() .getColumnPreferences(); @@ -725,12 +713,12 @@ private void showEntryTableConfigurationDialog() { showCitationKeyBox.setSelected(isCitationKeyVisible); - VBox content = new VBox(showCitationKeyBox); - content.getStyleClass().add("quick-settings-dialog-container"); - dialog.getDialogPane().setContent(content); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + Optional result = createQuickSettingsDialog( + "Customize entry table", + "Configure which columns are displayed in the entry table", + showCitationKeyBox + ); - Optional result = dialogService.showCustomDialogAndWait(dialog); if (result.isEmpty() || result.get() != ButtonType.OK) { return; } diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 8e1f46cbbf0..083140de342 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2658,6 +2658,7 @@ journalInfo .grid-cell-b { .quick-settings-dialog-container { -fx-padding: 16px; -fx-spacing: 16px; + -fx-pref-width: 512px; } .quick-settings-dialog-container > HBox { diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 3f06d08d241..e0bcf1fa5b5 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -2982,47 +2982,45 @@ 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 - -Configure\\ Online\\ Services=Configure Online Services -Configure\\ entry\\ table\\ display\\ settings=Configure entry table display settings -Quick\\ configuration\\ for\\ online\\ services\\ and\\ web\\ search=Quick configuration for online services and web search -Check\\ for\\ updates\\ on\\ startup=Check for updates on startup -Enable\\ web\\ search\\ functionality=Enable web search functionality -Enable\\ Grobid\\ service\\ for\\ metadata\\ extraction=Enable Grobid service for metadata extraction -Grobid\\ URL=Grobid URL -Configure\\ online\\ fetchers=Configure online fetchers -Entry\\ Table\\ Display=Entry Table Display -Show\\ citation\\ key\\ column\\ in\\ entry\\ table=Show citation key column in entry table -Online\\ Services=Online Services -Online\\ Fetchers=Online Fetchers -Web\\ Search\\ and\\ Import=Web Search and Import -Learn\\ more\\ about\\ importing\\ from\\ online\\ databases=Learn more about importing from online databases -Learn\\ more\\ about\\ Grobid\\ service=Learn more about Grobid service - -Use\\ Preferences\\ Web\\ search\\ to\\ configure\\ individual\\ fetchers=Use Preferences Web search to configure individual fetchers + +# Quick Settings Buttons +Set\ main\ file\ directory=Set main file directory +Change\ visual\ theme=Change visual theme +Optimize\ for\ large\ libraries=Optimize for large libraries +Configure\ push\ to\ applications=Configure push to applications +Configure\ web\ search\ services=Configure web search services +Customize\ entry\ table=Customize entry table + +# Main File Directory Dialog +Main\ file\ directory\ path=Main file directory path +Choose\ the\ default\ directory\ for\ storing\ attached\ files=Choose the default directory for storing attached files +Directory\ path=Directory path + +# Visual Theme Dialog +Select\ your\ preferred\ theme\ for\ the\ application=Select your preferred theme for the application +Custom\ theme\ file\ path=Custom theme file path +Specify\ a\ custom\ theme\ file\ path=Specify a custom theme file path + +# Large Library Optimization Dialog +Improve\ performance\ when\ working\ with\ libraries\ containing\ many\ entries=Improve performance when working with libraries containing many entries +Select\ performance\ optimizations\ to\ apply=Select performance optimizations to apply +Disable\ fulltext\ indexing=Disable fulltext indexing +Disable\ creation\ date\ timestamps=Disable creation date timestamps +Disable\ modification\ date\ timestamps=Disable modification date timestamps +Disable\ automatic\ saving=Disable automatic saving +Disable\ group\ entry\ counts=Disable group entry counts + +# Push to Applications Dialog +Select\ your\ text\ editor\ or\ LaTeX\ application\ for\ pushing\ citations=Select your text editor or LaTeX application for pushing citations +Detected\ applications\ are\ highlighted=Detected applications are highlighted + +# Online Services Dialog +Enable\ and\ configure\ online\ databases\ and\ services\ for\ importing\ entries=Enable and configure online databases and services for importing entries +Check\ for\ updates\ at\ startup=Check for updates at startup +Enable\ metadata\ extraction\ service=Enable metadata extraction service +Service\ URL=Service URL +Web\ search\ databases=Web search databases + +# Entry Table Customization Dialog +Configure\ which\ columns\ are\ displayed\ in\ the\ entry\ table=Configure which columns are displayed in the entry table +Show\ citation\ key\ column=Show citation key column From a759db99bef16f7b3c406f798457a5cfc6295e6c Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:40:16 -0400 Subject: [PATCH 09/21] Allow theme option to be selected by clicking on the WireFrame --- jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java | 3 +-- jabgui/src/main/resources/org/jabref/gui/Base.css | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 80b410245a0..93aa6575ca9 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -740,10 +740,9 @@ private void showEntryTableConfigurationDialog() { 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); + container.setOnMouseClicked(_ -> radio.fire()); 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 083140de342..6e9f029d0b5 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2677,6 +2677,8 @@ journalInfo .grid-cell-b { -fx-padding: 8px; -fx-border-radius: 4px; -fx-background-radius: 4px; + -fx-spacing: 12px; + -fx-alignment: center-left; } .theme-option:hover { From ed3a0013a079b2278f15d0036b0683c3ff6765c2 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:41:11 -0400 Subject: [PATCH 10/21] Remove usage of JetBrains annotation --- jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 93aa6575ca9..c69c0f9209d 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -75,7 +75,6 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.util.FileUpdateMonitor; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -158,7 +157,7 @@ private Optional createQuickSettingsDialog(String titleKey, String h return dialogService.showCustomDialogAndWait(dialog); } - private @NotNull Button createQuickSettingsButton(String text, IconTheme.JabRefIcons icon, Runnable action) { + private Button createQuickSettingsButton(String text, IconTheme.JabRefIcons icon, Runnable action) { Button button = new Button(text); button.setGraphic(icon.getGraphicNode()); button.getStyleClass().add("quick-settings-button"); @@ -167,7 +166,7 @@ private Optional createQuickSettingsDialog(String titleKey, String h return button; } - private @NotNull Button createHelpButton(String url) { + private Button createHelpButton(String url) { Button helpButton = new Button(); helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); helpButton.getStyleClass().add("help-button"); From e296fd1125cf0adb2119a5c9cb77106906127aae Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:42:12 -0400 Subject: [PATCH 11/21] Retract change to Base.css --- jabgui/src/main/resources/org/jabref/gui/Base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 6e9f029d0b5..7c17ef65891 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 me, the text is hard to see when it's on top of the accent color. This is an alternative lighter accent color + 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 for better text visibility. */ -jr-accent-alt: derive(-jr-accent, 15%); From a0af2e87d2734c96d83fe843a5d6605d97c1bab1 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:52:18 -0400 Subject: [PATCH 12/21] Fix according to Trag --- jabgui/src/main/java/module-info.java | 5 ++++- jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java | 6 +++--- jabgui/src/main/resources/org/jabref/gui/Base.css | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/jabgui/src/main/java/module-info.java b/jabgui/src/main/java/module-info.java index d5e3eaf14e6..eebdd2eb784 100644 --- a/jabgui/src/main/java/module-info.java +++ b/jabgui/src/main/java/module-info.java @@ -57,7 +57,7 @@ // endregion provides org.tinylog.writers.Writer - with org.jabref.gui.logging.GuiWriter; + with org.jabref.gui.logging.GuiWriter; // Preferences and XML requires java.prefs; @@ -180,6 +180,9 @@ // region: other libraries (alphabetically) // requires cuid; + requires com.dlsc.pdfviewfx; + requires com.pixelduke.fxthemes; + // requires com.sun.jna; // requires dd.plist; requires io.github.adr; // required by okhttp and some AI library diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index c69c0f9209d..b1f307c7907 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -309,7 +309,7 @@ private void showMainFileDirectoryDialog() { }); HBox pathContainer = new HBox( - new Label(Localization.lang("Directory path") + ":"), + new Label(Localization.lang("Directory path")), pathField, browseButton ); @@ -623,7 +623,7 @@ private void showOnlineServicesConfigurationDialog() { grobidCheckBox.setSelected(preferences.getGrobidPreferences().isGrobidEnabled()); HBox grobidUrl = new HBox(); - Label grobidUrlLabel = new Label(Localization.lang("Service URL") + ":"); + Label grobidUrlLabel = new Label(Localization.lang("Service URL")); TextField grobidUrlField = new TextField(preferences.getGrobidPreferences().getGrobidURL()); HBox.setHgrow(grobidUrlField, Priority.ALWAYS); grobidUrl.getChildren().addAll( @@ -635,7 +635,7 @@ private void showOnlineServicesConfigurationDialog() { grobidUrl.visibleProperty().bind(grobidCheckBox.selectedProperty()); grobidUrl.managedProperty().bind(grobidCheckBox.selectedProperty()); - Label fetchersLabel = new Label(Localization.lang("Web search databases") + ":"); + Label fetchersLabel = new Label(Localization.lang("Web search databases")); HBox fetchersHeader = new HBox(); fetchersHeader.getChildren().addAll( fetchersLabel, diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 7c17ef65891..bf5cebc7bc8 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2897,7 +2897,7 @@ journalInfo .grid-cell-b { -fx-font-weight: bold; } -.application-item:filled:selected{ +.application-item:filled:selected { -fx-background-color: -jr-accent; -fx-text-fill: white; } @@ -2922,7 +2922,7 @@ journalInfo .grid-cell-b { .help-button .glyph-icon, .help-button .ikonli-font-icon { -fx-icon-size: 16px; - -fx-icon-fill:-jr-theme-accent; + -fx-icon-fill: -jr-theme-accent; } /* Quick Settings: Online Fetchers */ From 2e47ab0e74962a7a8d2245c0d3cb8e68838df4d5 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 2 Jul 2025 21:57:06 -0400 Subject: [PATCH 13/21] Fix CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e3184230eb..89731263c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added the field `monthfiled` to the default list of fields to resolve BibTeX-Strings for [#13375](https://github.com/JabRef/jabref/issues/13375) - We added a new ID based fetcher for [EuropePMC](https://europepmc.org/). [#13389](https://github.com/JabRef/jabref/pull/13389) - We added an initial [cite as you write](https://retorque.re/zotero-better-bibtex/citing/cayw/) endpoint. [#13187](https://github.com/JabRef/jabref/issues/13187) +- We added quick settings for welcome tab. [#12664](https://github.com/JabRef/jabref/issues/12664) ### Changed @@ -496,7 +497,6 @@ 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 From 196f385162373cd1f25a717b63d95ad8242dc067 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Fri, 4 Jul 2025 15:30:40 -0400 Subject: [PATCH 14/21] Style change according to the discussion --- .../org/jabref/gui/welcome/WelcomeTab.java | 40 ++++++++----------- .../main/resources/org/jabref/gui/Base.css | 1 + .../main/resources/l10n/JabRef_en.properties | 2 +- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index b1f307c7907..3b3d3f3d644 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -108,7 +108,6 @@ public WelcomeTab(LibraryTabContainer tabContainer, TaskExecutor taskExecutor, FileHistoryMenu fileHistoryMenu, BuildInfo buildInfo) { - super(Localization.lang("Welcome")); setClosable(true); @@ -128,12 +127,8 @@ public WelcomeTab(LibraryTabContainer tabContainer, this.recentLibrariesBox = new VBox(); recentLibrariesBox.getStyleClass().add("welcome-recent-libraries"); - VBox topTitles = createTopTitles(); - HBox columnsContainer = createColumnsContainer(); - - VBox mainContainer = new VBox(); + VBox mainContainer = new VBox(createColumnsContainer()); mainContainer.getStyleClass().add("welcome-main-container"); - mainContainer.getChildren().addAll(topTitles, columnsContainer); VBox container = new VBox(); container.getChildren().add(mainContainer); @@ -206,24 +201,18 @@ private HBox createColumnsContainer() { } private VBox createLeftColumn() { - VBox startBox = createWelcomeStartBox(); - VBox recentBox = createWelcomeRecentBox(); - - VBox leftColumn = new VBox(); + VBox leftColumn = new VBox( + createTopTitles(), + createWelcomeStartBox(), + createWelcomeRecentBox() + ); 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(); + VBox rightColumn = new VBox(createQuickSettingsBox(), createCommunityBox()); rightColumn.getStyleClass().add("welcome-content-column"); - rightColumn.getChildren().addAll(quickSettingsBox, communityBox); - return rightColumn; } @@ -272,7 +261,13 @@ private VBox createQuickSettingsBox() { actions.getChildren().addAll(mainFileDirButton, themeButton, largeLibraryButton, pushApplicationButton, onlineServicesButton, entryTableButton); - return createVBoxContainer(header, actions); + ScrollPane scrollPane = new ScrollPane(actions); + scrollPane.setFitToWidth(true); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + scrollPane.setMaxHeight(172); // item-height * 4 + gap * 3 = 35 * 4 + 8 * 3 + + return createVBoxContainer(header, scrollPane); } private VBox createCommunityBox() { @@ -439,10 +434,7 @@ private void showLargeLibraryOptimizationDialog() { performanceOptimizationLabel.setWrapText(true); performanceOptimizationLabel.setMaxWidth(400); - HBox performanceOptimizationHeader = new HBox( - performanceOptimizationLabel, - createHelpButton("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues") - ); + HBox performanceOptimizationHeader = new HBox(performanceOptimizationLabel, createHelpButton("https://docs.jabref.org/faq#q-i-have-a-huge-library.-what-can-i-do-to-mitigate-performance-issues")); CheckBox disableFulltextIndexing = new CheckBox(Localization.lang("Disable fulltext indexing")); disableFulltextIndexing.setSelected(true); @@ -613,7 +605,7 @@ protected void updateItem(PushToApplication application, boolean empty) { } private void showOnlineServicesConfigurationDialog() { - CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for updates at startup")); + CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for software updates at startup")); versionCheckBox.setSelected(preferences.getInternalPreferences().isVersionCheckEnabled()); CheckBox webSearchBox = new CheckBox(Localization.lang("Enable web search")); diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 6af8d58075f..5e8c42efdaf 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -1774,6 +1774,7 @@ We want to have a look that matches our icons in the tool-bar */ -fx-max-width: 768px; -fx-spacing: 28px; -fx-alignment: center; + -fx-padding: 32px 24px; } .welcome-top-titles { diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index d1e11dfa0d3..a16f34d8d87 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -3039,7 +3039,7 @@ Detected\ applications\ are\ highlighted=Detected applications are highlighted # Online Services Dialog Enable\ and\ configure\ online\ databases\ and\ services\ for\ importing\ entries=Enable and configure online databases and services for importing entries -Check\ for\ updates\ at\ startup=Check for updates at startup +Check\ for\ software\ updates\ at\ startup=Check for software updates at startup Enable\ metadata\ extraction\ service=Enable metadata extraction service Service\ URL=Service URL Web\ search\ databases=Web search databases From ff5dcf20f28bf98005204d58ea258c32d758a36c Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Sat, 5 Jul 2025 21:40:35 -0400 Subject: [PATCH 15/21] Integrate Walthrough to the WelcomeTab --- .../jabref/gui/actions/StandardActions.java | 1 + .../java/org/jabref/gui/frame/MainMenu.java | 5 +- .../SingleWindowWalkthroughOverlay.java | 146 ++++++--------- .../gui/walkthrough/WalkthroughAction.java | 173 +++++++++++++----- .../gui/walkthrough/WalkthroughRenderer.java | 7 +- .../org/jabref/gui/welcome/WelcomeTab.java | 38 +++- .../main/resources/org/jabref/gui/Base.css | 7 - .../main/resources/l10n/JabRef_en.properties | 9 +- 8 files changed, 224 insertions(+), 162 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index 8e45f983c23..a5e17f223ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -188,6 +188,7 @@ public enum StandardActions implements Action { OPEN_GITHUB("GitHub", Localization.lang("Opens JabRef's GitHub page"), IconTheme.JabRefIcons.GITHUB), WALKTHROUGH_MENU(Localization.lang("Walkthroughs"), IconTheme.JabRefIcons.BOOK), MAIN_FILE_DIRECTORY_WALKTHROUGH(Localization.lang("Configure main file directory"), IconTheme.JabRefIcons.LATEX_FILE_DIRECTORY), + CUSTOMIZE_ENTRY_TABLE_WALKTHROUGH(Localization.lang("Customize entry table"), IconTheme.JabRefIcons.TOGGLE_GROUPS), DONATE(Localization.lang("Donate to JabRef"), Localization.lang("Donate to JabRef"), IconTheme.JabRefIcons.DONATE), OPEN_FORUM(Localization.lang("Community forum"), Localization.lang("Community forum"), IconTheme.JabRefIcons.FORUM), ERROR_CONSOLE(Localization.lang("View event log"), Localization.lang("Display all error messages")), diff --git a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java index c40f41cefc7..56c7495a4c8 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -240,7 +240,7 @@ private void createMenu() { // Work around for mac only issue, where cmd+v on a dialogue triggers the paste action of menu item, resulting in addition of the pasted content in the MainTable. // If the mainscreen is not focused, the actions captured by menu are consumed. boolean isStageUnfocused = !Injector.instantiateModelOrService(Stage.class).focusedProperty().get(); - + if (OS.OS_X && isStageUnfocused) { event.consume(); } @@ -376,7 +376,8 @@ private void createMenu() { new SeparatorMenuItem(), factory.createSubMenu(StandardActions.WALKTHROUGH_MENU, - factory.createMenuItem(StandardActions.MAIN_FILE_DIRECTORY_WALKTHROUGH, new WalkthroughAction("mainFileDirectory")) + factory.createMenuItem(StandardActions.MAIN_FILE_DIRECTORY_WALKTHROUGH, new WalkthroughAction("mainFileDirectory")), + factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TABLE_WALKTHROUGH, new WalkthroughAction("customizeEntryTable")) ), new SeparatorMenuItem(), diff --git a/jabgui/src/main/java/org/jabref/gui/walkthrough/SingleWindowWalkthroughOverlay.java b/jabgui/src/main/java/org/jabref/gui/walkthrough/SingleWindowWalkthroughOverlay.java index 62ecfcf2888..de20d36874a 100644 --- a/jabgui/src/main/java/org/jabref/gui/walkthrough/SingleWindowWalkthroughOverlay.java +++ b/jabgui/src/main/java/org/jabref/gui/walkthrough/SingleWindowWalkthroughOverlay.java @@ -4,19 +4,14 @@ import javafx.beans.value.ChangeListener; import javafx.geometry.Bounds; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Scene; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; -import javafx.scene.layout.RowConstraints; import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.stage.Window; -import org.jabref.gui.walkthrough.declarative.step.PanelPosition; import org.jabref.gui.walkthrough.declarative.step.PanelStep; import org.jabref.gui.walkthrough.declarative.step.TooltipPosition; import org.jabref.gui.walkthrough.declarative.step.TooltipStep; @@ -34,7 +29,7 @@ public class SingleWindowWalkthroughOverlay { private static final Logger LOGGER = LoggerFactory.getLogger(SingleWindowWalkthroughOverlay.class); private final Window window; - private final GridPane overlayPane; + private final BorderPane overlayPane; private final Pane originalRoot; private final StackPane stackPane; private final WalkthroughRenderer renderer; @@ -44,22 +39,23 @@ public SingleWindowWalkthroughOverlay(Window window) { this.window = window; this.renderer = new WalkthroughRenderer(); - overlayPane = new GridPane(); + overlayPane = new BorderPane(); overlayPane.getStyleClass().add("walkthrough-overlay"); overlayPane.setPickOnBounds(false); overlayPane.setMaxWidth(Double.MAX_VALUE); overlayPane.setMaxHeight(Double.MAX_VALUE); + overlayPane.prefWidthProperty().bind(window.widthProperty()); + overlayPane.prefHeightProperty().bind(window.heightProperty()); + overlayPane.minWidthProperty().bind(window.widthProperty()); + overlayPane.minHeightProperty().bind(window.heightProperty()); + Scene scene = window.getScene(); // This basically never happens, so only a development time check is needed assert scene != null; originalRoot = (Pane) scene.getRoot(); - stackPane = new StackPane(); - - stackPane.getChildren().add(originalRoot); - stackPane.getChildren().add(overlayPane); - + stackPane = new StackPane(originalRoot, overlayPane); scene.setRoot(stackPane); } @@ -84,7 +80,7 @@ public void displayStep(WalkthroughStep step, case PanelStep panelStep -> { Node content = renderer.render(panelStep, walkthrough, beforeNavigate); displayPanelStep(content, panelStep); - setupClipping(content); + setupClipping(content, panelStep); overlayPane.toFront(); } } @@ -101,7 +97,12 @@ public void displayStep(WalkthroughStep step, * Hide the overlay and clean up any resources. */ public void hide() { - overlayPane.getChildren().clear(); + overlayPane.setTop(null); + overlayPane.setBottom(null); + overlayPane.setLeft(null); + overlayPane.setRight(null); + overlayPane.setCenter(null); + overlayPane.setClip(null); overlayPane.setVisible(true); updater.cleanup(); @@ -149,87 +150,25 @@ private void displayTooltipStep(Node content, @Nullable Node targetNode, Tooltip } private void displayPanelStep(Node content, PanelStep step) { - overlayPane.getChildren().clear(); - overlayPane.getRowConstraints().clear(); - overlayPane.getColumnConstraints().clear(); - - configurePanelLayout(step.position()); - - overlayPane.getChildren().add(content); - GridPane.setHgrow(content, Priority.NEVER); - GridPane.setVgrow(content, Priority.NEVER); - switch (step.position()) { - case LEFT -> { - overlayPane.setAlignment(Pos.CENTER_LEFT); - GridPane.setVgrow(content, Priority.ALWAYS); - GridPane.setFillHeight(content, true); - } - case RIGHT -> { - overlayPane.setAlignment(Pos.CENTER_RIGHT); - GridPane.setVgrow(content, Priority.ALWAYS); - GridPane.setFillHeight(content, true); - } - case TOP -> { - overlayPane.setAlignment(Pos.TOP_CENTER); - GridPane.setHgrow(content, Priority.ALWAYS); - GridPane.setFillWidth(content, true); - } - case BOTTOM -> { - overlayPane.setAlignment(Pos.BOTTOM_CENTER); - GridPane.setHgrow(content, Priority.ALWAYS); - GridPane.setFillWidth(content, true); - } + case LEFT -> overlayPane.setLeft(content); + case RIGHT -> overlayPane.setRight(content); + case TOP -> overlayPane.setTop(content); + case BOTTOM -> overlayPane.setBottom(content); default -> { LOGGER.warn("Unsupported position for panel step: {}", step.position()); - overlayPane.setAlignment(Pos.CENTER); + overlayPane.setCenter(content); } } } - private void configurePanelLayout(PanelPosition position) { - overlayPane.getRowConstraints().add(switch (position) { - case LEFT, - RIGHT -> { - RowConstraints rowConstraints = new RowConstraints(); - rowConstraints.setVgrow(Priority.ALWAYS); - yield rowConstraints; - } - case TOP, - BOTTOM -> { - RowConstraints rowConstraints = new RowConstraints(); - rowConstraints.setVgrow(Priority.NEVER); - yield rowConstraints; - } - }); - overlayPane.getColumnConstraints().add(switch (position) { - case LEFT, - RIGHT -> { - ColumnConstraints columnConstraints = new ColumnConstraints(); - columnConstraints.setHgrow(Priority.NEVER); - yield columnConstraints; - } - case TOP, - BOTTOM -> { - ColumnConstraints columnConstraints = new ColumnConstraints(); - columnConstraints.setHgrow(Priority.ALWAYS); - yield columnConstraints; - } - }); - } - private Optional mapToArrowLocation(TooltipPosition position) { return Optional.ofNullable(switch (position) { - case TOP -> - PopOver.ArrowLocation.BOTTOM_CENTER; - case BOTTOM -> - PopOver.ArrowLocation.TOP_CENTER; - case LEFT -> - PopOver.ArrowLocation.RIGHT_CENTER; - case RIGHT -> - PopOver.ArrowLocation.LEFT_CENTER; - case AUTO -> - null; + case TOP -> PopOver.ArrowLocation.BOTTOM_CENTER; + case BOTTOM -> PopOver.ArrowLocation.TOP_CENTER; + case LEFT -> PopOver.ArrowLocation.RIGHT_CENTER; + case RIGHT -> PopOver.ArrowLocation.LEFT_CENTER; + case AUTO -> null; }); } @@ -238,15 +177,34 @@ private void hideOverlayPane() { updater.addCleanupTask(() -> overlayPane.setVisible(true)); } - private void setupClipping(Node node) { - ChangeListener listener = (_, _, bounds) -> { - if (bounds != null && bounds.getWidth() > 0 && bounds.getHeight() > 0) { - Rectangle clip = new Rectangle(bounds.getMinX(), bounds.getMinY(), - bounds.getWidth(), bounds.getHeight()); - overlayPane.setClip(clip); + private void setupClipping(Node node, PanelStep step) { + ChangeListener listener = (_, _, _) -> { + Bounds windowBounds = window.getScene().getRoot().getBoundsInLocal(); + Bounds nodeBounds = node.getBoundsInParent(); + + if (windowBounds.getWidth() <= 0 || windowBounds.getHeight() <= 0) { + return; } + + Rectangle clip = switch (step.position()) { + case LEFT -> + new Rectangle(0, 0, nodeBounds.getWidth(), windowBounds.getHeight()); + case RIGHT -> + new Rectangle(Math.max(0, windowBounds.getWidth() - nodeBounds.getWidth()), 0, + nodeBounds.getWidth(), windowBounds.getHeight()); + case TOP -> + new Rectangle(0, 0, windowBounds.getWidth(), nodeBounds.getHeight()); + case BOTTOM -> + new Rectangle(0, Math.max(0, windowBounds.getHeight() - nodeBounds.getHeight()), + windowBounds.getWidth(), nodeBounds.getHeight()); + }; + + overlayPane.setClip(clip); }; - updater.listen(node.boundsInLocalProperty(), listener); + + updater.listen(node.boundsInParentProperty(), listener); + updater.listen(overlayPane.boundsInLocalProperty(), listener); + listener.changed(null, null, node.getBoundsInParent()); } } diff --git a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java index 99646dbf9a5..fe3ffeb5cc3 100644 --- a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java +++ b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java @@ -29,8 +29,11 @@ import com.airhacks.afterburner.injection.Injector; public class WalkthroughAction extends SimpleCommand { - private static final Map> WALKTHROUGH_REGISTRY = Map.of("mainFileDirectory", WalkthroughAction::createMainFileDirectoryWalkthrough); - private static final Map WALKTHROUGH_CACHE = new HashMap<>(); // must be mutable to allow caching of created walkthroughs + private static final Map> WALKTHROUGH_REGISTRY = Map.of( + "mainFileDirectory", WalkthroughAction::createMainFileDirectoryWalkthrough, + "customizeEntryTable", WalkthroughAction::createEntryTableWalkthrough + ); + private static final Map WALKTHROUGH_CACHE = new HashMap<>(); private final Walkthrough walkthrough; private final Stage mainStage; @@ -52,45 +55,49 @@ public void execute() { walkthrough.start(this.mainStage); } - private static Walkthrough createMainFileDirectoryWalkthrough(Stage mainStage) { - WindowResolver mainResolver = () -> Optional.of(mainStage); + private static Walkthrough createEntryTableWalkthrough(Stage stage) { + WalkthroughBuilder builder = new WalkthroughBuilder(stage); - WalkthroughStep step1 = TooltipStep - .builder(Localization.lang("Click on \"File\" menu")) - .resolver(NodeResolver.selector(".menu-bar .menu-button:first-child")) - .navigation(NavigationPredicate.onClick()) - .position(TooltipPosition.BOTTOM) - .highlight(HighlightEffect.BACKDROP_HIGHLIGHT) - .build(); + WalkthroughStep step1 = builder.createFileMenuStep(); + WalkthroughStep step2 = builder.createPreferencesStep(); + WalkthroughStep step3 = builder.createTabSelectionStep( + "Entry table", + Localization.lang("This section allows you to customize the columns displayed in the entry table when viewing your bibliography.") + ); - WalkthroughStep step2 = TooltipStep - .builder(Localization.lang("Click on \"Preferences\"")) - .resolver(NodeResolver.menuItem("Preferences")) - .navigation(NavigationPredicate.onClick()) - .position(TooltipPosition.RIGHT) - .activeWindow(WindowResolver.clazz(ContextMenu.class)) - .highlight(new MultiWindowHighlight( - new WindowEffect(HighlightEffect.ANIMATED_PULSE), - new WindowEffect(mainResolver, HighlightEffect.FULL_SCREEN_DARKEN) - )) + WalkthroughStep step4 = PanelStep + .builder(Localization.lang("Customize your entry table columns")) + .content( + new TextBlock(Localization.lang("Here you can customize which columns appear in your entry table. You can add, remove, or reorder columns such as citation key, title, author, year, and journal. This helps you see the most relevant information for your research at a glance.")), + new InfoBlock(Localization.lang("The columns you configure here will be displayed whenever you open a library in JabRef. You can always return to this settings page to modify your column preferences.")) + ) + .continueButton(Localization.lang("Next")) + .backButton(Localization.lang("Back")) + .resolver(NodeResolver.fxId("columnsList")) + .navigation(NavigationPredicate.manual()) + .position(PanelPosition.RIGHT) + .highlight(builder.getPreferenceHighlight()) + .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) .build(); - MultiWindowHighlight preferenceHighlight = new MultiWindowHighlight( - new WindowEffect(HighlightEffect.BACKDROP_HIGHLIGHT), - new WindowEffect(mainResolver, HighlightEffect.FULL_SCREEN_DARKEN) + WalkthroughStep step5 = builder.createSaveStep( + Localization.lang("Great! Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef."), + Localization.lang("You can find more information about customizing JabRef at https://docs.jabref.org/"), + 150 + ); + + return new Walkthrough(step1, step2, step3, step4, step5); + } + + private static Walkthrough createMainFileDirectoryWalkthrough(Stage stage) { + WalkthroughBuilder builder = new WalkthroughBuilder(stage); + + WalkthroughStep step1 = builder.createFileMenuStep(); + WalkthroughStep step2 = builder.createPreferencesStep(); + WalkthroughStep step3 = builder.createTabSelectionStep( + "Linked files", + Localization.lang("This section manages how JabRef handles your PDF files and other documents.") ); - WalkthroughStep step3 = TooltipStep - .builder(Localization.lang("Select the \"Linked files\" tab")) - .content(new TextBlock(Localization.lang("This section manages how JabRef handles your PDF files and other documents."))) - .width(400) - .resolver(NodeResolver.predicate(node -> - node.getStyleClass().contains("list-cell") && - node.toString().contains(Localization.lang("Linked files")))) - .navigation(NavigationPredicate.onClick()) - .position(TooltipPosition.AUTO) - .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) - .highlight(preferenceHighlight) - .build(); WalkthroughStep step4 = TooltipStep .builder(Localization.lang("Enable \"Main file directory\" option")) @@ -99,24 +106,90 @@ private static Walkthrough createMainFileDirectoryWalkthrough(Stage mainStage) { .resolver(NodeResolver.fxId("useMainFileDirectory")) .navigation(NavigationPredicate.onClick()) .position(TooltipPosition.AUTO) - .highlight(preferenceHighlight) + .highlight(builder.getPreferenceHighlight()) .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) .build(); - WalkthroughStep step5 = PanelStep - .builder(Localization.lang("Click \"Save\" to save changes")) - .content( - new TextBlock(Localization.lang("Congratulations. Your main file directory is now configured. JabRef will use this location to automatically find and organize your research documents.")), - new InfoBlock(Localization.lang("Additional information on main file directory can be found in https://docs.jabref.org/v5/finding-sorting-and-cleaning-entries/filelinks")) - ) - .height(180) - .resolver(NodeResolver.predicate(node -> node.getStyleClass().contains("button") && node.toString().contains(Localization.lang("Save")))) - .navigation(NavigationPredicate.onClick()) - .position(PanelPosition.TOP) - .highlight(preferenceHighlight) - .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) - .build(); + WalkthroughStep step5 = builder.createSaveStep( + Localization.lang("Congratulations. Your main file directory is now configured. JabRef will use this location to automatically find and organize your research documents."), + Localization.lang("Additional information on main file directory can be found in https://docs.jabref.org/v5/finding-sorting-and-cleaning-entries/filelinks"), + 180 + ); return new Walkthrough(step1, step2, step3, step4, step5); } + + private static class WalkthroughBuilder { + private final WindowResolver mainResolver; + private final MultiWindowHighlight preferenceHighlight; + + public WalkthroughBuilder(Stage stage) { + this.mainResolver = () -> Optional.of(stage); + this.preferenceHighlight = new MultiWindowHighlight( + new WindowEffect(HighlightEffect.BACKDROP_HIGHLIGHT), + new WindowEffect(mainResolver, HighlightEffect.FULL_SCREEN_DARKEN) + ); + } + + public WalkthroughStep createFileMenuStep() { + return TooltipStep + .builder(Localization.lang("Click on \"File\" menu")) + .resolver(NodeResolver.selector(".menu-bar .menu-button:first-child")) + .navigation(NavigationPredicate.onClick()) + .position(TooltipPosition.BOTTOM) + .highlight(HighlightEffect.BACKDROP_HIGHLIGHT) + .build(); + } + + public WalkthroughStep createPreferencesStep() { + return TooltipStep + .builder(Localization.lang("Click on \"Preferences\"")) + .resolver(NodeResolver.menuItem("Preferences")) + .navigation(NavigationPredicate.onClick()) + .position(TooltipPosition.RIGHT) + .activeWindow(WindowResolver.clazz(ContextMenu.class)) + .highlight(new MultiWindowHighlight( + new WindowEffect(HighlightEffect.ANIMATED_PULSE), + new WindowEffect(mainResolver, HighlightEffect.FULL_SCREEN_DARKEN) + )) + .build(); + } + + public WalkthroughStep createTabSelectionStep(String tabName, String description) { + return TooltipStep + .builder(Localization.lang("Select the \"" + tabName + "\" tab")) + .content(new TextBlock(description)) + .width(400) + .resolver(NodeResolver.predicate(node -> + node.getStyleClass().contains("list-cell") && + node.toString().contains(Localization.lang(tabName)))) + .navigation(NavigationPredicate.onClick()) + .position(TooltipPosition.AUTO) + .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) + .highlight(preferenceHighlight) + .build(); + } + + public WalkthroughStep createSaveStep(String completionMessage, String infoMessage, int height) { + return PanelStep + .builder(Localization.lang("Click \"Save\" to save changes")) + .content( + new TextBlock(completionMessage), + new InfoBlock(infoMessage) + ) + .height(height) + .resolver(NodeResolver.predicate(node -> + node.getStyleClass().contains("button") && + node.toString().contains(Localization.lang("Save")))) + .navigation(NavigationPredicate.onClick()) + .position(PanelPosition.TOP) + .highlight(preferenceHighlight) + .activeWindow(WindowResolver.title(PreferencesDialogView.DIALOG_TITLE)) + .build(); + } + + public MultiWindowHighlight getPreferenceHighlight() { + return preferenceHighlight; + } + } } diff --git a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughRenderer.java b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughRenderer.java index bf9a7939ff6..b18da25b17e 100644 --- a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughRenderer.java +++ b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughRenderer.java @@ -85,11 +85,10 @@ public Node render(PanelStep step, Walkthrough walkthrough, Runnable beforeNavig private void configurePanelSize(VBox panel, PanelStep step) { boolean isVertical = step.position() == PanelPosition.LEFT || step.position() == PanelPosition.RIGHT; + VBox.setVgrow(panel, Priority.ALWAYS); if (isVertical) { panel.getStyleClass().add("walkthrough-side-panel-vertical"); - VBox.setVgrow(panel, Priority.ALWAYS); - panel.setMaxHeight(Double.MAX_VALUE); step.width().ifPresent(width -> { panel.setPrefWidth(width); panel.setMaxWidth(width); @@ -97,8 +96,6 @@ private void configurePanelSize(VBox panel, PanelStep step) { }); } else if (step.position() == PanelPosition.TOP || step.position() == PanelPosition.BOTTOM) { panel.getStyleClass().add("walkthrough-side-panel-horizontal"); - HBox.setHgrow(panel, Priority.ALWAYS); - panel.setMaxWidth(Double.MAX_VALUE); step.height().ifPresent(height -> { panel.setPrefHeight(height); panel.setMaxHeight(height); @@ -114,6 +111,7 @@ private Node render(ArbitraryJFXBlock block, Walkthrough walkthrough, Runnable b private Node render(TextBlock textBlock) { Label textLabel = new Label(Localization.lang(textBlock.text())); textLabel.getStyleClass().add("walkthrough-text-content"); + textLabel.setWrapText(true); return textLabel; } @@ -122,6 +120,7 @@ private Node render(InfoBlock infoBlock) { infoContainer.getStyleClass().add("walkthrough-info-container"); JabRefIconView icon = new JabRefIconView(IconTheme.JabRefIcons.INTEGRITY_INFO); Label infoLabel = new Label(Localization.lang(infoBlock.text())); + infoLabel.setWrapText(true); HBox.setHgrow(infoLabel, Priority.ALWAYS); infoContainer.getChildren().addAll(icon, infoLabel); return infoContainer; diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 3b3d3f3d644..62755c13ecd 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -58,6 +58,7 @@ import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.util.URLs; +import org.jabref.gui.walkthrough.WalkthroughAction; import org.jabref.logic.FilePreferences; import org.jabref.logic.ai.AiService; import org.jabref.logic.importer.Importer; @@ -161,6 +162,14 @@ private Button createQuickSettingsButton(String text, IconTheme.JabRefIcons icon return button; } + private Button createWalkthroughButton(String text, IconTheme.JabRefIcons icon, String walkthroughId) { + return createQuickSettingsButton( + Localization.lang("Walkthrough") + ": " + Localization.lang(text), + icon, + () -> new WalkthroughAction(walkthroughId).execute() + ); + } + private Button createHelpButton(String url) { Button helpButton = new Button(); helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); @@ -229,6 +238,12 @@ private VBox createQuickSettingsBox() { this::showMainFileDirectoryDialog ); + Button walkthroughMainFileDirButton = createWalkthroughButton( + "Set main file directory", + IconTheme.JabRefIcons.FOLDER, + "mainFileDirectory" + ); + Button themeButton = createQuickSettingsButton( Localization.lang("Change visual theme"), IconTheme.JabRefIcons.PREFERENCES, @@ -259,13 +274,28 @@ private VBox createQuickSettingsBox() { this::showEntryTableConfigurationDialog ); - actions.getChildren().addAll(mainFileDirButton, themeButton, largeLibraryButton, pushApplicationButton, onlineServicesButton, entryTableButton); + Button walkthroughEntryTableButton = createWalkthroughButton( + "Customize entry table", + IconTheme.JabRefIcons.TOGGLE_GROUPS, + "customizeEntryTable" + ); + + actions.getChildren().addAll( + mainFileDirButton, + walkthroughMainFileDirButton, + themeButton, + largeLibraryButton, + pushApplicationButton, + onlineServicesButton, + entryTableButton, + walkthroughEntryTableButton + ); ScrollPane scrollPane = new ScrollPane(actions); scrollPane.setFitToWidth(true); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); - scrollPane.setMaxHeight(172); // item-height * 4 + gap * 3 = 35 * 4 + 8 * 3 + scrollPane.setMaxHeight(172); // Scroll pane show exactly 4 times, item-height * 4 + gap * 3 = 35 * 4 + 8 * 3 return createVBoxContainer(header, scrollPane); } @@ -605,13 +635,13 @@ protected void updateItem(PushToApplication application, boolean empty) { } private void showOnlineServicesConfigurationDialog() { - CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for software updates at startup")); + CheckBox versionCheckBox = new CheckBox(Localization.lang("Check for updates at startup")); versionCheckBox.setSelected(preferences.getInternalPreferences().isVersionCheckEnabled()); CheckBox webSearchBox = new CheckBox(Localization.lang("Enable web search")); webSearchBox.setSelected(preferences.getImporterPreferences().areImporterEnabled()); - CheckBox grobidCheckBox = new CheckBox(Localization.lang("Enable metadata extraction service")); + CheckBox grobidCheckBox = new CheckBox(Localization.lang("Enable Grobid (metadata extraction service)")); grobidCheckBox.setSelected(preferences.getGrobidPreferences().isGrobidEnabled()); HBox grobidUrl = new HBox(); diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 5e8c42efdaf..61b1f11e9a9 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -3027,10 +3027,3 @@ journalInfo .grid-cell-b { -fx-background-radius: 4; -fx-padding: 0.25em 0.5em; } - -.walkthrough-fullscreen-content { - -fx-padding: 0.1875em; - -fx-spacing: 1em; - -fx-max-width: 48em; - -fx-fill-width: true; -} diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index a16f34d8d87..040e6d1443a 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -2971,6 +2971,14 @@ Choose\ this\ option\ to\ tell\ JabRef\ where\ your\ research\ files\ are\ store Click\ "Save"\ to\ save\ changes=Click "Save" to save changes Congratulations.\ Your\ main\ file\ directory\ is\ now\ configured.\ JabRef\ will\ use\ this\ location\ to\ automatically\ find\ and\ organize\ your\ research\ documents.=Congratulations. Your main file directory is now configured. JabRef will use this location to automatically find and organize your research documents. Additional\ information\ on\ main\ file\ directory\ can\ be\ found\ in\ https\://docs.jabref.org/v5/finding-sorting-and-cleaning-entries/filelinks=Additional information on main file directory can be found in https://docs.jabref.org/v5/finding-sorting-and-cleaning-entries/filelinks +# Entry table walkthrough +Select\ the\ "Entry\ table"\ tab=Select the "Entry table" tab +This\ section\ allows\ you\ to\ customize\ the\ columns\ displayed\ in\ the\ entry\ table\ when\ viewing\ your\ bibliography.=This section allows you to customize the columns displayed in the entry table when viewing your bibliography. +Customize\ your\ entry\ table\ columns=Customize your entry table columns +Here\ you\ can\ customize\ which\ columns\ appear\ in\ your\ entry\ table.\ You\ can\ add,\ remove,\ or\ reorder\ columns\ such\ as\ citation\ key,\ title,\ author,\ year,\ and\ journal.\ This\ helps\ you\ see\ the\ most\ relevant\ information\ for\ your\ research\ at\ a\ glance.=Here you can customize which columns appear in your entry table. You can add, remove, or reorder columns such as citation key, title, author, year, and journal. This helps you see the most relevant information for your research at a glance. +The\ columns\ you\ configure\ here\ will\ be\ displayed\ whenever\ you\ open\ a\ library\ in\ JabRef.\ You\ can\ always\ return\ to\ this\ settings\ page\ to\ modify\ your\ column\ preferences.=The columns you configure here will be displayed whenever you open a library in JabRef. You can always return to this settings page to modify your column preferences. +Great!\ Your\ entry\ table\ columns\ are\ now\ configured.\ These\ settings\ will\ be\ applied\ to\ all\ your\ libraries\ in\ JabRef.=Great! Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef. +You\ can\ find\ more\ information\ about\ customizing\ JabRef\ at\ https\://docs.jabref.org/=You can find more information about customizing JabRef at https://docs.jabref.org/ # CommandLine Available\ export\ formats\:=Available export formats: @@ -3039,7 +3047,6 @@ Detected\ applications\ are\ highlighted=Detected applications are highlighted # Online Services Dialog Enable\ and\ configure\ online\ databases\ and\ services\ for\ importing\ entries=Enable and configure online databases and services for importing entries -Check\ for\ software\ updates\ at\ startup=Check for software updates at startup Enable\ metadata\ extraction\ service=Enable metadata extraction service Service\ URL=Service URL Web\ search\ databases=Web search databases From 5d3d719a717aa28071768060218355f0240eacf5 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Wed, 9 Jul 2025 21:45:23 -0400 Subject: [PATCH 16/21] Fix according to Trag bot --- .../src/main/java/org/jabref/gui/actions/StandardActions.java | 2 +- .../java/org/jabref/gui/walkthrough/WalkthroughAction.java | 2 +- jablib/src/main/resources/l10n/JabRef_en.properties | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index a5e17f223ac..f9b0cdfa87f 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -181,7 +181,7 @@ public enum StandardActions implements Action { OPEN_FACEBOOK("Facebook", Localization.lang("Opens JabRef's Facebook page"), IconTheme.JabRefIcons.FACEBOOK), OPEN_LINKEDIN("LinkedIn", Localization.lang("Opens JabRef's LinkedIn page"), IconTheme.JabRefIcons.LINKEDIN), OPEN_MASTODON("Mastodon", Localization.lang("Opens JabRef's Mastodon page"), IconTheme.JabRefIcons.MASTODON), - OPEN_PRIVACY_POLICY("Privacy Policy", Localization.lang("Opens JabRef's privacy policy"), IconTheme.JabRefIcons.BOOK), + OPEN_PRIVACY_POLICY("Privacy policy", Localization.lang("Opens JabRef's privacy policy"), IconTheme.JabRefIcons.BOOK), OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), diff --git a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java index fe3ffeb5cc3..c49916f6ac6 100644 --- a/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java +++ b/jabgui/src/main/java/org/jabref/gui/walkthrough/WalkthroughAction.java @@ -81,7 +81,7 @@ private static Walkthrough createEntryTableWalkthrough(Stage stage) { .build(); WalkthroughStep step5 = builder.createSaveStep( - Localization.lang("Great! Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef."), + Localization.lang("Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef."), Localization.lang("You can find more information about customizing JabRef at https://docs.jabref.org/"), 150 ); diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 040e6d1443a..136546088ec 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1342,6 +1342,7 @@ Opens\ JabRef's\ LinkedIn\ page=Opens JabRef's LinkedIn page LinkedIn=LinkedIn Opens\ JabRef's\ Mastodon\ page=Opens JabRef's Mastodon page Mastodon=Mastodon +Privacy\ policy=Privacy policy Opens\ JabRef's\ Facebook\ page=Opens JabRef's Facebook page Opens\ JabRef's\ blog=Opens JabRef's blog Opens\ JabRef's\ website=Opens JabRef's website @@ -2977,7 +2978,7 @@ This\ section\ allows\ you\ to\ customize\ the\ columns\ displayed\ in\ the\ ent Customize\ your\ entry\ table\ columns=Customize your entry table columns Here\ you\ can\ customize\ which\ columns\ appear\ in\ your\ entry\ table.\ You\ can\ add,\ remove,\ or\ reorder\ columns\ such\ as\ citation\ key,\ title,\ author,\ year,\ and\ journal.\ This\ helps\ you\ see\ the\ most\ relevant\ information\ for\ your\ research\ at\ a\ glance.=Here you can customize which columns appear in your entry table. You can add, remove, or reorder columns such as citation key, title, author, year, and journal. This helps you see the most relevant information for your research at a glance. The\ columns\ you\ configure\ here\ will\ be\ displayed\ whenever\ you\ open\ a\ library\ in\ JabRef.\ You\ can\ always\ return\ to\ this\ settings\ page\ to\ modify\ your\ column\ preferences.=The columns you configure here will be displayed whenever you open a library in JabRef. You can always return to this settings page to modify your column preferences. -Great!\ Your\ entry\ table\ columns\ are\ now\ configured.\ These\ settings\ will\ be\ applied\ to\ all\ your\ libraries\ in\ JabRef.=Great! Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef. +Your\ entry\ table\ columns\ are\ now\ configured.\ These\ settings\ will\ be\ applied\ to\ all\ your\ libraries\ in\ JabRef.=Your entry table columns are now configured. These settings will be applied to all your libraries in JabRef. You\ can\ find\ more\ information\ about\ customizing\ JabRef\ at\ https\://docs.jabref.org/=You can find more information about customizing JabRef at https://docs.jabref.org/ # CommandLine From a831fb29daa8790c9f3d3f647dd49ad42156195f Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Thu, 10 Jul 2025 22:40:33 -0400 Subject: [PATCH 17/21] Slight refactoring and improve the push to application resolution --- .../org/jabref/gui/welcome/WelcomeTab.java | 468 +++++++++++++----- .../main/resources/org/jabref/gui/Base.css | 14 +- .../main/resources/l10n/JabRef_en.properties | 10 +- 3 files changed, 373 insertions(+), 119 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 62755c13ecd..6b637f2eaea 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -3,11 +3,24 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; - +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javafx.application.Platform; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.geometry.Pos; import javafx.scene.Node; @@ -26,7 +39,6 @@ import javafx.scene.control.TextField; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; -import javafx.scene.control.Tooltip; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -42,7 +54,6 @@ 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.maintable.ColumnPreferences; @@ -68,6 +79,7 @@ import org.jabref.logic.importer.fetcher.CompositeSearchBasedFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.os.OS; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.TaskExecutor; @@ -138,7 +150,8 @@ public WelcomeTab(LibraryTabContainer tabContainer, setContent(container); } - private Optional createQuickSettingsDialog(String titleKey, String headerKey, Node... children) { + private Optional createQuickSettingsDialog(String titleKey, String headerKey, + BooleanSupplier validationSupplier, List> deps, Node... children) { Dialog dialog = new Dialog<>(); dialog.setTitle(Localization.lang(titleKey)); dialog.setHeaderText(Localization.lang(headerKey)); @@ -150,9 +163,29 @@ private Optional createQuickSettingsDialog(String titleKey, String h dialog.getDialogPane().setContent(content); dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK); + okButton.setDisable(!validationSupplier.getAsBoolean()); + deps.forEach(obs -> obs.addListener((_, _, _) -> okButton.setDisable(!validationSupplier.getAsBoolean()))); + return dialogService.showCustomDialogAndWait(dialog); } + private Optional createQuickSettingsDialog(String titleKey, String headerKey, Node... children) { + return createQuickSettingsDialog(titleKey, headerKey, () -> true, List.of(), children); + } + + private boolean validateDialogSubmission(ListView applicationsList, + PathSelectionField pathSelector) { + PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); + if (selectedApp == null) { + return false; + } + + String pathText = pathSelector.getText().trim(); + Path path = Path.of(pathText); + return !pathText.isEmpty() && path.isAbsolute() && path.toFile().exists(); + } + private Button createQuickSettingsButton(String text, IconTheme.JabRefIcons icon, Runnable action) { Button button = new Button(text); button.setGraphic(icon.getGraphicNode()); @@ -173,11 +206,45 @@ private Button createWalkthroughButton(String text, IconTheme.JabRefIcons icon, private Button createHelpButton(String url) { Button helpButton = new Button(); helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); - helpButton.getStyleClass().add("help-button"); + helpButton.getStyleClass().add("icon-button"); helpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); return helpButton; } + private static class PathSelectionField extends HBox { + private final TextField pathField; + private final Button browseButton; + + public PathSelectionField(String promptText) { + pathField = new TextField(); + pathField.setPromptText(promptText); + HBox.setHgrow(pathField, Priority.ALWAYS); + + browseButton = new Button(); + browseButton.setGraphic(IconTheme.JabRefIcons.OPEN.getGraphicNode()); + browseButton.getStyleClass().addAll("icon-button"); + + setSpacing(4); + getChildren().addAll(pathField, browseButton); + } + + public void setOnBrowseAction(Runnable action) { + browseButton.setOnAction(_ -> action.run()); + } + + public String getText() { + return pathField.getText(); + } + + public void setText(String text) { + pathField.setText(text); + } + + public TextField getTextField() { + return pathField; + } + } + private VBox createTopTitles() { Label welcomeLabel = new Label(Localization.lang("Welcome to JabRef")); welcomeLabel.getStyleClass().add("welcome-label"); @@ -316,41 +383,31 @@ private VBox createCommunityBox() { } private void showMainFileDirectoryDialog() { - TextField pathField = new TextField(); - pathField.setPromptText(Localization.lang("Main file directory path")); 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(_ -> { + PathSelectionField pathSelector = new PathSelectionField(Localization.lang("Main file directory path")); + pathSelector.setText(filePreferences.getMainFileDirectory() + .map(Path::toString) + .orElse("")); + + pathSelector.setOnBrowseAction(() -> { DirectoryDialogConfiguration dirConfig = new DirectoryDialogConfiguration.Builder() .withInitialDirectory(filePreferences.getWorkingDirectory()) .build(); dialogService.showDirectorySelectionDialog(dirConfig) - .ifPresent(selectedDir -> pathField.setText(selectedDir.toString())); + .ifPresent(selectedDir -> pathSelector.setText(selectedDir.toString())); }); - HBox pathContainer = new HBox( - new Label(Localization.lang("Directory path")), - pathField, - browseButton - ); - Optional result = createQuickSettingsDialog( "Set main file directory", "Choose the default directory for storing attached files", - pathContainer + pathSelector ); - if (result.isEmpty() || result.get() != ButtonType.OK) { - return; + if (result.isPresent() && result.get() == ButtonType.OK) { + filePreferences.setMainFileDirectory(pathSelector.getText()); + filePreferences.setStoreFilesRelativeToBibFile(false); } - - filePreferences.setMainFileDirectory(pathField.getText()); - filePreferences.setStoreFilesRelativeToBibFile(false); } private void showThemeDialog() { @@ -384,29 +441,10 @@ private void showThemeDialog() { case CUSTOM -> customRadio.setSelected(true); } - TextField customThemePath = new TextField(); - customThemePath.setPromptText(Localization.lang("Custom theme file path")); + PathSelectionField customThemePath = new PathSelectionField(Localization.lang("Custom theme file path")); 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); - - themeGroup.selectedToggleProperty().addListener((_, _, newValue) -> { - boolean isCustom = newValue != null && newValue.getUserData() == ThemeTypes.CUSTOM; - customThemePathBox.setManaged(isCustom); - customThemePathBox.setVisible(isCustom); - customThemePathBox.getScene().getWindow().sizeToScene(); - }); - - browseButton.setOnAction(_ -> { + customThemePath.setOnBrowseAction(() -> { String fileDir = customThemePath.getText().isEmpty() ? preferences.getInternalPreferences().getLastPreferencesExportPath().toString() : customThemePath.getText(); @@ -420,18 +458,32 @@ private void showThemeDialog() { dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> customThemePath.setText(file.toAbsolutePath().toString())); }); + customThemePath.setAlignment(Pos.CENTER_LEFT); + + themeGroup.selectedToggleProperty().addListener((_, _, newValue) -> { + boolean isCustom = newValue != null && newValue.getUserData() == ThemeTypes.CUSTOM; + customThemePath.setManaged(isCustom); + customThemePath.setVisible(isCustom); + customThemePath.getScene().getWindow().sizeToScene(); + }); if (currentTheme.getType() != Theme.Type.CUSTOM) { - customThemePathBox.setVisible(false); - customThemePathBox.setManaged(false); + customThemePath.setVisible(false); + customThemePath.setManaged(false); } Optional result = createQuickSettingsDialog( "Change visual theme", "Select your preferred theme for the application", + () -> Optional + .ofNullable(themeGroup.getSelectedToggle()) + .map(toggle -> toggle.getUserData() != ThemeTypes.CUSTOM || Path.of(customThemePath.getText()).toFile().exists()) + .orElse(false), + List.of(customThemePath.pathField.textProperty(), themeGroup.selectedToggleProperty()), radioContainer, - customThemePathBox + customThemePath ); + if (result.isEmpty() || result.get() != ButtonType.OK) { return; } @@ -442,20 +494,9 @@ private void showThemeDialog() { 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("Specify a custom theme file path")); - yield null; - } - yield Theme.custom(customPath); - } + case CUSTOM -> Theme.custom(customThemePath.getText().trim()); }; - if (newTheme != null) { - workspacePreferences.setTheme(newTheme); - } + workspacePreferences.setTheme(newTheme); } } @@ -513,7 +554,7 @@ private void showLargeLibraryOptimizationDialog() { } private void showPushApplicationConfigurationDialog() { - Label explanationLabel = new Label(Localization.lang("Detected applications are highlighted")); + Label explanationLabel = new Label(Localization.lang("Detected applications are highlighted. Application that are not detected can be set manually by specifying the path to the executable.")); explanationLabel.setWrapText(true); explanationLabel.setMaxWidth(400); @@ -521,64 +562,215 @@ private void showPushApplicationConfigurationDialog() { applicationsList.getStyleClass().add("applications-list"); 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)); + applicationsList.getItems().addAll(allApplications); + applicationsList.setCellFactory(_ -> new PushApplicationListCell(Collections.emptySet())); PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); - String currentAppName = pushToApplicationPreferences.getActiveApplicationName(); - if (!currentAppName.isEmpty()) { - sortedApplications.stream() - .filter(app -> app.getDisplayName().equals(currentAppName)) - .findFirst() - .ifPresent(applicationsList.getSelectionModel()::select); + if (!pushToApplicationPreferences.getActiveApplicationName().isEmpty()) { + allApplications.stream() + .filter(app -> app.getDisplayName().equals(pushToApplicationPreferences.getActiveApplicationName())) + .findFirst() + .ifPresent(applicationsList.getSelectionModel()::select); } + PathSelectionField pathSelector = new PathSelectionField(Localization.lang("Path to application executable")); + pathSelector.setOnBrowseAction(() -> { + FileDialogConfiguration fileConfig = new FileDialogConfiguration.Builder() + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); + dialogService.showFileOpenDialog(fileConfig) + .ifPresent(selectedFile -> pathSelector.setText(selectedFile.toString())); + }); + + Map detectedApplicationPaths = new ConcurrentHashMap<>(); + + TextField pathField = pathSelector.getTextField(); + applicationsList.getSelectionModel().selectedItemProperty().addListener((_, _, selectedApp) -> { + if (selectedApp == null) { + pathSelector.setText(""); + pathField.setPromptText(Localization.lang("Path to application executable")); + return; + } + + String existingPath = pushToApplicationPreferences.getCommandPaths().get(selectedApp.getDisplayName()); + pathSelector.setText(isValidAbsolutePath(existingPath) ? + existingPath : + Objects.requireNonNullElse(detectedApplicationPaths.get(selectedApp), "")); + }); + + pathField.textProperty().addListener((_, _, newText) -> { + if (newText == null || newText.trim().isEmpty()) { + pathField.getStyleClass().removeAll("invalid-path"); + return; + } + + if (isValidAbsolutePath(newText)) { + pathField.getStyleClass().removeAll("invalid-path"); + } else { + if (!pathField.getStyleClass().contains("invalid-path")) { + pathField.getStyleClass().add("invalid-path"); + } + } + }); + + CompletableFuture> detectionFuture = + detectApplicationPathsAsync(allApplications); + + detectionFuture.thenAccept(detectedPaths -> Platform.runLater(() -> { + detectedApplicationPaths.putAll(detectedPaths); + applicationsList.setCellFactory(_ -> new PushApplicationListCell(detectedPaths.keySet())); + + List sortedApplications = new ArrayList<>(detectedPaths.keySet()); + allApplications.stream() + .filter(app -> !detectedPaths.containsKey(app)) + .forEach(sortedApplications::add); + + applicationsList.getItems().clear(); + applicationsList.getItems().addAll(sortedApplications); + + if (!pushToApplicationPreferences.getActiveApplicationName().isEmpty()) { + sortedApplications.stream() + .filter(app -> app.getDisplayName().equals(pushToApplicationPreferences.getActiveApplicationName())) + .findFirst() + .ifPresent(applicationsList.getSelectionModel()::select); + } + + LOGGER.info("Application detection completed. Found {} applications", detectedPaths.size()); + })).exceptionally(throwable -> { + LOGGER.warn("Application detection failed", throwable); + return null; + }); + Optional result = createQuickSettingsDialog( "Configure push to applications", "Select your text editor or LaTeX application for pushing citations", + () -> validateDialogSubmission(applicationsList, pathSelector), + List.of(pathSelector.pathField.textProperty()), explanationLabel, - applicationsList + applicationsList, + pathSelector ); if (result.isEmpty() || result.get() == ButtonType.CANCEL) { + detectionFuture.cancel(true); return; } + PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); - if (selectedApp != null) { - pushToApplicationPreferences.setActiveApplicationName(selectedApp.getDisplayName()); + pushToApplicationPreferences.setActiveApplicationName(selectedApp.getDisplayName()); + + Map commandPaths = new HashMap<>(pushToApplicationPreferences.getCommandPaths()); + commandPaths.put(selectedApp.getDisplayName(), pathSelector.getText().trim()); + pushToApplicationPreferences.setCommandPaths(commandPaths); + } + + private CompletableFuture> detectApplicationPathsAsync(List allApplications) { + return CompletableFuture.supplyAsync(() -> + allApplications + .parallelStream() + .map(application -> { + Optional path = findApplicationPath(application); + if (path.isPresent()) { + LOGGER.debug("Detected application {}: {}", application.getDisplayName(), path.get()); + return Optional.of(Map.entry(application, path.get())); + } + return Optional.>empty(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + ); + } + + private Optional findApplicationPath(PushToApplication application) { + String appName = application.getDisplayName(); + String[] possibleNames = getPossibleExecutableNames(appName); + + for (String executable : possibleNames) { + Optional pathInPath = findExecutableInPath(executable); + if (pathInPath.isPresent()) { + return pathInPath; + } } + + return findExecutableInCommonPaths(possibleNames); } - private List detectAvailableApplications(List allApplications) { - return allApplications.stream().filter(this::isApplicationAvailable).toList(); + private Optional findExecutableInCommonPaths(String[] executableNames) { + List commonPaths = getCommonPaths(); + + for (Path basePath : commonPaths) { + try { + if (basePath.toFile().exists()) { + Optional result = findExecutableInDirectory(basePath, executableNames); + if (result.isPresent()) { + return result; + } + } + } catch (Exception e) { + LOGGER.trace("Error checking path {}: {}", basePath, e.getMessage()); + } + } + + return Optional.empty(); } - private boolean isApplicationAvailable(PushToApplication application) { - String appName = application.getDisplayName().toLowerCase(); + private List getCommonPaths() { + List paths = new ArrayList<>(); + + if (OS.WINDOWS) { + paths.addAll(List.of( + Path.of("C:/Program Files"), + Path.of("C:/Program Files (x86)"), + Path.of(System.getProperty("user.home"), "AppData/Local"), + Path.of(System.getProperty("user.home"), "AppData/Roaming") + )); + } else if (OS.OS_X) { + paths.addAll(List.of( + Path.of("/Applications"), + Path.of("/usr/local/bin"), + Path.of("/opt/homebrew/bin"), + Path.of(System.getProperty("user.home"), "Applications"), + Path.of("/usr/local/texlive"), + Path.of("/Library/TeX/texbin") + )); + } else if (OS.LINUX) { + paths.addAll(List.of( + Path.of("/usr/bin"), + Path.of("/usr/local/bin"), + Path.of("/opt"), + Path.of("/snap/bin"), + Path.of(System.getProperty("user.home"), ".local/bin"), + Path.of("/usr/local/texlive") + )); + } - 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()}; - }; + return paths; + } - for (String executable : possibleNames) { - if (isExecutableInPath(executable)) { + private Optional findExecutableInDirectory(Path directory, String[] executableNames) { + try (Stream pathStream = Files.walk(directory, 3)) { + return pathStream + .filter(path -> isValidExecutable(path, executableNames)) + .map(Path::toAbsolutePath) + .map(Path::toString) + .findFirst(); + } catch (IOException e) { + LOGGER.trace("Error searching directory {}: {}", directory, e.getMessage()); + return Optional.empty(); + } + } + + private boolean isValidExecutable(Path path, String[] executableNames) { + if (!path.toFile().exists()) { + return false; + } + + String fileName = path.getFileName().toString().toLowerCase(); + + for (String execName : executableNames) { + if (isExecutableNameMatch(fileName, execName)) { return true; } } @@ -586,26 +778,78 @@ private boolean isApplicationAvailable(PushToApplication application) { return false; } - private boolean isExecutableInPath(String executable) { + private boolean isExecutableNameMatch(String fileName, String executableName) { + if (OS.WINDOWS) { + return fileName.equals(executableName + ".exe") || + fileName.equals(executableName + ".bat") || + fileName.equals(executableName + ".cmd"); + } else { // OSX or Linux + return fileName.equals(executableName) || + fileName.equals(executableName + ".sh") || + fileName.equals(executableName + ".app"); + } + } + + private boolean isValidAbsolutePath(String pathStr) { + if (pathStr == null || pathStr.trim().isEmpty()) { + return false; + } + Path path = Path.of(pathStr); + return path.isAbsolute() && path.toFile().exists(); + } + + private String[] getPossibleExecutableNames(String appName) { + return switch (appName) { + case PushToApplications.EMACS -> new String[] {"emacs", "emacsclient"}; + case PushToApplications.LYX -> new String[] {"lyx", "kile"}; + case PushToApplications.TEXMAKER -> new String[] {"texmaker"}; + case PushToApplications.TEXSTUDIO -> new String[] {"texstudio"}; + case PushToApplications.TEXWORKS -> new String[] {"texworks"}; + case PushToApplications.VIM -> new String[] {"vim", "nvim", "gvim"}; + case PushToApplications.WIN_EDT -> new String[] {"winedt"}; + case PushToApplications.SUBLIME_TEXT -> + new String[] {"subl", "sublime_text"}; + case PushToApplications.TEXSHOP -> new String[] {"texshop"}; + case PushToApplications.VSCODE -> new String[] {"code", "code-insiders"}; + default -> new String[] {appName.replace(" ", "").toLowerCase()}; + }; + } + + private Optional findExecutableInPath(String executable) { + Optional result = trySystemCommand("which", executable); + System.out.println("which " + executable + " result: " + result.orElse("Not found")); + if (result.isPresent()) { + return result; + } + + return trySystemCommand("where", executable) + .map(output -> { + String[] lines = output.split("\n"); + return lines.length > 0 && !lines[0].trim().isEmpty() ? lines[0].trim() : null; + }) + .filter(Objects::nonNull); + } + + private Optional trySystemCommand(String command, String argument) { try { - ProcessBuilder pb = new ProcessBuilder("which", executable); + ProcessBuilder pb = new ProcessBuilder(command, argument); 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; + if (process.waitFor() == 0) { + String result = new String(process.getInputStream().readAllBytes()).trim(); + if (!result.isEmpty()) { + return Optional.of(result); + } } + } catch (IOException | InterruptedException e) { + LOGGER.trace("Failed to execute '{}' command: {}", command, e.getMessage()); } + return Optional.empty(); } private static class PushApplicationListCell extends ListCell { - private final List detectedApplications; + private final Set detectedApplications; - public PushApplicationListCell(List detectedApplications) { + public PushApplicationListCell(Set detectedApplications) { this.detectedApplications = detectedApplications; this.getStyleClass().add("application-item"); } diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index d2d18c2208b..e152828d532 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2869,7 +2869,7 @@ journalInfo .grid-cell-b { } /* Quick Settings: Help Button */ -.help-button { +.icon-button { -fx-min-width: 28px; -fx-max-width: 28px; -fx-min-height: 28px; @@ -2877,10 +2877,12 @@ journalInfo .grid-cell-b { -fx-padding: 4px; -fx-background-radius: 4px; -fx-border-radius: 4px; + -fx-border-color: -jr-gray-1; + -fx-border-width: 1px; } -.help-button .glyph-icon, -.help-button .ikonli-font-icon { +.icon-button .glyph-icon, +.icon-button .ikonli-font-icon { -fx-icon-size: 16px; -fx-icon-fill: -jr-theme-accent; } @@ -3115,3 +3117,9 @@ journalInfo .grid-cell-b { .markdown-link:hover { -fx-text-fill: #004499; } + +.invalid-path { + -fx-background-color: rgba(238, 82, 83, 0.2); + -fx-border-color: -jr-error; + -fx-border-width: 1px; +} diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 136546088ec..df642f542d5 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -3022,16 +3022,15 @@ Optimize\ for\ large\ libraries=Optimize for large libraries Configure\ push\ to\ applications=Configure push to applications Configure\ web\ search\ services=Configure web search services Customize\ entry\ table=Customize entry table +Walkthrough=Walkthrough # Main File Directory Dialog Main\ file\ directory\ path=Main file directory path Choose\ the\ default\ directory\ for\ storing\ attached\ files=Choose the default directory for storing attached files -Directory\ path=Directory path # Visual Theme Dialog Select\ your\ preferred\ theme\ for\ the\ application=Select your preferred theme for the application Custom\ theme\ file\ path=Custom theme file path -Specify\ a\ custom\ theme\ file\ path=Specify a custom theme file path # Large Library Optimization Dialog Improve\ performance\ when\ working\ with\ libraries\ containing\ many\ entries=Improve performance when working with libraries containing many entries @@ -3044,13 +3043,16 @@ Disable\ group\ entry\ counts=Disable group entry counts # Push to Applications Dialog Select\ your\ text\ editor\ or\ LaTeX\ application\ for\ pushing\ citations=Select your text editor or LaTeX application for pushing citations -Detected\ applications\ are\ highlighted=Detected applications are highlighted +Detected\ applications\ are\ highlighted.\ Application\ that\ are\ not\ detected\ can\ be\ set\ manually\ by\ specifying\ the\ path\ to\ the\ executable.=Detected applications are highlighted. Application that are not detected can be set manually by specifying the path to the executable. +Application\ path=Application path +Path\ to\ application\ executable=Path to application executable # Online Services Dialog Enable\ and\ configure\ online\ databases\ and\ services\ for\ importing\ entries=Enable and configure online databases and services for importing entries -Enable\ metadata\ extraction\ service=Enable metadata extraction service Service\ URL=Service URL Web\ search\ databases=Web search databases +Check\ for\ updates\ at\ startup=Check for updates at startup +Enable\ Grobid\ (metadata\ extraction\ service)=Enable Grobid (metadata extraction service) # Entry Table Customization Dialog Configure\ which\ columns\ are\ displayed\ in\ the\ entry\ table=Configure which columns are displayed in the entry table From 130967537c7053aebb90ce406397bd327761de61 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Fri, 11 Jul 2025 14:50:36 -0400 Subject: [PATCH 18/21] Quick fix after the merge --- .../org/jabref/gui/welcome/WelcomeTab.java | 136 +++++++++++------- .../main/resources/org/jabref/gui/Base.css | 6 +- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 6b637f2eaea..bd33ce9eb3c 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -59,9 +59,8 @@ import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTableColumnModel; 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.push.GuiPushToApplication; +import org.jabref.gui.push.GuiPushToApplications; import org.jabref.gui.slr.StudyCatalogItem; import org.jabref.gui.theme.Theme; import org.jabref.gui.theme.ThemeTypes; @@ -80,6 +79,8 @@ import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.os.OS; +import org.jabref.logic.push.PushToApplication; +import org.jabref.logic.push.PushToApplicationPreferences; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.TaskExecutor; @@ -174,7 +175,7 @@ private Optional createQuickSettingsDialog(String titleKey, String h return createQuickSettingsDialog(titleKey, headerKey, () -> true, List.of(), children); } - private boolean validateDialogSubmission(ListView applicationsList, + private boolean validateDialogSubmission(ListView applicationsList, PathSelectionField pathSelector) { PushToApplication selectedApp = applicationsList.getSelectionModel().getSelectedItem(); if (selectedApp == null) { @@ -206,7 +207,7 @@ private Button createWalkthroughButton(String text, IconTheme.JabRefIcons icon, private Button createHelpButton(String url) { Button helpButton = new Button(); helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); - helpButton.getStyleClass().add("icon-button"); + helpButton.getStyleClass().add("qs-icon-button"); helpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); return helpButton; } @@ -222,7 +223,7 @@ public PathSelectionField(String promptText) { browseButton = new Button(); browseButton.setGraphic(IconTheme.JabRefIcons.OPEN.getGraphicNode()); - browseButton.getStyleClass().addAll("icon-button"); + browseButton.getStyleClass().addAll("qs-icon-button"); setSpacing(4); getChildren().addAll(pathField, browseButton); @@ -436,9 +437,12 @@ private void showThemeDialog() { radioContainer.getChildren().add(customBox); switch (currentTheme.getType()) { - case DEFAULT -> lightRadio.setSelected(true); - case EMBEDDED -> darkRadio.setSelected(true); - case CUSTOM -> customRadio.setSelected(true); + case DEFAULT -> + lightRadio.setSelected(true); + case EMBEDDED -> + darkRadio.setSelected(true); + case CUSTOM -> + customRadio.setSelected(true); } PathSelectionField customThemePath = new PathSelectionField(Localization.lang("Custom theme file path")); @@ -492,9 +496,12 @@ private void showThemeDialog() { if (selectedToggle != null) { ThemeTypes selectedTheme = (ThemeTypes) selectedToggle.getUserData(); Theme newTheme = switch (selectedTheme) { - case LIGHT -> Theme.light(); - case DARK -> Theme.dark(); - case CUSTOM -> Theme.custom(customThemePath.getText().trim()); + case LIGHT -> + Theme.light(); + case DARK -> + Theme.dark(); + case CUSTOM -> + Theme.custom(customThemePath.getText().trim()); }; workspacePreferences.setTheme(newTheme); } @@ -558,15 +565,15 @@ private void showPushApplicationConfigurationDialog() { explanationLabel.setWrapText(true); explanationLabel.setMaxWidth(400); - ListView applicationsList = new ListView<>(); + ListView applicationsList = new ListView<>(); applicationsList.getStyleClass().add("applications-list"); - List allApplications = PushToApplications.getAllApplications(dialogService, preferences); + PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); + List allApplications = GuiPushToApplications.getAllGUIApplications(dialogService, pushToApplicationPreferences); applicationsList.getItems().addAll(allApplications); applicationsList.setCellFactory(_ -> new PushApplicationListCell(Collections.emptySet())); - PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); if (!pushToApplicationPreferences.getActiveApplicationName().isEmpty()) { allApplications.stream() .filter(app -> app.getDisplayName().equals(pushToApplicationPreferences.getActiveApplicationName())) @@ -614,14 +621,14 @@ private void showPushApplicationConfigurationDialog() { } }); - CompletableFuture> detectionFuture = + CompletableFuture> detectionFuture = detectApplicationPathsAsync(allApplications); detectionFuture.thenAccept(detectedPaths -> Platform.runLater(() -> { detectedApplicationPaths.putAll(detectedPaths); applicationsList.setCellFactory(_ -> new PushApplicationListCell(detectedPaths.keySet())); - List sortedApplications = new ArrayList<>(detectedPaths.keySet()); + List sortedApplications = new ArrayList<>(detectedPaths.keySet()); allApplications.stream() .filter(app -> !detectedPaths.containsKey(app)) .forEach(sortedApplications::add); @@ -665,25 +672,25 @@ private void showPushApplicationConfigurationDialog() { pushToApplicationPreferences.setCommandPaths(commandPaths); } - private CompletableFuture> detectApplicationPathsAsync(List allApplications) { + private CompletableFuture> detectApplicationPathsAsync(List allApplications) { return CompletableFuture.supplyAsync(() -> allApplications - .parallelStream() - .map(application -> { - Optional path = findApplicationPath(application); - if (path.isPresent()) { - LOGGER.debug("Detected application {}: {}", application.getDisplayName(), path.get()); - return Optional.of(Map.entry(application, path.get())); - } - return Optional.>empty(); - }) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + .parallelStream() + .map(application -> { + Optional path = findApplicationPath(application); + if (path.isPresent()) { + LOGGER.debug("Detected application {}: {}", application.getDisplayName(), path.get()); + return Optional.of(Map.entry(application, path.get())); + } + return Optional.>empty(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); } - private Optional findApplicationPath(PushToApplication application) { + private Optional findApplicationPath(GuiPushToApplication application) { String appName = application.getDisplayName(); String[] possibleNames = getPossibleExecutableNames(appName); @@ -800,18 +807,28 @@ private boolean isValidAbsolutePath(String pathStr) { private String[] getPossibleExecutableNames(String appName) { return switch (appName) { - case PushToApplications.EMACS -> new String[] {"emacs", "emacsclient"}; - case PushToApplications.LYX -> new String[] {"lyx", "kile"}; - case PushToApplications.TEXMAKER -> new String[] {"texmaker"}; - case PushToApplications.TEXSTUDIO -> new String[] {"texstudio"}; - case PushToApplications.TEXWORKS -> new String[] {"texworks"}; - case PushToApplications.VIM -> new String[] {"vim", "nvim", "gvim"}; - case PushToApplications.WIN_EDT -> new String[] {"winedt"}; - case PushToApplications.SUBLIME_TEXT -> + case "Emacs" -> + new String[] {"emacs", "emacsclient"}; + case "LyX/Kile" -> + new String[] {"lyx", "kile"}; + case "Taxmaker" -> + 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 PushToApplications.TEXSHOP -> new String[] {"texshop"}; - case PushToApplications.VSCODE -> new String[] {"code", "code-insiders"}; - default -> new String[] {appName.replace(" ", "").toLowerCase()}; + case "TeXShop" -> + new String[] {"texshop"}; + case "VScode" -> + new String[] {"code", "code-insiders"}; + default -> + new String[] {appName.replace(" ", "").toLowerCase()}; }; } @@ -846,16 +863,16 @@ private Optional trySystemCommand(String command, String argument) { return Optional.empty(); } - private static class PushApplicationListCell extends ListCell { - private final Set detectedApplications; + private static class PushApplicationListCell extends ListCell { + private final Set detectedApplications; - public PushApplicationListCell(Set detectedApplications) { + public PushApplicationListCell(Set detectedApplications) { this.detectedApplications = detectedApplications; this.getStyleClass().add("application-item"); } @Override - protected void updateItem(PushToApplication application, boolean empty) { + protected void updateItem(GuiPushToApplication application, boolean empty) { super.updateItem(application, empty); if (empty || application == null) { @@ -1135,15 +1152,24 @@ private Hyperlink createFooterLink(String text, StandardActions action, IconThem 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; - case OPEN_PRIVACY_POLICY -> URLs.PRIVACY_POLICY_URL; - default -> null; + 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; + case OPEN_PRIVACY_POLICY -> + URLs.PRIVACY_POLICY_URL; + default -> + null; }; if (url != null) { diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index e152828d532..dcedccff992 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2869,7 +2869,7 @@ journalInfo .grid-cell-b { } /* Quick Settings: Help Button */ -.icon-button { +.qs-icon-button { -fx-min-width: 28px; -fx-max-width: 28px; -fx-min-height: 28px; @@ -2881,8 +2881,8 @@ journalInfo .grid-cell-b { -fx-border-width: 1px; } -.icon-button .glyph-icon, -.icon-button .ikonli-font-icon { +.qs-icon-button .glyph-icon, +.qs-icon-button .ikonli-font-icon { -fx-icon-size: 16px; -fx-icon-fill: -jr-theme-accent; } From 37eaf71d4d4808aeaa2e643a215647987b4ddea3 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Fri, 11 Jul 2025 15:15:49 -0400 Subject: [PATCH 19/21] Quick improvement on the elegance of Wireframe styling --- .../main/resources/org/jabref/gui/Base.css | 237 ++++++------------ .../gui/welcome/ThemeWireFrameComponent.fxml | 131 ++++++---- 2 files changed, 165 insertions(+), 203 deletions(-) diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index dcedccff992..8234faf51e2 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2647,197 +2647,114 @@ journalInfo .grid-cell-b { /* 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); + -fx-border-width: 0.0625em; + -fx-border-radius: 0.25em; + -fx-background-radius: 0.25em; + -fx-effect: dropshadow(three-pass-box , rgba(0,0,0,0.1) , 0.125em, 0.0 , 0 , 0.0625em ); + -fx-border-color: -jr-wf-border-color; } .wireframe-menubar { - -fx-padding: 3px 1px; + -fx-padding: 0.1875em 0.0625em; + -fx-background-color: -jr-wf-menubar-bg; } .wireframe-menu-item { - -fx-border-radius: 1px; - -fx-background-radius: 1px; + -fx-border-radius: 0.0625em; + -fx-background-radius: 0.0625em; + -fx-background-color: -jr-wf-menu-item-bg; } .wireframe-toolbar { - -fx-padding: 2px; - -fx-spacing: 6px; + -fx-padding: 0.125em; + -fx-spacing: 0.375em; + -fx-background-color: -jr-wf-toolbar-bg; } .wireframe-tool-item { - -fx-border-radius: 1px; - -fx-background-radius: 1px; + -fx-border-radius: 0.0625em; + -fx-background-radius: 0.0625em; + -fx-background-color: -jr-wf-tool-item-bg; } .wireframe-search { - -fx-border-width: 0.5px; - -fx-border-radius: 2px; - -fx-background-radius: 2px; + -fx-border-width: 0.03125em; + -fx-border-radius: 0.125em; + -fx-background-radius: 0.125em; + -fx-background-color: -jr-wf-search-bg; + -fx-border-color: -jr-wf-search-border; } .wireframe-search-field { - -fx-border-radius: 1px; - -fx-background-radius: 1px; + -fx-border-radius: 0.0625em; + -fx-background-radius: 0.0625em; + -fx-background-color: -jr-wf-search-field-bg; } .wireframe-sidebar { - -fx-padding: 4px 2px; + -fx-padding: 0.25em 0.125em; + -fx-background-color: -jr-wf-sidebar-bg; } .wireframe-sidebar-item { - -fx-border-radius: 1px; - -fx-background-radius: 1px; + -fx-border-radius: 0.0625em; + -fx-background-radius: 0.0625em; + -fx-background-color: -jr-wf-sidebar-item-bg; } .wireframe-welcome-tab-area { - -fx-padding: 4px; + -fx-padding: 0.25em; -fx-alignment: center; + -fx-background-color: -jr-wf-welcome-area-bg; } .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; + -fx-border-radius: 0.0625em; + -fx-background-radius: 0.0625em; + -fx-background-color: -jr-wf-welcome-item-bg; +} + +/* Other color scheme */ +.wireframe-light { + -jr-wf-menubar-bg: #f9f9f9; + -jr-wf-menu-item-bg: -jr-gray-1; + -jr-wf-toolbar-bg: #f9f9f9; + -jr-wf-tool-item-bg: -jr-theme; + -jr-wf-search-bg: -jr-white; + -jr-wf-search-border: #dddddd; + -jr-wf-search-field-bg: #f8f8f8; + -jr-wf-sidebar-bg: -jr-gray-1; + -jr-wf-sidebar-item-bg: -jr-theme; + -jr-wf-welcome-area-bg: #f3f3f3; + -jr-wf-welcome-item-bg: #dddddd; +} + +.wireframe-dark { + -jr-wf-menubar-bg: #141824; + -jr-wf-menu-item-bg: #424758; + -jr-wf-toolbar-bg: #141824; + -jr-wf-tool-item-bg: #2c9490; + -jr-wf-search-bg: #2c2e3b; + -jr-wf-search-border: #424758; + -jr-wf-search-field-bg: #424758; + -jr-wf-sidebar-bg: #212330; + -jr-wf-sidebar-item-bg: #2c9490; + -jr-wf-welcome-area-bg: #272b38; + -jr-wf-welcome-item-bg: #7d8591; +} + +.wireframe-custom { + -jr-wf-menubar-bg: #f5ffe5; + -jr-wf-menu-item-bg: #346963; + -jr-wf-toolbar-bg: #f5ffe5; + -jr-wf-tool-item-bg: #2E838C; + -jr-wf-search-bg: -jr-white; + -jr-wf-search-border: #2E838C; + -jr-wf-search-field-bg: #f0f3ff; + -jr-wf-sidebar-bg: #e1ebd1; + -jr-wf-sidebar-item-bg: #2E838C; + -jr-wf-welcome-area-bg: #E8F2D8; + -jr-wf-welcome-item-bg: #2E838C; } /* Quick Settings: Push Application Configuration */ diff --git a/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml b/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml index 1ed3189783e..78630d1954c 100644 --- a/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/welcome/ThemeWireFrameComponent.fxml @@ -2,73 +2,100 @@ - - - + - - - - - - + - - - - - + - - - - - - - - - - - @@ -76,10 +103,15 @@ - - + @@ -98,7 +130,9 @@ - + @@ -119,9 +153,13 @@ + styleClass="wireframe-welcome-tab-area" + HBox.hgrow="ALWAYS" + alignment="CENTER"> - + @@ -131,10 +169,13 @@ - - + + - + - + - + - + - + Date: Fri, 11 Jul 2025 15:20:01 -0400 Subject: [PATCH 20/21] Fix the spelling issue with Texmaker --- jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index bd33ce9eb3c..4b098a687d3 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -811,7 +811,7 @@ private String[] getPossibleExecutableNames(String appName) { new String[] {"emacs", "emacsclient"}; case "LyX/Kile" -> new String[] {"lyx", "kile"}; - case "Taxmaker" -> + case "Texmaker" -> new String[] {"texmaker"}; case "TeXstudio" -> new String[] {"texstudio"}; From 78e81482a1050d961591187d852e1e472e424d40 Mon Sep 17 00:00:00 2001 From: Yubo-Cao Date: Fri, 11 Jul 2025 15:24:01 -0400 Subject: [PATCH 21/21] Get rid of the qs icon button --- .../org/jabref/gui/welcome/WelcomeTab.java | 4 ++-- .../main/resources/org/jabref/gui/Base.css | 19 ------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java index 4b098a687d3..fe0518b029b 100644 --- a/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java +++ b/jabgui/src/main/java/org/jabref/gui/welcome/WelcomeTab.java @@ -207,7 +207,7 @@ private Button createWalkthroughButton(String text, IconTheme.JabRefIcons icon, private Button createHelpButton(String url) { Button helpButton = new Button(); helpButton.setGraphic(IconTheme.JabRefIcons.HELP.getGraphicNode()); - helpButton.getStyleClass().add("qs-icon-button"); + helpButton.getStyleClass().add("icon-button"); helpButton.setOnAction(_ -> new OpenBrowserAction(url, dialogService, preferences.getExternalApplicationsPreferences()).execute()); return helpButton; } @@ -223,7 +223,7 @@ public PathSelectionField(String promptText) { browseButton = new Button(); browseButton.setGraphic(IconTheme.JabRefIcons.OPEN.getGraphicNode()); - browseButton.getStyleClass().addAll("qs-icon-button"); + browseButton.getStyleClass().addAll("icon-button"); setSpacing(4); getChildren().addAll(pathField, browseButton); diff --git a/jabgui/src/main/resources/org/jabref/gui/Base.css b/jabgui/src/main/resources/org/jabref/gui/Base.css index 8234faf51e2..c8a71f92b02 100644 --- a/jabgui/src/main/resources/org/jabref/gui/Base.css +++ b/jabgui/src/main/resources/org/jabref/gui/Base.css @@ -2785,25 +2785,6 @@ journalInfo .grid-cell-b { -fx-fill: -jr-theme-text; } -/* Quick Settings: Help Button */ -.qs-icon-button { - -fx-min-width: 28px; - -fx-max-width: 28px; - -fx-min-height: 28px; - -fx-max-height: 28px; - -fx-padding: 4px; - -fx-background-radius: 4px; - -fx-border-radius: 4px; - -fx-border-color: -jr-gray-1; - -fx-border-width: 1px; -} - -.qs-icon-button .glyph-icon, -.qs-icon-button .ikonli-font-icon { - -fx-icon-size: 16px; - -fx-icon-fill: -jr-theme-accent; -} - /* Quick Settings: Online Fetchers */ .fetchers-container { -fx-padding: 12px;