Skip to content

Commit 37c8d2c

Browse files
committed
feat: add toggle functionality for journal abbreviation lists
This feature allows users to enable/disable specific journal abbreviation lists, including both the built-in list and external CSV files, without removing them from configuration. - Added toggle controls in UI with visual indicators for enabled/disabled states - Implemented filtering of abbreviations based on source enabled state - Ensured toggle states persist between application sessions - Optimized performance with efficient repository loading - Added comprehensive test coverage for new functionality Closes #12468
1 parent 859dafb commit 37c8d2c

14 files changed

+1107
-140
lines changed

CHANGELOG.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2828
- We added a new functionality where author names having multiple spaces in-between will be considered as separate user block as it does for " and ". [#12701](https://github.com/JabRef/jabref/issues/12701)
2929
- We enhanced support for parsing XMP metadata from PDF files. [#12829](https://github.com/JabRef/jabref/issues/12829)
3030
- We added a "Preview" header in the JStyles tab in the "Select style" dialog, to make it consistent with the CSL styles tab. [#12838](https://github.com/JabRef/jabref/pull/12838)
31-
- We added path validation to file directories in library properties dialog. [#11840](https://github.com/JabRef/jabref/issues/11840)
31+
- We added ability to toggle journal abbreviation lists (including built-in and external CSV files) on/off in preferences. [#{Issue Number}](https://github.com/JabRef/jabref/pull/{Issue Number})
3232

3333
### Changed
3434

@@ -62,11 +62,9 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
6262
- A tooltip now appears after 300ms (instead of 2s). [#12649](https://github.com/JabRef/jabref/issues/12649)
6363
- We improved search in preferences and keybindings. [#12647](https://github.com/JabRef/jabref/issues/12647)
6464
- We improved the performance of the LibreOffice integration when inserting CSL citations/bibliography. [#12851](https://github.com/JabRef/jabref/pull/12851)
65-
- 'Affected fields' and 'Do not wrap when saving' are now displayed as tags. [#12550](https://github.com/JabRef/jabref/issues/12550)
6665

6766
### Fixed
6867

69-
- We fixed an issue where warning signs were improperly positioned next to text fields containing capital letters. [#12884](https://github.com/JabRef/jabref/issues/12884)
7068
- We fixed an issue where the drag'n'drop functionality in entryeditor did not work [#12561](https://github.com/JabRef/jabref/issues/12561)
7169
- We fixed an issue where the F4 shortcut key did not work without opening the right-click context menu. [#6101](https://github.com/JabRef/jabref/pull/6101)
7270
- We fixed an issue where the file renaming dialog was not resizable and its size was too small for long file names. [#12518](https://github.com/JabRef/jabref/pull/12518)

src/main/java/org/jabref/gui/frame/MainMenu.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ private void createMenu() {
263263
new SeparatorMenuItem(),
264264

265265
factory.createSubMenu(StandardActions.ABBREVIATE,
266-
factory.createMenuItem(StandardActions.ABBREVIATE_DEFAULT, new AbbreviateAction(StandardActions.ABBREVIATE_DEFAULT, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)),
267-
factory.createMenuItem(StandardActions.ABBREVIATE_DOTLESS, new AbbreviateAction(StandardActions.ABBREVIATE_DOTLESS, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)),
268-
factory.createMenuItem(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, new AbbreviateAction(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager))),
266+
factory.createMenuItem(StandardActions.ABBREVIATE_DEFAULT, new AbbreviateAction(StandardActions.ABBREVIATE_DEFAULT, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), taskExecutor, undoManager)),
267+
factory.createMenuItem(StandardActions.ABBREVIATE_DOTLESS, new AbbreviateAction(StandardActions.ABBREVIATE_DOTLESS, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), taskExecutor, undoManager)),
268+
factory.createMenuItem(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, new AbbreviateAction(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), taskExecutor, undoManager))),
269269

270-
factory.createMenuItem(StandardActions.UNABBREVIATE, new AbbreviateAction(StandardActions.UNABBREVIATE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager))
270+
factory.createMenuItem(StandardActions.UNABBREVIATE, new AbbreviateAction(StandardActions.UNABBREVIATE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), taskExecutor, undoManager))
271271
);
272272

273273
Menu lookupIdentifiers = factory.createSubMenu(StandardActions.LOOKUP_DOC_IDENTIFIER);

src/main/java/org/jabref/gui/journals/AbbreviateAction.java

+39-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44
import java.util.function.Supplier;
5+
import java.nio.file.Path;
56

67
import javax.swing.undo.UndoManager;
78

@@ -14,6 +15,7 @@
1415
import org.jabref.gui.undo.NamedCompound;
1516
import org.jabref.logic.journals.JournalAbbreviationPreferences;
1617
import org.jabref.logic.journals.JournalAbbreviationRepository;
18+
import org.jabref.logic.journals.JournalAbbreviationLoader;
1719
import org.jabref.logic.l10n.Localization;
1820
import org.jabref.logic.util.BackgroundTask;
1921
import org.jabref.logic.util.TaskExecutor;
@@ -36,7 +38,7 @@ public class AbbreviateAction extends SimpleCommand {
3638
private final DialogService dialogService;
3739
private final StateManager stateManager;
3840
private final JournalAbbreviationPreferences journalAbbreviationPreferences;
39-
private final JournalAbbreviationRepository abbreviationRepository;
41+
private JournalAbbreviationRepository abbreviationRepository;
4042
private final TaskExecutor taskExecutor;
4143
private final UndoManager undoManager;
4244

@@ -47,15 +49,13 @@ public AbbreviateAction(StandardActions action,
4749
DialogService dialogService,
4850
StateManager stateManager,
4951
JournalAbbreviationPreferences abbreviationPreferences,
50-
JournalAbbreviationRepository abbreviationRepository,
5152
TaskExecutor taskExecutor,
5253
UndoManager undoManager) {
5354
this.action = action;
5455
this.tabSupplier = tabSupplier;
5556
this.dialogService = dialogService;
5657
this.stateManager = stateManager;
5758
this.journalAbbreviationPreferences = abbreviationPreferences;
58-
this.abbreviationRepository = abbreviationRepository;
5959
this.taskExecutor = taskExecutor;
6060
this.undoManager = undoManager;
6161

@@ -80,6 +80,11 @@ public void execute() {
8080
.onSuccess(dialogService::notify)
8181
.executeWith(taskExecutor));
8282
} else if (action == StandardActions.UNABBREVIATE) {
83+
if (!areAnyJournalSourcesEnabled()) {
84+
dialogService.notify(Localization.lang("Cannot unabbreviate: all journal lists are disabled"));
85+
return;
86+
}
87+
8388
dialogService.notify(Localization.lang("Unabbreviating..."));
8489
stateManager.getActiveDatabase().ifPresent(_ ->
8590
BackgroundTask.wrap(() -> unabbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries()))
@@ -91,6 +96,9 @@ public void execute() {
9196
}
9297

9398
private String abbreviate(BibDatabaseContext databaseContext, List<BibEntry> entries) {
99+
// Reload repository to ensure latest preferences are used
100+
abbreviationRepository = JournalAbbreviationLoader.loadRepository(journalAbbreviationPreferences);
101+
94102
UndoableAbbreviator undoableAbbreviator = new UndoableAbbreviator(
95103
abbreviationRepository,
96104
abbreviationType,
@@ -113,6 +121,9 @@ private String abbreviate(BibDatabaseContext databaseContext, List<BibEntry> ent
113121
}
114122

115123
private String unabbreviate(BibDatabaseContext databaseContext, List<BibEntry> entries) {
124+
// Reload repository to ensure latest preferences are used
125+
abbreviationRepository = JournalAbbreviationLoader.loadRepository(journalAbbreviationPreferences);
126+
116127
UndoableUnabbreviator undoableAbbreviator = new UndoableUnabbreviator(abbreviationRepository);
117128

118129
NamedCompound ce = new NamedCompound(Localization.lang("Unabbreviate journal names"));
@@ -128,4 +139,29 @@ private String unabbreviate(BibDatabaseContext databaseContext, List<BibEntry> e
128139
tabSupplier.get().markBaseChanged();
129140
return Localization.lang("Unabbreviated %0 journal names.", String.valueOf(count));
130141
}
142+
143+
/**
144+
* Checks if any journal abbreviation source is enabled in the preferences.
145+
* This includes both the built-in list and any external journal lists.
146+
*
147+
* @return true if at least one source is enabled, false if all sources are disabled
148+
*/
149+
private boolean areAnyJournalSourcesEnabled() {
150+
boolean anySourceEnabled = journalAbbreviationPreferences.isSourceEnabled(JournalAbbreviationRepository.BUILTIN_LIST_ID);
151+
152+
if (!anySourceEnabled) {
153+
for (String listPath : journalAbbreviationPreferences.getExternalJournalLists()) {
154+
if (listPath != null && !listPath.isBlank()) {
155+
// Just check the filename since that's what's used as the source key
156+
String fileName = Path.of(listPath).getFileName().toString();
157+
if (journalAbbreviationPreferences.isSourceEnabled(fileName)) {
158+
anySourceEnabled = true;
159+
break;
160+
}
161+
}
162+
}
163+
}
164+
165+
return anySourceEnabled;
166+
}
131167
}

src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public UndoableUnabbreviator(JournalAbbreviationRepository journalAbbreviationRe
2121

2222
/**
2323
* Unabbreviate the journal name of the given entry.
24+
* This method respects the enabled/disabled state of journal abbreviation sources.
25+
* If an abbreviation comes from a disabled source, it will not be unabbreviated.
2426
*
2527
* @param entry The entry to be treated.
2628
* @param field The field
@@ -42,15 +44,16 @@ public boolean unabbreviate(BibDatabase database, BibEntry entry, Field field, C
4244
text = database.resolveForStrings(text);
4345
}
4446

45-
if (!journalAbbreviationRepository.isKnownName(text)) {
46-
return false; // Cannot do anything if it is not known.
47+
var abbreviationOpt = journalAbbreviationRepository.get(text);
48+
if (abbreviationOpt.isEmpty()) {
49+
return false;
4750
}
4851

4952
if (!journalAbbreviationRepository.isAbbreviatedName(text)) {
50-
return false; // Cannot unabbreviate unabbreviated name.
53+
return false;
5154
}
5255

53-
Abbreviation abbreviation = journalAbbreviationRepository.get(text).get();
56+
Abbreviation abbreviation = abbreviationOpt.get();
5457
String newText = abbreviation.getName();
5558
entry.setField(field, newText);
5659
ce.addEdit(new UndoableFieldChange(entry, field, origText, newText));
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,176 @@
11
package org.jabref.gui.preferences.journals;
22

3-
import java.io.FileNotFoundException;
43
import java.io.IOException;
54
import java.nio.file.Files;
5+
import java.nio.file.NoSuchFileException;
66
import java.nio.file.Path;
77
import java.util.Collection;
88
import java.util.List;
99
import java.util.Objects;
1010
import java.util.Optional;
1111
import java.util.stream.Collectors;
12+
import java.util.ArrayList;
1213

14+
import javafx.beans.property.BooleanProperty;
1315
import javafx.beans.property.ReadOnlyBooleanProperty;
16+
import javafx.beans.property.ReadOnlyBooleanWrapper;
1417
import javafx.beans.property.SimpleBooleanProperty;
1518
import javafx.beans.property.SimpleListProperty;
19+
import javafx.beans.property.SimpleStringProperty;
1620
import javafx.collections.FXCollections;
21+
import javafx.collections.ObservableList;
1722

1823
import org.jabref.logic.journals.Abbreviation;
1924
import org.jabref.logic.journals.AbbreviationWriter;
2025
import org.jabref.logic.journals.JournalAbbreviationLoader;
26+
import org.jabref.logic.journals.JournalAbbreviationRepository;
27+
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2130

2231
/**
2332
* This class provides a model for abbreviation files. It actually doesn't save the files as objects but rather saves
2433
* their paths. This also allows to specify pseudo files as placeholder objects.
2534
*/
2635
public class AbbreviationsFileViewModel {
36+
private static final Logger LOGGER = LoggerFactory.getLogger(AbbreviationsFileViewModel.class);
2737

28-
private final SimpleListProperty<AbbreviationViewModel> abbreviations = new SimpleListProperty<>(
29-
FXCollections.observableArrayList());
30-
private final ReadOnlyBooleanProperty isBuiltInList;
31-
private final String name;
32-
private final Optional<Path> path;
33-
34-
public AbbreviationsFileViewModel(Path filePath) {
35-
this.path = Optional.ofNullable(filePath);
36-
this.name = path.get().toAbsolutePath().toString();
37-
this.isBuiltInList = new SimpleBooleanProperty(false);
38-
}
38+
private final SimpleStringProperty name = new SimpleStringProperty();
39+
private final ReadOnlyBooleanWrapper isBuiltInList = new ReadOnlyBooleanWrapper();
40+
private final SimpleListProperty<AbbreviationViewModel> abbreviations = new SimpleListProperty<>(FXCollections.observableArrayList());
41+
private final Path filePath;
42+
private final SimpleBooleanProperty enabled = new SimpleBooleanProperty(true);
3943

4044
/**
41-
* This constructor should only be called to create a pseudo abbreviation file for built in lists. This means it is
42-
* a placeholder and its path will be null meaning it has no place on the filesystem. Its isPseudoFile property
43-
* will therefore be set to true.
45+
* This creates a built in list containing the abbreviations from the given list
46+
*
47+
* @param name The name of the built in list
4448
*/
4549
public AbbreviationsFileViewModel(List<AbbreviationViewModel> abbreviations, String name) {
4650
this.abbreviations.addAll(abbreviations);
47-
this.name = name;
48-
this.path = Optional.empty();
49-
this.isBuiltInList = new SimpleBooleanProperty(true);
51+
this.name.setValue(name);
52+
this.isBuiltInList.setValue(true);
53+
this.filePath = null;
5054
}
5155

52-
public void readAbbreviations() throws IOException {
53-
if (path.isPresent()) {
54-
Collection<Abbreviation> abbreviationList = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path.get());
55-
abbreviationList.forEach(abbreviation -> abbreviations.addAll(new AbbreviationViewModel(abbreviation)));
56-
} else {
57-
throw new FileNotFoundException();
58-
}
56+
public AbbreviationsFileViewModel(Path filePath) {
57+
this.name.setValue(filePath.getFileName().toString());
58+
this.filePath = filePath;
59+
this.isBuiltInList.setValue(false);
5960
}
6061

61-
/**
62-
* This method will write all abbreviations of this abbreviation file to the file on the file system.
63-
* It essentially will check if the current file is a builtin list and if not it will call
64-
* {@link AbbreviationWriter#writeOrCreate}.
65-
*/
66-
public void writeOrCreate() throws IOException {
67-
if (!isBuiltInList.get()) {
68-
List<Abbreviation> actualAbbreviations =
69-
abbreviations.stream().filter(abb -> !abb.isPseudoAbbreviation())
70-
.map(AbbreviationViewModel::getAbbreviationObject)
71-
.collect(Collectors.toList());
72-
AbbreviationWriter.writeOrCreate(path.get(), actualAbbreviations);
73-
}
62+
public boolean exists() {
63+
return isBuiltInList.get() || Files.exists(filePath);
64+
}
65+
66+
public SimpleStringProperty nameProperty() {
67+
return name;
68+
}
69+
70+
public ReadOnlyBooleanProperty isBuiltInListProperty() {
71+
return isBuiltInList.getReadOnlyProperty();
7472
}
7573

7674
public SimpleListProperty<AbbreviationViewModel> abbreviationsProperty() {
7775
return abbreviations;
7876
}
7977

80-
public boolean exists() {
81-
return path.isPresent() && Files.exists(path.get());
78+
public void readAbbreviations() throws IOException {
79+
if (isBuiltInList.get()) {
80+
return;
81+
}
82+
try {
83+
Collection<Abbreviation> abbreviationsFromFile = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(filePath);
84+
85+
List<AbbreviationViewModel> viewModels = abbreviationsFromFile.stream()
86+
.map(AbbreviationViewModel::new)
87+
.collect(Collectors.toCollection(ArrayList::new));
88+
abbreviations.setAll(viewModels);
89+
} catch (NoSuchFileException e) {
90+
LOGGER.debug("Journal abbreviation list {} does not exist", filePath);
91+
}
8292
}
8393

84-
public Optional<Path> getAbsolutePath() {
85-
return path;
94+
public void writeOrCreate() throws IOException {
95+
if (isBuiltInList.get()) {
96+
return;
97+
}
98+
99+
List<Abbreviation> abbreviationList = abbreviationsProperty().stream()
100+
.map(AbbreviationViewModel::getAbbreviationObject)
101+
.collect(Collectors.toList());
102+
AbbreviationWriter.writeOrCreate(filePath, abbreviationList);
86103
}
87104

88-
public ReadOnlyBooleanProperty isBuiltInListProperty() {
89-
return isBuiltInList;
105+
/**
106+
* Gets the absolute path of this abbreviation file
107+
*
108+
* @return The optional absolute path of the file, or empty if it's a built-in list
109+
*/
110+
public Optional<Path> getAbsolutePath() {
111+
if (isBuiltInList.get()) {
112+
return Optional.empty();
113+
}
114+
115+
try {
116+
Path normalizedPath = filePath.toAbsolutePath().normalize();
117+
return Optional.of(normalizedPath);
118+
} catch (Exception e) {
119+
return Optional.of(filePath);
120+
}
121+
}
122+
123+
/**
124+
* Checks if this source is enabled
125+
*
126+
* @return true if the source is enabled
127+
*/
128+
public boolean isEnabled() {
129+
return enabled.get();
130+
}
131+
132+
/**
133+
* Sets the enabled state of this source
134+
*
135+
* @param enabled true to enable the source, false to disable it
136+
*/
137+
public void setEnabled(boolean enabled) {
138+
this.enabled.set(enabled);
139+
}
140+
141+
/**
142+
* Gets the enabled property for binding
143+
*
144+
* @return the enabled property
145+
*/
146+
public BooleanProperty enabledProperty() {
147+
return enabled;
90148
}
91149

92150
@Override
93-
public String toString() {
94-
return name;
151+
public boolean equals(Object o) {
152+
if (this == o) {
153+
return true;
154+
}
155+
if ((o == null) || (getClass() != o.getClass())) {
156+
return false;
157+
}
158+
AbbreviationsFileViewModel viewModel = (AbbreviationsFileViewModel) o;
159+
if (isBuiltInList.get() && viewModel.isBuiltInList.get()) {
160+
return name.get().equals(viewModel.name.get());
161+
}
162+
return !isBuiltInList.get() && !viewModel.isBuiltInList.get() &&
163+
Objects.equals(filePath, viewModel.filePath);
95164
}
96165

97166
@Override
98167
public int hashCode() {
99-
return Objects.hash(name);
168+
return Objects.hash(isBuiltInList, filePath);
100169
}
101170

102171
@Override
103-
public boolean equals(Object obj) {
104-
if (obj instanceof AbbreviationsFileViewModel model) {
105-
return Objects.equals(this.name, model.name);
106-
} else {
107-
return false;
108-
}
172+
public String toString() {
173+
return name.get();
109174
}
110175
}
176+

0 commit comments

Comments
 (0)