diff --git a/CHANGELOG.md b/CHANGELOG.md index 52105585c0f..5760240dfdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) - We added probable search hits instead of exact matches. Sorting by hit score can be done by the new score table column. [#11542](https://github.com/JabRef/jabref/pull/11542) - We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) - When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) @@ -54,6 +55,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We improved the performance when pasting and importing entries in an existing library. [#11843](https://github.com/JabRef/jabref/pull/11843) - When fulltext search is selected but indexing is deactivated, a dialog is now shown asking if the user wants to enable indexing now [#9491](https://github.com/JabRef/jabref/issues/9491) - We changed instances of 'Search Selected' to 'Search Pre-configured' in Web Search Preferences UI. [#11871](https://github.com/JabRef/jabref/pull/11871) +- We added a new CSS style class `main-table` for the main table. [#11881](https://github.com/JabRef/jabref/pull/11881) ### Fixed @@ -81,6 +83,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where unescaped braces in the arXiv fetcher were not treated. [#11704](https://github.com/JabRef/jabref/issues/11704) - We fixed an issue where HTML instead of the fulltext pdf was downloaded when importing arXiv entries. [#4913](https://github.com/JabRef/jabref/issues/4913) - We fixed an issue where the keywords and crossref fields were not properly focused. [#11177](https://github.com/JabRef/jabref/issues/11177) +- We fixed an issue where the Undo/Redo buttons were active even when all libraries are closed. [#11837](https://github.com/JabRef/jabref/issues/11837) - We fixed an issue where recently opened files were not displayed in the main menu properly. [#9042](https://github.com/JabRef/jabref/issues/9042) - We fixed an issue where the DOI lookup would show an error when a DOI was found for an entry. [#11850](https://github.com/JabRef/jabref/issues/11850) diff --git a/docs/code-howtos/faq.md b/docs/code-howtos/faq.md index c75f168b093..e92bb62daa4 100644 --- a/docs/code-howtos/faq.md +++ b/docs/code-howtos/faq.md @@ -65,6 +65,11 @@ More information on the architecture can be found at [../getting-into-the-code/h This test is triggered when any kind of documentation is touched (be it the JabRef docs, or JavaDoc in code). If you changed something in the documentation, and particularly added/changed any links (to external files or websites), check if the links are correct and working. If you didn't change/add any link, or added correct links, the test is most probably failing due to any of the existing links being broken, and thus can be ignored (in the context of your contribution). +### Failing Fetcher tests + +Fetcher tests are run when any file in the `.../fetcher` directory has been touched. If you have changed any fetcher logic, check if the changes are correct. You can look for more details on how to locally run fetcher tests [here](https://devdocs.jabref.org/code-howtos/testing.html#fetchers-in-tests). +Otherwise, since these tests depend on remote services, their failure can also be caused by the network or an external server, and thus can be ignored in the context of your contribution. For more information, you can look [here](https://devdocs.jabref.org/code-howtos/fetchers.html#committing-and-pushing-changes-to-fetcher-files). + ## Gradle outputs ### `ANTLR Tool version 4.12.0 used for code generation does not match the current runtime version 4.13.1` diff --git a/docs/code-howtos/fetchers.md b/docs/code-howtos/fetchers.md index a0be55e912d..b9520cb56be 100644 --- a/docs/code-howtos/fetchers.md +++ b/docs/code-howtos/fetchers.md @@ -83,3 +83,10 @@ new BuildInfo().springerNatureAPIKey ``` When executing `./gradlew run`, gradle executes `processResources` and populates `build/build.properties` accordingly. However, when working directly in the IDE, Eclipse keeps reading `build.properties` from `src/main/resources`. In IntelliJ, the task `JabRef Main` is executing `./gradlew processResources` before running JabRef from the IDE to ensure the `build.properties` is properly populated. + +## Committing and pushing changes to fetcher files + +Fetcher tests are run when a PR contains changes touching any file in the `src/main/java/org/jabref/logic/importer/fetcher/` directory. +Since these tests rely on remote services, some of them may fail due to the network or the external server. + +To learn more about doing fetcher tests locally, see Fetchers in tests in [Testing](https://devdocs.jabref.org/code-howtos/testing.html). diff --git a/docs/code-howtos/testing.md b/docs/code-howtos/testing.md index 084a3350de7..d32bf51b62c 100644 --- a/docs/code-howtos/testing.md +++ b/docs/code-howtos/testing.md @@ -140,6 +140,16 @@ docker run -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=jabref -p 3800:3307 mys Set the environment variable `DBMS` to `mysql`. +## Fetchers in tests + +Fetcher tests can be run locally by executing the Gradle task `fetcherTest`. This can be done by running the following command in the command line: + +```shell +./gradlew fetcherTest +``` + +Alternatively, if one is using IntelliJ, this can also be done by double-clicking the `fetcherTest` task under the `other` group in the Gradle Tool window (`JabRef > Tasks > other > fetcherTest`). + ## Advanced testing and further reading On top of basic unit testing, there are more ways to test a software: diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index cd22dd41ad4..852af1ba396 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -149,6 +149,7 @@ requires jvm.openai; requires langchain4j; requires langchain4j.core; + requires langchain4j.google.ai.gemini; requires langchain4j.hugging.face; requires langchain4j.mistral.ai; requires langchain4j.open.ai; @@ -188,6 +189,5 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; - requires langchain4j.google.ai.gemini; // endregion } diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index 14ae1873d4a..49d581717c7 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -340,7 +340,6 @@ TextFlow > .hyperlink:visited, -fx-underline: true; } - .glyph-icon { /* This adjusts text alignment within the bounds of text nodes so that the text is always vertically centered within the bounds. Based on @@ -1517,4 +1516,221 @@ We want to have a look that matches our icons in the tool-bar */ -fx-background-color: transparent; } +/* endregion */ + +/* region: maintable css */ + + +.main-table .column-icon { + -fx-alignment: baseline-center; + -fx-padding: 0; +} + +.main-table .column-header.column-icon > .label { + -fx-padding: 0; + -fx-alignment: baseline-center; +} + +.main-table .empty-special-field { + visibility: hidden; +} + +.main-table .table-row-cell:hover .empty-special-field { + visibility: visible; + -fx-icon-color: -jr-gray-2; + -fx-fill: -jr-gray-2; +} + +.main-table .table-row-cell:dragOver-bottom { + -fx-border-color: -jr-drag-target; + -fx-border-width: 0 0 2 0; + -fx-padding: 0 0 -2 0; +} + +.main-table .table-row-cell:dragOver-center { + -fx-border-color: -jr-drag-target; + -fx-border-width: 1 1 1 1; + -fx-padding: -1 -1 -1 -1; + -fx-background-color: -jr-drag-target-hover; +} + +.main-table .table-row-cell:dragOver-top { + -fx-border-color: -jr-drag-target; + -fx-border-width: 2 0 0 0; + -fx-padding: -2 0 0 0; +} + +/** even and odd are swapped around somehow. Below "odd" matches lines 2, 4, ... **/ + +.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:odd:selected, +.main-table .table-row-cell:matching-search-and-groups:odd:focused, +.main-table .table-row-cell:matching-search-and-groups:odd:focused:hover, +.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:odd:hover { + -fx-background-color: -jr-hover; +} + +.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:odd:selected, +.main-table .table-row-cell:matching-search-not-groups:odd:focused, +.main-table .table-row-cell:matching-search-not-groups:odd:focused:hover, +.main-table .table-row-cell:matching-search-not-groups:focused:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:matching-search-not-groups:odd:hover { + -fx-background-color: -jr-hover; +} + +.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:odd:selected, +.main-table .table-row-cell:matching-groups-not-search:odd:focused, +.main-table .table-row-cell:matching-groups-not-search:odd:focused:hover, +.main-table .table-row-cell:matching-groups-not-search:focused:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:matching-groups-not-search:odd:hover { + -fx-background-color: -jr-hover; +} + +.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:odd:selected, +.main-table .table-row-cell:not-matching-search-and-groups:odd:focused, +.main-table .table-row-cell:not-matching-search-and-groups:odd:focused:hover, +.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:odd:hover { + -fx-background-color: -jr-hover; +} + +.rating > .container { + -fx-spacing: 2; +} + +.rating > .container > .button { + -fx-pref-width: 16; + -fx-pref-height: 10; + -fx-background-repeat: no-repeat no-repeat; + -fx-background-size: 16 16; + -fx-border-style: none; + -fx-border-width: 0; + -fx-padding: 0; +} + +.rating > .container > .button.strong { + +} + +.rating > .container > .button:hover { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.6), 8, 0.0, 0, 0); +} + + /* endregion */ diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 93901045848..791eec0dee1 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -260,8 +260,8 @@ private EntryEditor createEntryEditor() { Supplier tabSupplier = () -> this; return new EntryEditor(this, // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. - new UndoAction(tabSupplier, dialogService, stateManager), - new RedoAction(tabSupplier, dialogService, stateManager)); + new UndoAction(tabSupplier, undoManager, dialogService, stateManager), + new RedoAction(tabSupplier, undoManager, dialogService, stateManager)); } private static void addChangedInformation(StringBuilder text, String fileName) { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 7e99897cadb..88c282366f2 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -299,7 +299,7 @@ private List createTabs() { tabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); tabs.add(new SciteTab(preferences, taskExecutor, dialogService)); tabs.add(new CitationRelationsTab(dialogService, databaseContext, - undoManager, stateManager, fileMonitor, preferences, libraryTab, taskExecutor)); + undoManager, stateManager, fileMonitor, preferences, libraryTab, taskExecutor, bibEntryTypesManager)); tabs.add(new RelatedArticlesTab(buildInfo, databaseContext, preferences, dialogService, taskExecutor)); sourceTab = new SourceTab( databaseContext, diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java index f6f8e9ca111..3ab2a167b9c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java @@ -1,6 +1,7 @@ package org.jabref.gui.entryeditor.citationrelationtab; import java.io.IOException; +import java.io.StringWriter; import java.net.URI; import java.util.Arrays; import java.util.List; @@ -12,11 +13,15 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.css.PseudoClass; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.DialogPane; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; import javafx.scene.control.SplitPane; import javafx.scene.control.ToggleButton; import javafx.scene.control.Tooltip; @@ -28,6 +33,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; +import org.jabref.gui.collab.entrychange.PreviewWithSourceTab; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.entryeditor.EntryEditorTab; import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher; @@ -36,11 +42,17 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.util.NoSelectionModel; import org.jabref.gui.util.ViewModelListCellFactory; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.database.DuplicateCheck; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.os.OS; import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.database.BibDatabaseModeDetection; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; @@ -51,6 +63,8 @@ import com.tobiasdiez.easybind.EasyBind; import org.controlsfx.control.CheckListView; +import org.fxmisc.flowless.VirtualizedScrollPane; +import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +88,7 @@ public class CitationRelationsTab extends EntryEditorTab { private final BibEntryRelationsRepository bibEntryRelationsRepository; private final CitationsRelationsTabViewModel citationsRelationsTabViewModel; private final DuplicateCheck duplicateCheck; + private final BibEntryTypesManager entryTypesManager; public CitationRelationsTab(DialogService dialogService, BibDatabaseContext databaseContext, @@ -82,7 +97,8 @@ public CitationRelationsTab(DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, GuiPreferences preferences, LibraryTab libraryTab, - TaskExecutor taskExecutor) { + TaskExecutor taskExecutor, + BibEntryTypesManager bibEntryTypesManager) { this.dialogService = dialogService; this.databaseContext = databaseContext; this.preferences = preferences; @@ -91,7 +107,8 @@ public CitationRelationsTab(DialogService dialogService, setText(Localization.lang("Citation relations")); setTooltip(new Tooltip(Localization.lang("Show articles related by citation"))); - this.duplicateCheck = new DuplicateCheck(new BibEntryTypesManager()); + this.entryTypesManager = bibEntryTypesManager; + this.duplicateCheck = new DuplicateCheck(entryTypesManager); this.bibEntryRelationsRepository = new BibEntryRelationsRepository(new SemanticScholarFetcher(preferences.getImporterPreferences()), new BibEntryRelationsCache()); citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferences, undoManager, stateManager, dialogService, fileUpdateMonitor, taskExecutor); @@ -254,6 +271,14 @@ private void styleFetchedListView(CheckListView listView) vContainer.getChildren().addLast(openWeb); } + Button showEntrySource = IconTheme.JabRefIcons.SOURCE.asButton(); + showEntrySource.setTooltip(new Tooltip(Localization.lang("%0 source", "BibTeX"))); + showEntrySource.setOnMouseClicked(event -> { + showEntrySourceDialog(entry.entry()); + }); + + vContainer.getChildren().addLast(showEntrySource); + hContainer.getChildren().addAll(entryNode, separator, vContainer); hContainer.getStyleClass().add("entry-container"); @@ -270,6 +295,43 @@ private void styleFetchedListView(CheckListView listView) listView.setSelectionModel(new NoSelectionModel<>()); } + /** + * @implNote This code is similar to {@link PreviewWithSourceTab#getSourceString(BibEntry, BibDatabaseMode, FieldPreferences, BibEntryTypesManager)}. + */ + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); + new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); + return writer.toString(); + } + + private void showEntrySourceDialog(BibEntry entry) { + CodeArea ca = new CodeArea(); + try { + ca.appendText(getSourceString(entry, databaseContext.getMode(), preferences.getFieldPreferences(), this.entryTypesManager)); + } catch (IOException e) { + LOGGER.warn("Incorrect entry, could not load source:", e); + return; + } + + ca.setWrapText(true); + ca.setPadding(new Insets(0, 10, 0, 10)); + ca.showParagraphAtTop(0); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + scrollPane.setContent(new VirtualizedScrollPane<>(ca)); + + DialogPane dialogPane = new DialogPane(); + dialogPane.setPrefSize(800, 400); + dialogPane.setContent(scrollPane); + String title = Localization.lang("Show BibTeX source"); + + dialogService.showCustomDialogAndWait(title, dialogPane, ButtonType.OK); + } + /** * Method to style heading labels * diff --git a/src/main/java/org/jabref/gui/frame/MainMenu.java b/src/main/java/org/jabref/gui/frame/MainMenu.java index 7500cf72e03..96ab72d70d4 100644 --- a/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -2,8 +2,6 @@ import java.util.function.Supplier; -import javax.swing.undo.UndoManager; - import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; @@ -70,6 +68,7 @@ import org.jabref.gui.slr.StartNewStudyAction; import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory; import org.jabref.gui.texparser.ParseLatexAction; +import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.RedoAction; import org.jabref.gui.undo.UndoAction; import org.jabref.gui.util.UiTaskExecutor; @@ -98,7 +97,7 @@ public class MainMenu extends MenuBar { private final DialogService dialogService; private final JournalAbbreviationRepository abbreviationRepository; private final BibEntryTypesManager entryTypesManager; - private final UndoManager undoManager; + private final CountingUndoManager undoManager; private final ClipBoardManager clipBoardManager; private final Supplier openDatabaseActionSupplier; private final AiService aiService; @@ -114,7 +113,7 @@ public MainMenu(JabRefFrame frame, DialogService dialogService, JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager entryTypesManager, - UndoManager undoManager, + CountingUndoManager undoManager, ClipBoardManager clipBoardManager, Supplier openDatabaseActionSupplier, AiService aiService) { @@ -184,8 +183,8 @@ private void createMenu() { ); edit.getItems().addAll( - factory.createMenuItem(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, dialogService, stateManager)), - factory.createMenuItem(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, dialogService, stateManager)), + factory.createMenuItem(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), + factory.createMenuItem(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/frame/MainToolBar.java b/src/main/java/org/jabref/gui/frame/MainToolBar.java index 03bbc72e3b0..e07467e2c3d 100644 --- a/src/main/java/org/jabref/gui/frame/MainToolBar.java +++ b/src/main/java/org/jabref/gui/frame/MainToolBar.java @@ -133,8 +133,8 @@ private void createToolBar() { new Separator(Orientation.VERTICAL), new HBox( - factory.createIconButton(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, dialogService, stateManager)), - factory.createIconButton(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, dialogService, stateManager)), + factory.createIconButton(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), + factory.createIconButton(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), factory.createIconButton(StandardActions.CUT, new EditAction(StandardActions.CUT, frame::getCurrentLibraryTab, stateManager, undoManager)), factory.createIconButton(StandardActions.COPY, new EditAction(StandardActions.COPY, frame::getCurrentLibraryTab, stateManager, undoManager)), factory.createIconButton(StandardActions.PASTE, new EditAction(StandardActions.PASTE, frame::getCurrentLibraryTab, stateManager, undoManager))), diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.css b/src/main/java/org/jabref/gui/maintable/MainTable.css deleted file mode 100644 index f468033b854..00000000000 --- a/src/main/java/org/jabref/gui/maintable/MainTable.css +++ /dev/null @@ -1,210 +0,0 @@ -.column-icon { - -fx-alignment: baseline-center; - -fx-padding: 0; -} - -.column-header.column-icon > .label { - -fx-padding: 0; - -fx-alignment: baseline-center; -} - -.empty-special-field { - visibility: hidden; -} - -.table-row-cell:hover .empty-special-field { - visibility: visible; - -fx-icon-color: -jr-gray-2; - -fx-fill: -jr-gray-2; -} - -.table-row-cell:dragOver-bottom { - -fx-border-color: -jr-drag-target; - -fx-border-width: 0 0 2 0; - -fx-padding: 0 0 -2 0; -} - -.table-row-cell:dragOver-center { - -fx-border-color: -jr-drag-target; - -fx-border-width: 1 1 1 1; - -fx-padding: -1 -1 -1 -1; - -fx-background-color: -jr-drag-target-hover; -} - -.table-row-cell:dragOver-top { - -fx-border-color: -jr-drag-target; - -fx-border-width: 2 0 0 0; - -fx-padding: -2 0 0 0; -} - -/** even and odd are swapped around somehow. Below "odd" matches lines 2, 4, ... **/ - -.table-row-cell:matching-search-and-groups { - -fx-background-color: -jr-match-1-even; -} -.table-row-cell:matching-search-and-groups > .table-cell { - -fx-text-fill: -jr-match-1-text-color; -} -.table-row-cell:matching-search-and-groups:focused > .table-cell { - -fx-text-fill: -fx-focused-text-base-color; -} -.table-row-cell:matching-search-and-groups:focused:hover > .table-cell { - -fx-text-fill: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-search-and-groups:hover > .table-cell { - -fx-text-fill: -jr-hover-text; -} -.table-row-cell:matching-search-and-groups > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-match-1-text-color; -} -.table-row-cell:matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-hover-text; -} -.table-row-cell:matching-search-and-groups:odd { - -fx-background-color: -jr-match-1-odd; -} -.table-row-cell:matching-search-and-groups:odd:selected, -.table-row-cell:matching-search-and-groups:odd:focused, -.table-row-cell:matching-search-and-groups:odd:focused:hover, -.table-row-cell:matching-search-and-groups:focused:hover { - -fx-background-color: -jr-selected; -} -.table-row-cell:matching-search-and-groups:odd:hover { - -fx-background-color: -jr-hover; -} - -.table-row-cell:matching-search-not-groups { - -fx-background-color: -jr-match-2-even; -} -.table-row-cell:matching-search-not-groups > .table-cell { - -fx-text-fill: -jr-match-2-text-color; -} -.table-row-cell:matching-search-not-groups:focused > .table-cell { - -fx-text-fill: -fx-focused-text-base-color; -} -.table-row-cell:matching-search-not-groups:focused:hover > .table-cell { - -fx-text-fill: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-search-not-groups:focused:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-search-not-groups:hover > .table-cell { - -fx-text-fill: -jr-hover-text; -} -.table-row-cell:matching-search-not-groups > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-match-2-text-color; -} -.table-row-cell:matching-search-not-groups:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-hover-text; -} -.table-row-cell:matching-search-not-groups:odd { - -fx-background-color: -jr-match-2-odd; -} -.table-row-cell:matching-search-not-groups:odd:selected, -.table-row-cell:matching-search-not-groups:odd:focused, -.table-row-cell:matching-search-not-groups:odd:focused:hover, -.table-row-cell:matching-search-not-groups:focused:hover { - -fx-background-color: -jr-selected; -} -.table-row-cell:matching-search-not-groups:odd:hover { - -fx-background-color: -jr-hover; -} - -.table-row-cell:matching-groups-not-search { - -fx-background-color: -jr-match-3-even; -} -.table-row-cell:matching-groups-not-search > .table-cell { - -fx-text-fill: -jr-match-3-text-color; -} -.table-row-cell:matching-groups-not-search:focused > .table-cell { - -fx-text-fill: -fx-focused-text-base-color; -} -.table-row-cell:matching-groups-not-search:focused:hover > .table-cell { - -fx-text-fill: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-groups-not-search:focused:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-maintable-focused-hover-text; -} -.table-row-cell:matching-groups-not-search:hover > .table-cell { - -fx-text-fill: -jr-hover-text; -} -.table-row-cell:matching-groups-not-search > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-match-3-text-color; -} -.table-row-cell:matching-groups-not-search:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-hover-text; -} -.table-row-cell:matching-groups-not-search:odd { - -fx-background-color: -jr-match-3-odd; -} -.table-row-cell:matching-groups-not-search:odd:selected, -.table-row-cell:matching-groups-not-search:odd:focused, -.table-row-cell:matching-groups-not-search:odd:focused:hover, -.table-row-cell:matching-groups-not-search:focused:hover { - -fx-background-color: -jr-selected; -} -.table-row-cell:matching-groups-not-search:odd:hover { - -fx-background-color: -jr-hover; -} - -.table-row-cell:not-matching-search-and-groups { - -fx-background-color: -jr-match-4-even; -} -.table-row-cell:not-matching-search-and-groups > .table-cell { - -fx-text-fill: -jr-match-4-text-color; -} -.table-row-cell:not-matching-search-and-groups:focused > .table-cell { - -fx-text-fill: -fx-focused-text-base-color; -} -.table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell { - -fx-text-fill: -jr-maintable-focused-hover-text; -} -.table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-maintable-focused-hover-text; -} -.table-row-cell:not-matching-search-and-groups:hover > .table-cell { - -fx-text-fill: -jr-hover-text; -} -.table-row-cell:not-matching-search-and-groups > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-match-4-text-color; -} -.table-row-cell:not-matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { - -fx-icon-color: -jr-hover-text; -} -.table-row-cell:not-matching-search-and-groups:odd { - -fx-background-color: -jr-match-4-odd; -} -.table-row-cell:not-matching-search-and-groups:odd:selected, -.table-row-cell:not-matching-search-and-groups:odd:focused, -.table-row-cell:not-matching-search-and-groups:odd:focused:hover, -.table-row-cell:not-matching-search-and-groups:focused:hover { - -fx-background-color: -jr-selected; -} -.table-row-cell:not-matching-search-and-groups:odd:hover { - -fx-background-color: -jr-hover; -} - -.rating > .container { - -fx-spacing: 2; -} - -.rating > .container > .button { - -fx-pref-width: 16; - -fx-pref-height: 10; - -fx-background-repeat: no-repeat no-repeat; - -fx-background-size: 16 16; - -fx-border-style: none; - -fx-border-width: 0; - -fx-padding: 0; -} - -.rating > .container > .button.strong { - -} - -.rating > .container > .button:hover { - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.6), 8, 0.0, 0, 0); -} diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index e8939f9aec8..4a94fd56b5f 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -114,6 +114,8 @@ public MainTable(MainTableDataModel model, this.setOnDragOver(this::handleOnDragOverTableView); this.setOnDragDropped(this::handleOnDragDroppedTableView); + this.getStyleClass().add("main-table"); + MainTableColumnFactory mainTableColumnFactory = new MainTableColumnFactory( database, preferences, @@ -185,8 +187,6 @@ public MainTable(MainTableDataModel model, // Enable sorting model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty()); - this.getStylesheets().add(Objects.requireNonNull(MainTable.class.getResource("MainTable.css")).toExternalForm()); - // Store visual state new PersistenceVisualStateTable(this, mainTablePreferences.getColumnPreferences()).addListeners(); diff --git a/src/main/java/org/jabref/gui/search/SearchResultsTable.java b/src/main/java/org/jabref/gui/search/SearchResultsTable.java index 5bb4049ecae..76ebb2cff26 100644 --- a/src/main/java/org/jabref/gui/search/SearchResultsTable.java +++ b/src/main/java/org/jabref/gui/search/SearchResultsTable.java @@ -12,7 +12,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.maintable.BibEntryTableViewModel; -import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableColumnFactory; import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.maintable.PersistenceVisualStateTable; @@ -35,6 +34,8 @@ public SearchResultsTable(SearchResultsTableDataModel model, TaskExecutor taskExecutor) { super(); + this.getStyleClass().add("main-table"); + MainTablePreferences mainTablePreferences = preferences.getMainTablePreferences(); List> allCols = new MainTableColumnFactory( @@ -68,8 +69,6 @@ public SearchResultsTable(SearchResultsTableDataModel model, // Enable sorting model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty()); - this.getStylesheets().add(MainTable.class.getResource("MainTable.css").toExternalForm()); - // Store visual state new PersistenceVisualStateTable(this, preferences.getSearchDialogColumnPreferences()).addListeners(); diff --git a/src/main/java/org/jabref/gui/undo/RedoAction.java b/src/main/java/org/jabref/gui/undo/RedoAction.java index 4ec944731e7..d5d3170a993 100644 --- a/src/main/java/org/jabref/gui/undo/RedoAction.java +++ b/src/main/java/org/jabref/gui/undo/RedoAction.java @@ -4,36 +4,42 @@ import javax.swing.undo.CannotRedoException; +import javafx.beans.binding.Bindings; + import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + /** * @implNote See also {@link UndoAction} */ public class RedoAction extends SimpleCommand { private final Supplier tabSupplier; private final DialogService dialogService; + private final CountingUndoManager undoManager; - public RedoAction(Supplier tabSupplier, DialogService dialogService, StateManager stateManager) { + public RedoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; + this.undoManager = undoManager; - // TODO: The old listener should be removed. Otherwise, memory consumption will increase. - stateManager.activeTabProperty().addListener((observable, oldValue, activeLibraryTab) -> { - activeLibraryTab.ifPresent(libraryTab -> - this.executable.bind(libraryTab.getUndoManager().getRedoableProperty())); - }); + this.executable.bind(Bindings.and(needsDatabase(stateManager), undoManager.getRedoableProperty())); } @Override public void execute() { LibraryTab libraryTab = this.tabSupplier.get(); try { - libraryTab.getUndoManager().redo(); - dialogService.notify(Localization.lang("Redo")); + if (undoManager.canRedo()) { + undoManager.redo(); + dialogService.notify(Localization.lang("Redo")); + } else { + throw new CannotRedoException(); + } } catch (CannotRedoException ex) { dialogService.notify(Localization.lang("Nothing to redo") + '.'); } diff --git a/src/main/java/org/jabref/gui/undo/UndoAction.java b/src/main/java/org/jabref/gui/undo/UndoAction.java index a61551bcf6b..5eab905a9f8 100644 --- a/src/main/java/org/jabref/gui/undo/UndoAction.java +++ b/src/main/java/org/jabref/gui/undo/UndoAction.java @@ -4,35 +4,42 @@ import javax.swing.undo.CannotUndoException; +import javafx.beans.binding.Bindings; + import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + /** * @implNote See also {@link RedoAction} */ public class UndoAction extends SimpleCommand { private final Supplier tabSupplier; private final DialogService dialogService; + private final CountingUndoManager undoManager; - public UndoAction(Supplier tabSupplier, DialogService dialogService, StateManager stateManager) { + public UndoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; + this.undoManager = undoManager; - stateManager.activeTabProperty().addListener((observable, oldValue, activeLibraryTab) -> { - activeLibraryTab.ifPresent(libraryTab -> - this.executable.bind(libraryTab.getUndoManager().getUndoableProperty())); - }); + this.executable.bind(Bindings.and(needsDatabase(stateManager), undoManager.getUndoableProperty())); } @Override public void execute() { LibraryTab libraryTab = this.tabSupplier.get(); try { - libraryTab.getUndoManager().undo(); - dialogService.notify(Localization.lang("Undo")); + if (undoManager.canUndo()) { + undoManager.undo(); + dialogService.notify(Localization.lang("Undo")); + } else { + throw new CannotUndoException(); + } } catch (CannotUndoException ex) { dialogService.notify(Localization.lang("Nothing to undo") + '.'); }