Skip to content

Commit d80e164

Browse files
authored
JournalAbbreviation search feature (#7804)
1 parent 0c9217e commit d80e164

File tree

7 files changed

+169
-9
lines changed

7 files changed

+169
-9
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
2626
- We added a feature that allows the user to open all linked files of multiple selected entries by "Open file" option. [#6966](https://github.com/JabRef/jabref/issues/6966)
2727
- We added a keybinding preset for new entries. [#7705](https://github.com/JabRef/jabref/issues/7705)
2828
- We added a select all button for the library import function. [#7786](https://github.com/JabRef/jabref/issues/7786)
29-
- We added auto-key-generation progress to the background task list. [#7267](https://github.com/JabRef/jabref/issues/7267)
29+
- We added a search feature for journal abbreviations. [#7804](https://github.com/JabRef/jabref/pull/7804)
30+
- We added auto-key-generation progress to the background task list. [#7267](https://github.com/JabRef/jabref/issues/72)
3031

3132
### Changed
3233

src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jabref.gui.preferences.journals;
22

3+
import java.util.Locale;
34
import java.util.Objects;
45

56
import javafx.beans.property.BooleanProperty;
@@ -94,4 +95,11 @@ public boolean equals(Object o) {
9495
public int hashCode() {
9596
return Objects.hash(getName(), isPseudoAbbreviation());
9697
}
98+
99+
public boolean containsCaseIndependent(String searchTerm) {
100+
searchTerm = searchTerm.toLowerCase(Locale.ROOT);
101+
return this.abbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm) ||
102+
this.name.get().toLowerCase(Locale.ROOT).contains(searchTerm) ||
103+
this.shortestUniqueAbbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm);
104+
}
97105
}

src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.fxml

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<?import javafx.scene.layout.HBox?>
1111
<?import javafx.scene.layout.Pane?>
1212
<?import javafx.scene.layout.VBox?>
13+
<?import org.controlsfx.control.textfield.CustomTextField?>
14+
<?import javafx.geometry.Insets?>
1315
<fx:root spacing="10.0" type="VBox"
1416
xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
1517
fx:controller="org.jabref.gui.preferences.journals.JournalAbbreviationsTab">
@@ -64,4 +66,9 @@
6466
<ProgressIndicator fx:id="progressIndicator" maxHeight="30.0" opacity="0.4"/>
6567
</placeholder>
6668
</TableView>
69+
<CustomTextField fx:id="searchBox" promptText="%Search" VBox.vgrow="NEVER">
70+
<VBox.margin>
71+
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0"/>
72+
</VBox.margin>
73+
</CustomTextField>
6774
</fx:root>

src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java

+69-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
import javax.inject.Inject;
44

5+
import javafx.animation.Interpolator;
6+
import javafx.animation.KeyFrame;
7+
import javafx.animation.KeyValue;
8+
import javafx.animation.Timeline;
9+
import javafx.beans.property.ObjectProperty;
10+
import javafx.beans.property.SimpleObjectProperty;
11+
import javafx.beans.property.SimpleStringProperty;
12+
import javafx.beans.property.StringProperty;
13+
import javafx.collections.transformation.FilteredList;
14+
import javafx.event.ActionEvent;
515
import javafx.fxml.FXML;
616
import javafx.scene.control.Button;
717
import javafx.scene.control.ComboBox;
@@ -10,16 +20,20 @@
1020
import javafx.scene.control.TableColumn;
1121
import javafx.scene.control.TableView;
1222
import javafx.scene.control.cell.TextFieldTableCell;
23+
import javafx.scene.paint.Color;
24+
import javafx.util.Duration;
1325

1426
import org.jabref.gui.icon.IconTheme;
1527
import org.jabref.gui.preferences.AbstractPreferenceTabView;
1628
import org.jabref.gui.preferences.PreferencesTab;
29+
import org.jabref.gui.util.ColorUtil;
1730
import org.jabref.gui.util.TaskExecutor;
1831
import org.jabref.logic.journals.JournalAbbreviationRepository;
1932
import org.jabref.logic.l10n.Localization;
2033

2134
import com.airhacks.afterburner.views.ViewLoader;
2235
import com.tobiasdiez.easybind.EasyBind;
36+
import org.controlsfx.control.textfield.CustomTextField;
2337

2438
/**
2539
* This class controls the user interface of the journal abbreviations dialog. The UI elements and their layout are
@@ -33,16 +47,23 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView<JournalAb
3347
@FXML private TableColumn<AbbreviationViewModel, String> journalTableNameColumn;
3448
@FXML private TableColumn<AbbreviationViewModel, String> journalTableAbbreviationColumn;
3549
@FXML private TableColumn<AbbreviationViewModel, String> journalTableShortestUniqueAbbreviationColumn;
50+
private FilteredList<AbbreviationViewModel> filteredAbbreviations;
3651
@FXML private ComboBox<AbbreviationsFileViewModel> journalFilesBox;
3752
@FXML private Button addAbbreviationButton;
3853
@FXML private Button removeAbbreviationButton;
3954
@FXML private Button openAbbreviationListButton;
4055
@FXML private Button addAbbreviationListButton;
4156
@FXML private Button removeAbbreviationListButton;
4257

58+
@FXML private CustomTextField searchBox;
59+
4360
@Inject private TaskExecutor taskExecutor;
4461
@Inject private JournalAbbreviationRepository abbreviationRepository;
4562

63+
private Timeline invalidateSearch;
64+
private ObjectProperty<Color> flashingColor;
65+
private StringProperty flashingColorStringProperty;
66+
4667
public JournalAbbreviationsTab() {
4768
ViewLoader.view(this)
4869
.root(this)
@@ -53,9 +74,15 @@ public JournalAbbreviationsTab() {
5374
private void initialize() {
5475
viewModel = new JournalAbbreviationsTabViewModel(preferencesService, dialogService, taskExecutor, abbreviationRepository);
5576

77+
filteredAbbreviations = new FilteredList<>(viewModel.abbreviationsProperty());
78+
5679
setButtonStyles();
5780
setUpTable();
5881
setBindings();
82+
setAnimations();
83+
84+
searchBox.setPromptText(Localization.lang("Search") + "...");
85+
searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode());
5986
}
6087

6188
private void setButtonStyles() {
@@ -78,7 +105,7 @@ private void setUpTable() {
78105
}
79106

80107
private void setBindings() {
81-
journalAbbreviationsTable.itemsProperty().bindBidirectional(viewModel.abbreviationsProperty());
108+
journalAbbreviationsTable.setItems(filteredAbbreviations);
82109

83110
EasyBind.subscribe(journalAbbreviationsTable.getSelectionModel().selectedItemProperty(), newValue ->
84111
viewModel.currentAbbreviationProperty().set(newValue));
@@ -98,6 +125,27 @@ private void setBindings() {
98125

99126
loadingLabel.visibleProperty().bind(viewModel.isLoadingProperty());
100127
progressIndicator.visibleProperty().bind(viewModel.isLoadingProperty());
128+
129+
searchBox.textProperty().addListener((observable, previousText, searchTerm) -> {
130+
filteredAbbreviations.setPredicate(abbreviation -> searchTerm.isEmpty() ? true : abbreviation.containsCaseIndependent(searchTerm));
131+
});
132+
}
133+
134+
private void setAnimations() {
135+
flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT);
136+
flashingColorStringProperty = createFlashingColorStringProperty(flashingColor);
137+
searchBox.styleProperty().bind(
138+
new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty).concat(";")
139+
);
140+
invalidateSearch = new Timeline(
141+
new KeyFrame(Duration.seconds(0), new KeyValue(flashingColor, Color.TRANSPARENT, Interpolator.LINEAR)),
142+
new KeyFrame(Duration.seconds(0.25), new KeyValue(flashingColor, Color.RED, Interpolator.LINEAR)),
143+
new KeyFrame(Duration.seconds(0.25), new KeyValue(searchBox.textProperty(), "", Interpolator.DISCRETE)),
144+
new KeyFrame(Duration.seconds(0.25), (ActionEvent event) -> {
145+
addAbbreviationActions();
146+
}),
147+
new KeyFrame(Duration.seconds(0.5), new KeyValue(flashingColor, Color.TRANSPARENT, Interpolator.LINEAR))
148+
);
101149
}
102150

103151
@FXML
@@ -117,11 +165,30 @@ private void removeList() {
117165

118166
@FXML
119167
private void addAbbreviation() {
168+
if (!searchBox.getText().isEmpty()) {
169+
invalidateSearch.play();
170+
} else {
171+
addAbbreviationActions();
172+
}
173+
}
174+
175+
private void addAbbreviationActions() {
120176
viewModel.addAbbreviation();
121177
selectNewAbbreviation();
122178
editAbbreviation();
123179
}
124180

181+
private static StringProperty createFlashingColorStringProperty(final ObjectProperty<Color> flashingColor) {
182+
final StringProperty flashingColorStringProperty = new SimpleStringProperty();
183+
setColorStringFromColor(flashingColorStringProperty, flashingColor);
184+
flashingColor.addListener((observable, oldValue, newValue) -> setColorStringFromColor(flashingColorStringProperty, flashingColor));
185+
return flashingColorStringProperty;
186+
}
187+
188+
private static void setColorStringFromColor(StringProperty colorStringProperty, ObjectProperty<Color> color) {
189+
colorStringProperty.set(ColorUtil.toRGBACode(color.get()));
190+
}
191+
125192
@FXML
126193
private void editAbbreviation() {
127194
journalAbbreviationsTable.edit(
@@ -138,7 +205,7 @@ private void selectNewAbbreviation() {
138205
int lastRow = viewModel.abbreviationsCountProperty().get() - 1;
139206
journalAbbreviationsTable.scrollTo(lastRow);
140207
journalAbbreviationsTable.getSelectionModel().select(lastRow);
141-
journalAbbreviationsTable.getFocusModel().focus(lastRow);
208+
journalAbbreviationsTable.getFocusModel().focus(lastRow, journalTableNameColumn);
142209
}
143210

144211
@Override

src/main/java/org/jabref/gui/util/ColorUtil.java

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ public static String toRGBCode(Color color) {
1111
(int) (color.getBlue() * 255));
1212
}
1313

14+
public static String toRGBACode(Color color) {
15+
return String.format("rgba(%d,%d,%d,%f)",
16+
(int) (color.getRed() * 255),
17+
(int) (color.getGreen() * 255),
18+
(int) (color.getBlue() * 255),
19+
color.getOpacity());
20+
}
21+
1422
public static String toHex(Color validFieldBackgroundColor) {
1523
return String.format("#%02x%02x%02x", (int) validFieldBackgroundColor.getRed(), (int) validFieldBackgroundColor.getGreen(), (int) validFieldBackgroundColor.getBlue());
1624
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.jabref.gui.preferences.journals;
2+
3+
import java.util.stream.Stream;
4+
5+
import org.jabref.logic.journals.Abbreviation;
6+
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
10+
11+
import static org.junit.jupiter.api.Assertions.assertFalse;
12+
import static org.junit.jupiter.api.Assertions.assertTrue;
13+
14+
class AbbreviationViewModelTest {
15+
16+
@ParameterizedTest
17+
@MethodSource("provideContainsCaseIndependentContains")
18+
void containsCaseIndependentContains(String searchTerm, AbbreviationViewModel abbreviation) {
19+
assertTrue(abbreviation.containsCaseIndependent(searchTerm));
20+
}
21+
22+
private static Stream<Arguments> provideContainsCaseIndependentContains() {
23+
return Stream.of(
24+
Arguments.of("name", new AbbreviationViewModel(new Abbreviation("Long Name", "abbr", "unique"))),
25+
Arguments.of("bBr", new AbbreviationViewModel(new Abbreviation("Long Name", "abbr", "unique"))),
26+
Arguments.of("Uniq", new AbbreviationViewModel(new Abbreviation("Long Name", "abbr", "unique"))),
27+
Arguments.of("", new AbbreviationViewModel(new Abbreviation("Long Name", "abbr", "unique"))),
28+
Arguments.of("", new AbbreviationViewModel(new Abbreviation("", "", "")))
29+
);
30+
}
31+
32+
@ParameterizedTest
33+
@MethodSource("provideContainsCaseIndependentDoesNotContain")
34+
void containsCaseIndependentDoesNotContain(String searchTerm, AbbreviationViewModel abbreviation) {
35+
assertFalse(abbreviation.containsCaseIndependent(searchTerm));
36+
}
37+
38+
private static Stream<Arguments> provideContainsCaseIndependentDoesNotContain() {
39+
return Stream.of(
40+
Arguments.of("Something else", new AbbreviationViewModel(new Abbreviation("Long Name", "abbr", "unique"))),
41+
Arguments.of("Something", new AbbreviationViewModel(new Abbreviation("", "", "")))
42+
);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,51 @@
11
package org.jabref.gui.util;
22

3+
import java.util.stream.Stream;
4+
35
import javafx.scene.paint.Color;
46

57
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
611

712
import static org.junit.jupiter.api.Assertions.assertEquals;
813

914
public class ColorUtilTest {
1015

16+
private static final Color C1 = Color.color(0.2, 0.4, 1);
17+
private static final Color C2 = Color.rgb(255, 255, 255);
18+
private static final Color C3 = Color.color(0, 0, 0, 0);
19+
private static final Color C4 = Color.color(1, 1, 1, 1);
20+
private static final Color C5 = Color.color(0.6, 0.8, 0.5, 0.3);
21+
1122
private ColorUtil colorUtil = new ColorUtil();
12-
private final Color c1 = Color.color(0.2, 0.4, 1);
13-
private final Color c2 = Color.rgb(255, 255, 255);
1423

1524
@Test
1625
public void toRGBCodeTest() {
17-
assertEquals("#3366FF", ColorUtil.toRGBCode(c1));
18-
assertEquals("#FFFFFF", ColorUtil.toRGBCode(c2));
26+
assertEquals("#3366FF", ColorUtil.toRGBCode(C1));
27+
assertEquals("#FFFFFF", ColorUtil.toRGBCode(C2));
28+
}
29+
30+
@ParameterizedTest
31+
@MethodSource("provideToRGBACodeTest")
32+
public void toRGBACodeTest(Color color, String expected) {
33+
assertEquals(expected, ColorUtil.toRGBACode(color));
34+
}
35+
36+
private static Stream<Arguments> provideToRGBACodeTest() {
37+
return Stream.of(
38+
Arguments.of(C1, String.format("rgba(51,102,255,%f)", 1.0)),
39+
Arguments.of(C2, String.format("rgba(255,255,255,%f)", 1.0)),
40+
Arguments.of(C3, String.format("rgba(0,0,0,%f)", 0.0)),
41+
Arguments.of(C4, String.format("rgba(255,255,255,%f)", 1.0)),
42+
Arguments.of(C5, String.format("rgba(153,204,127,%f)", 0.3))
43+
);
1944
}
2045

2146
@Test
2247
public void toHexTest() {
23-
assertEquals("#000001", ColorUtil.toHex(c1));
24-
assertEquals("#010101", ColorUtil.toHex(c2));
48+
assertEquals("#000001", ColorUtil.toHex(C1));
49+
assertEquals("#010101", ColorUtil.toHex(C2));
2550
}
2651
}

0 commit comments

Comments
 (0)