diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd6edca1bdb..759a6d819756 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 library-specific override of the "Auto rename files if entry changes" preference to the library properties. [#769](https://github.com/JabRef/jabref-koppor/pull/769) - In directory libraries, a sidecar and its PDF are now renamed together to the configured filename pattern (Preferences > Linked files) whenever the entry is edited — e.g. changing the citation key renames both files. [#741](https://github.com/JabRef/jabref-koppor/pull/741) - The groups panel of a directory library now mirrors the folder structure: each subdirectory appears as a group containing the entries whose files live there (updated live as files change). [#740](https://github.com/JabRef/jabref-koppor/pull/740) - Directory libraries now save into their sidecar files: edits are written back automatically (debounced until typing pauses; Ctrl+S forces the write and no longer creates a `.bib`), the first edit of a PDF-only entry creates a Markdown sidecar (`X.md` with the Hayagriva data as frontmatter and the comment fields as notes body), renaming a citation key renames the YAML key, and deleting an entry removes it from its file (the file is trashed once empty, the PDF stays). Hand-written content that JabRef does not understand survives rewrites. [#739](https://github.com/JabRef/jabref-koppor/pull/739) diff --git a/docs/requirements/directory-library.md b/docs/requirements/directory-library.md index 92a86318b104..1c5066aa3e0a 100644 --- a/docs/requirements/directory-library.md +++ b/docs/requirements/directory-library.md @@ -82,11 +82,13 @@ entries or subgroups can be added to them, they cannot be dragged or edited. Needs: impl ## The sidecar and its PDF follow the configured filename pattern -`req~directory-library.pattern-rename~1` +`req~directory-library.pattern-rename~2` -When write-back touches a single-entry sidecar, the sidecar and its equally named PDF are -renamed together to the base name the configured filename pattern (Linked files preferences) -generates for the entry, keeping the pair in sync. Multi-entry files have no single generating +When write-back touches a single-entry sidecar and auto-renaming is enabled, the sidecar and +its equally named PDF are renamed together to the base name the configured filename pattern +(Linked files preferences) generates for the entry, keeping the pair in sync. Auto-renaming +follows the global "Auto rename files if entry changes" preference unless the library +properties override it (General tab; the same override applies to `.bib` libraries). Multi-entry files have no single generating entry and keep their name; occupied target names and pattern failures leave the current name untouched. Entry file links and the catalog follow the rename; the watcher does not re-import the renamed files. diff --git a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java index 696e8cfa738d..112e30ed7715 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java @@ -1,5 +1,7 @@ package org.jabref.gui.collab.metedatachange; +import java.util.Optional; + import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; @@ -72,14 +74,23 @@ private ScrollPane createDefaultDiffScrollPane(MetaDataDiff.Difference diff) { VBox diffContainer = new VBox(12); // Show both original and new values - diffContainer.getChildren().add(new Label(diff.originalObject().toString())); - diffContainer.getChildren().add(new Label(diff.newObject().toString())); + diffContainer.getChildren().add(new Label(formatValue(diff, diff.originalObject()))); + diffContainer.getChildren().add(new Label(formatValue(diff, diff.newObject()))); ScrollPane scrollPane = new ScrollPane(diffContainer); scrollPane.setFitToWidth(true); return scrollPane; } + private static String formatValue(MetaDataDiff.Difference diff, Object value) { + if (diff.differenceType() == MetaDataDiff.DifferenceType.AUTO_RENAME_FILES_ON_CHANGE + && value instanceof Optional override) { + return override.map(rename -> Boolean.TRUE.equals(rename) ? Localization.lang("Yes") : Localization.lang("No")) + .orElse(Localization.lang("Use global preference")); + } + return value.toString(); + } + private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { return switch (changeType) { case PROTECTED -> @@ -106,6 +117,8 @@ private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { Localization.lang("Library-specific file directory"); case CONTENT_SELECTOR -> Localization.lang("Content selectors"); + case AUTO_RENAME_FILES_ON_CHANGE -> + Localization.lang("Auto rename files if entry changes"); }; } } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java index 98af6a04f795..a32f3f52c874 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java @@ -2,6 +2,8 @@ import org.jabref.logic.FilePreferences; import org.jabref.logic.cleanup.RenamePdfCleanup; +import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.logic.util.strings.StringUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.FieldChangedEvent; @@ -14,19 +16,27 @@ public class AutoRenameFileOnEntryChange { private static final Logger LOGGER = LoggerFactory.getLogger(AutoRenameFileOnEntryChange.class); + private final BibDatabaseContext bibDatabaseContext; private final FilePreferences filePreferences; private final RenamePdfCleanup renamePdfCleanup; public AutoRenameFileOnEntryChange(BibDatabaseContext bibDatabaseContext, FilePreferences filePreferences) { + this.bibDatabaseContext = bibDatabaseContext; this.filePreferences = filePreferences; renamePdfCleanup = new RenamePdfCleanup(false, () -> bibDatabaseContext, filePreferences); } + /// The library properties override the global preference when set (`MetaData#getAutoRenameFilesOnChange`); + /// without a filename pattern there is nothing to rename to. + public static boolean isEnabled(BibDatabaseContext bibDatabaseContext, FilePreferences filePreferences) { + return !StringUtil.isBlank(filePreferences.getFileNamePattern()) + && bibDatabaseContext.getMetaData().getAutoRenameFilesOnChange().orElseGet(filePreferences::shouldAutoRenameFilesOnChange); + } + @Subscribe public void listen(FieldChangedEvent event) { - if (!filePreferences.shouldAutoRenameFilesOnChange() - || filePreferences.getFileNamePattern().isEmpty() - || filePreferences.getFileNamePattern() == null) { + // A directory library renames its sidecar/PDF pairs in the write-back (see SidecarWriteBack) + if (bibDatabaseContext.getLocation() == DatabaseLocation.DIRECTORY || !isEnabled(bibDatabaseContext, filePreferences)) { return; } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java index aa4eaea0ff1d..c7c6869aa976 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java @@ -19,6 +19,7 @@ import org.jabref.gui.clipboard.ClipBoardManager; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.exporter.SaveDatabaseAction; +import org.jabref.gui.externalfiles.AutoRenameFileOnEntryChange; import org.jabref.gui.git.GitConflictResolverDialog; import org.jabref.gui.git.GuiGitConflictResolverStrategy; import org.jabref.gui.preferences.GuiPreferences; @@ -160,8 +161,12 @@ private void showLibraryTab(DirectoryLibraryScanner.ScanResult scanResult, PdfEn libraryTab.updateTabTitle(false); BibDatabaseContext databaseContext = scanResult.databaseContext(); - Function> fileNameGenerator = entry -> FileUtil.createFileNameFromPattern( - databaseContext.getDatabase(), entry, preferences.getFilePreferences().getFileNamePattern()); + // Evaluated per write, so a changed preference (global or library) takes effect immediately + // [impl->req~directory-library.pattern-rename~2] + Function> fileNameGenerator = entry -> + AutoRenameFileOnEntryChange.isEnabled(databaseContext, preferences.getFilePreferences()) + ? FileUtil.createFileNameFromPattern(databaseContext.getDatabase(), entry, preferences.getFilePreferences().getFileNamePattern()) + : Optional.empty(); GuiGitConflictResolverStrategy conflictResolver = new GuiGitConflictResolverStrategy( new GitConflictResolverDialog(dialogService, preferences, stateManager)); DirectoryLibrarySynchronizer synchronizer = new DirectoryLibrarySynchronizer( diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java index e745030452ff..2d22941b6b21 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java @@ -2,9 +2,11 @@ import java.nio.charset.Charset; import java.nio.file.Path; +import java.util.Optional; import java.util.function.UnaryOperator; import javafx.application.Platform; +import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; @@ -37,6 +39,7 @@ public class GeneralPropertiesView extends AbstractPropertiesTabView> autoRenameFilesOnChange; @FXML private Button libSpecificFileDirSwitchId; @FXML private Button userSpecificFileDirSwitchId; @FXML private Button laTexSpecificFileDirSwitchId; @@ -101,6 +104,14 @@ public void initialize() { change -> change.getControlNewText().length() <= 1 ? change : null; keywordSeparator.setTextFormatter(new TextFormatter<>(singleCharacterFilter)); + boolean globalAutoRename = preferences.getFilePreferences().shouldAutoRenameFilesOnChange(); + new ViewModelListCellFactory>() + .withText(choice -> choice.map(GeneralPropertiesView::yesOrNo) + .orElse(Localization.lang("Use global preference (%0)", yesOrNo(globalAutoRename)))) + .install(autoRenameFilesOnChange); + autoRenameFilesOnChange.setItems(FXCollections.observableArrayList(Optional.empty(), Optional.of(true), Optional.of(false))); + autoRenameFilesOnChange.valueProperty().bindBidirectional(viewModel.autoRenameFilesOnChangeProperty()); + userSpecificFileDirectoryTooltip.setText(Localization.lang("User-specific file directory: %0", preferences.getFilePreferences().getUserAndHost())); userSpecificFileDirectory.setTooltip(userSpecificFileDirectoryTooltip); @@ -179,4 +190,8 @@ void userSpecificFileDirPathSwitch() { void laTexSpecificFileDirPathSwitch() { viewModel.togglePath(viewModel.laTexFileDirectoryProperty()); } + + private static String yesOrNo(boolean value) { + return value ? Localization.lang("Yes") : Localization.lang("No"); + } } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java index 31e42d327ff4..05ed0a05d4da 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java @@ -47,6 +47,7 @@ public class GeneralPropertiesViewModel implements PropertiesTabViewModel { private final StringProperty userSpecificFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty laTexFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty keywordSeparatorProperty = new SimpleStringProperty(""); + private final ObjectProperty> autoRenameFilesOnChangeProperty = new SimpleObjectProperty<>(Optional.empty()); private final Validator librarySpecificFileDirectoryValidator; private final Validator userSpecificFileDirectoryValidator; @@ -91,6 +92,7 @@ public void setValues(MetaData metaData) { userSpecificFileDirectoryProperty.setValue(metaData.getUserFileDirectory(preferences.getFilePreferences().getUserAndHost()).orElse("").trim()); laTexFileDirectoryProperty.setValue(metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()).map(Path::toString).orElse("")); keywordSeparatorProperty.setValue(metaData.getKeywordSeparator().map(Object::toString).orElse("")); + autoRenameFilesOnChangeProperty.setValue(metaData.getAutoRenameFilesOnChange()); } @Override @@ -120,6 +122,7 @@ public void storeSettings(MetaData metaData) { } storeKeywordSeparator(metaData); + autoRenameFilesOnChangeProperty.getValue().ifPresentOrElse(metaData::setAutoRenameFilesOnChange, metaData::clearAutoRenameFilesOnChange); } /// The separator and the group definitions the migration rewrites are both metadata, so the @@ -225,6 +228,10 @@ public StringProperty keywordSeparatorProperty() { return this.keywordSeparatorProperty; } + public ObjectProperty> autoRenameFilesOnChangeProperty() { + return this.autoRenameFilesOnChangeProperty; + } + private Path getBrowseDirectory(String configuredDir) { Optional libPath = this.databaseContext.getDatabasePath(); Path workingDir = preferences.getFilePreferences().getWorkingDirectory(); diff --git a/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml b/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml index ab0665cee773..390a93dc31a5 100644 --- a/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml @@ -135,6 +135,10 @@ GridPane.columnIndex="0" GridPane.rowIndex="7"/> +