Skip to content

Commit e0f3bea

Browse files
authored
Automatic Grouping By Entry Type (#15081)
* Added RadioButton for setting "Entry type" in GroupDialog * Implemented logic for grouping by entry type * Added Tests for AutomaticEntryTypeGroup * Updated CHANGELOG.md * Fixed missing blank line in CHANGELOG.md * Fixed group persistence, entry type label and EntryTypeGroup switch coverage
1 parent 5e4f7cf commit e0f3bea

File tree

14 files changed

+222
-2
lines changed

14 files changed

+222
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
3030
- We added the option to enable/disable the HTTP-Server for the browser extension to the Quick Settings on the Welcome screen [#14902](https://github.com/JabRef/jabref/issues/14902)
3131
- We added the ability to update bibliographic information based on the existing entry data. [#14185](https://github.com/JabRef/jabref/issues/14185)
3232
- We added an option to clear [groups with explicitly selected entries](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups#explicit-selection). [#15001](https://github.com/JabRef/jabref/issues/15001)
33+
- We added the option to group entries by entry type [#15040](https://github.com/JabRef/jabref/issues/15040)
3334

3435
### Changed
3536

jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public class GroupDialogView extends BaseDialog<AbstractGroup> {
8484
@FXML private RadioButton searchRadioButton;
8585
@FXML private RadioButton autoRadioButton;
8686
@FXML private RadioButton texRadioButton;
87+
@FXML private RadioButton entryTypeRadioButton;
8788

8889
// Option Groups
8990
@FXML private TextField keywordGroupSearchTerm;
@@ -209,6 +210,7 @@ public void initialize() {
209210
searchRadioButton.selectedProperty().bindBidirectional(viewModel.typeSearchProperty());
210211
autoRadioButton.selectedProperty().bindBidirectional(viewModel.typeAutoProperty());
211212
texRadioButton.selectedProperty().bindBidirectional(viewModel.typeTexProperty());
213+
entryTypeRadioButton.selectedProperty().bindBidirectional(viewModel.typeEntryTypeProperty());
212214

213215
keywordGroupSearchTerm.textProperty().bindBidirectional(viewModel.keywordGroupSearchTermProperty());
214216
keywordGroupSearchField.textProperty().bindBidirectional(viewModel.keywordGroupSearchFieldProperty());

jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.jabref.model.entry.field.StandardField;
4545
import org.jabref.model.groups.AbstractGroup;
4646
import org.jabref.model.groups.AutomaticDateGroup;
47+
import org.jabref.model.groups.AutomaticEntryTypeGroup;
4748
import org.jabref.model.groups.AutomaticGroup;
4849
import org.jabref.model.groups.AutomaticKeywordGroup;
4950
import org.jabref.model.groups.AutomaticPersonsGroup;
@@ -83,6 +84,7 @@ public class GroupDialogViewModel {
8384
private final BooleanProperty typeSearchProperty = new SimpleBooleanProperty();
8485
private final BooleanProperty typeAutoProperty = new SimpleBooleanProperty();
8586
private final BooleanProperty typeTexProperty = new SimpleBooleanProperty();
87+
private final BooleanProperty typeEntryTypeProperty = new SimpleBooleanProperty();
8688

8789
// Option Groups
8890
private final StringProperty keywordGroupSearchTermProperty = new SimpleStringProperty("");
@@ -384,6 +386,8 @@ public AbstractGroup resultConverter(ButtonType button) {
384386
currentDatabase.getMetaData(),
385387
preferences.getFilePreferences().getUserAndHost()
386388
);
389+
} else if (Boolean.TRUE.equals((typeEntryTypeProperty.getValue()))) {
390+
resultingGroup = new AutomaticEntryTypeGroup(groupName, groupHierarchySelectedProperty.getValue());
387391
} else if (Boolean.TRUE.equals(dateRadioButtonSelectedProperty.getValue())) {
388392
resultingGroup = new AutomaticDateGroup(
389393
groupName,
@@ -484,6 +488,8 @@ public void setValues() {
484488
dateGroupFieldProperty.setValue(group.getField());
485489
dateGroupOptionProperty.setValue(group.getGranularity());
486490
dateGroupIncludeEmptyProperty.setValue(false);
491+
} else if (editedGroup.getClass() == AutomaticEntryTypeGroup.class) {
492+
typeEntryTypeProperty.setValue(Boolean.TRUE);
487493
}
488494
} else if (editedGroup.getClass() == TexGroup.class) {
489495
typeTexProperty.setValue(true);
@@ -617,6 +623,10 @@ public BooleanProperty typeTexProperty() {
617623
return typeTexProperty;
618624
}
619625

626+
public BooleanProperty typeEntryTypeProperty() {
627+
return typeEntryTypeProperty;
628+
}
629+
620630
public StringProperty keywordGroupSearchTermProperty() {
621631
return keywordGroupSearchTermProperty;
622632
}

jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@
3737
import org.jabref.model.groups.AbstractGroup;
3838
import org.jabref.model.groups.AllEntriesGroup;
3939
import org.jabref.model.groups.AutomaticDateGroup;
40+
import org.jabref.model.groups.AutomaticEntryTypeGroup;
4041
import org.jabref.model.groups.AutomaticGroup;
4142
import org.jabref.model.groups.AutomaticKeywordGroup;
4243
import org.jabref.model.groups.AutomaticPersonsGroup;
4344
import org.jabref.model.groups.DateGroup;
45+
import org.jabref.model.groups.EntryTypeGroup;
4446
import org.jabref.model.groups.ExplicitGroup;
4547
import org.jabref.model.groups.GroupEntryChanger;
4648
import org.jabref.model.groups.GroupTreeNode;
@@ -462,6 +464,10 @@ public boolean canAddEntriesIn() {
462464
return false;
463465
} else if (group instanceof DateGroup) {
464466
return false;
467+
} else if (group instanceof AutomaticEntryTypeGroup) {
468+
return false;
469+
} else if (group instanceof EntryTypeGroup) {
470+
return false;
465471
} else {
466472
throw new UnsupportedOperationException("canAddEntriesIn method not yet implemented in group: " + group.getClass().getName());
467473
}
@@ -477,6 +483,8 @@ public boolean canBeDragged() {
477483
AutomaticKeywordGroup _,
478484
AutomaticPersonsGroup _,
479485
AutomaticDateGroup _,
486+
AutomaticEntryTypeGroup _,
487+
EntryTypeGroup _,
480488
TexGroup _ ->
481489
true;
482490
case KeywordGroup _ ->
@@ -505,7 +513,9 @@ public boolean canAddGroupsIn() {
505513
case AutomaticKeywordGroup _,
506514
AutomaticPersonsGroup _,
507515
AutomaticDateGroup _,
508-
DateGroup _ ->
516+
DateGroup _,
517+
AutomaticEntryTypeGroup _,
518+
EntryTypeGroup _ ->
509519
false;
510520
case KeywordGroup _ ->
511521
// KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup
@@ -530,7 +540,9 @@ public boolean canRemove() {
530540
AutomaticKeywordGroup _,
531541
AutomaticPersonsGroup _,
532542
AutomaticDateGroup _,
543+
AutomaticEntryTypeGroup _,
533544
DateGroup _,
545+
EntryTypeGroup _,
534546
TexGroup _ ->
535547
true;
536548
case KeywordGroup _ ->
@@ -550,13 +562,15 @@ public boolean isEditable() {
550562
AbstractGroup group = groupNode.getGroup();
551563
return switch (group) {
552564
case AllEntriesGroup _,
553-
DateGroup _ ->
565+
DateGroup _,
566+
EntryTypeGroup _ ->
554567
false;
555568
case ExplicitGroup _,
556569
SearchGroup _,
557570
AutomaticKeywordGroup _,
558571
AutomaticPersonsGroup _,
559572
AutomaticDateGroup _,
573+
AutomaticEntryTypeGroup _,
560574
TexGroup _ ->
561575
true;
562576
case KeywordGroup _ ->

jabgui/src/main/resources/org/jabref/gui/groups/GroupDialog.fxml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
<Tooltip text="%Group containing entries cited in a given TeX file"/>
100100
</tooltip>
101101
</RadioButton>
102+
<RadioButton fx:id="entryTypeRadioButton" toggleGroup="$type" wrapText="true"
103+
text="%Entry type">
104+
<tooltip>
105+
<Tooltip text="%Automatically create groups by entry type"/>
106+
</tooltip>
107+
</RadioButton>
102108
<RadioButton fx:id="dateRadioButton" toggleGroup="$type" wrapText="true"
103109
text="%Date">
104110
<tooltip>

jablib/src/main/java/org/jabref/logic/exporter/GroupSerializer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.jabref.model.groups.AbstractGroup;
1010
import org.jabref.model.groups.AllEntriesGroup;
1111
import org.jabref.model.groups.AutomaticDateGroup;
12+
import org.jabref.model.groups.AutomaticEntryTypeGroup;
1213
import org.jabref.model.groups.AutomaticGroup;
1314
import org.jabref.model.groups.AutomaticKeywordGroup;
1415
import org.jabref.model.groups.AutomaticPersonsGroup;
@@ -126,6 +127,8 @@ private String serializeGroup(AbstractGroup group) {
126127
serializeAutomaticPersonsGroup(personsGroup);
127128
case AutomaticDateGroup dateGroup ->
128129
serializeAutomaticDateGroup(dateGroup);
130+
case AutomaticEntryTypeGroup entryTypeGroup ->
131+
serializeAutomaticEntryTypeGroup(entryTypeGroup);
129132
case TexGroup texGroup ->
130133
serializeTexGroup(texGroup);
131134
case null ->
@@ -192,4 +195,12 @@ private String serializeAutomaticKeywordGroup(AutomaticKeywordGroup group) {
192195
appendGroupDetails(sb, group);
193196
return sb.toString();
194197
}
198+
199+
private String serializeAutomaticEntryTypeGroup(AutomaticEntryTypeGroup group) {
200+
StringBuilder sb = new StringBuilder();
201+
sb.append(MetadataSerializationConfiguration.AUTOMATIC_ENTRY_TYPE_GROUP_ID);
202+
appendAutomaticGroupDetails(sb, group);
203+
appendGroupDetails(sb, group);
204+
return sb.toString();
205+
}
195206
}

jablib/src/main/java/org/jabref/logic/importer/util/GroupsParser.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.jabref.model.entry.field.FieldFactory;
1919
import org.jabref.model.groups.AbstractGroup;
2020
import org.jabref.model.groups.AutomaticDateGroup;
21+
import org.jabref.model.groups.AutomaticEntryTypeGroup;
2122
import org.jabref.model.groups.AutomaticKeywordGroup;
2223
import org.jabref.model.groups.AutomaticPersonsGroup;
2324
import org.jabref.model.groups.DateGranularity;
@@ -126,6 +127,9 @@ public static AbstractGroup fromString(String input, Character keywordSeparator,
126127
if (input.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID)) {
127128
return automaticKeywordGroupFromString(input);
128129
}
130+
if (input.startsWith(MetadataSerializationConfiguration.AUTOMATIC_ENTRY_TYPE_GROUP_ID)) {
131+
return automaticEntryTypeGroupFromString(input);
132+
}
129133
if (input.startsWith(MetadataSerializationConfiguration.AUTOMATIC_DATE_GROUP_ID)) {
130134
return automaticDateGroupFromString(input);
131135
}
@@ -193,6 +197,19 @@ private static AbstractGroup automaticDateGroupFromString(String input) {
193197
return newGroup;
194198
}
195199

200+
private static AbstractGroup automaticEntryTypeGroupFromString(String input) {
201+
assert input.startsWith(MetadataSerializationConfiguration.AUTOMATIC_ENTRY_TYPE_GROUP_ID);
202+
203+
QuotedStringTokenizer token = new QuotedStringTokenizer(input.substring(MetadataSerializationConfiguration.AUTOMATIC_ENTRY_TYPE_GROUP_ID
204+
.length()), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
205+
206+
String name = StringUtil.unquote(token.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
207+
GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(token.nextToken()));
208+
AutomaticEntryTypeGroup newGroup = new AutomaticEntryTypeGroup(name, context);
209+
addGroupDetails(token, newGroup);
210+
return newGroup;
211+
}
212+
196213
private static AbstractGroup automaticKeywordGroupFromString(String input) {
197214
assert input.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID);
198215

jablib/src/main/java/org/jabref/logic/util/MetadataSerializationConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public class MetadataSerializationConfiguration {
3232
/// Identifier for {@link org.jabref.model.groups.AutomaticKeywordGroup}.
3333
public static final String AUTOMATIC_KEYWORD_GROUP_ID = "AutomaticKeywordGroup:";
3434

35+
/// Identifier for {@link org.jabref.model.groups.AutomaticEntryTypeGroup}.
36+
public static final String AUTOMATIC_ENTRY_TYPE_GROUP_ID = "AutomaticEntryTypeGroup:";
37+
3538
/// Identifier for {@link org.jabref.model.groups.TexGroup}.
3639
public static final String TEX_GROUP_ID = "TexGroup:";
3740

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.jabref.model.groups;
2+
3+
import java.util.Set;
4+
5+
import org.jabref.model.entry.BibEntry;
6+
import org.jabref.model.entry.types.EntryType;
7+
8+
public class AutomaticEntryTypeGroup extends AutomaticGroup {
9+
10+
public AutomaticEntryTypeGroup(String name, GroupHierarchyType context) {
11+
super(name, context);
12+
}
13+
14+
@Override
15+
public Set<GroupTreeNode> createSubgroups(BibEntry entry) {
16+
EntryType type = entry.getType();
17+
return Set.of(new GroupTreeNode(new EntryTypeGroup(type.getName(),
18+
GroupHierarchyType.INDEPENDENT, type)));
19+
}
20+
21+
@Override
22+
public AbstractGroup deepCopy() {
23+
return new AutomaticEntryTypeGroup(getName(), getHierarchicalContext());
24+
}
25+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.jabref.model.groups;
2+
3+
import java.util.Objects;
4+
5+
import org.jabref.model.entry.BibEntry;
6+
import org.jabref.model.entry.types.EntryType;
7+
8+
public class EntryTypeGroup extends AbstractGroup {
9+
10+
private final EntryType entryType;
11+
12+
public EntryTypeGroup(String groupName, GroupHierarchyType context, EntryType entryType) {
13+
super(groupName, context);
14+
this.entryType = entryType;
15+
}
16+
17+
public EntryType getEntryType() {
18+
return entryType;
19+
}
20+
21+
@Override
22+
public boolean equals(Object o) {
23+
if (this == o) {
24+
return true;
25+
}
26+
if (o == null || getClass() != o.getClass()) {
27+
return false;
28+
}
29+
if (!super.equals(o)) {
30+
return false;
31+
}
32+
EntryTypeGroup that = (EntryTypeGroup) o;
33+
return Objects.equals(getEntryType(), that.getEntryType());
34+
}
35+
36+
@Override
37+
public int hashCode() {
38+
return Objects.hash(super.hashCode(), getEntryType());
39+
}
40+
41+
@Override
42+
public boolean contains(BibEntry entry) {
43+
return getEntryType().equals(entry.getType());
44+
}
45+
46+
@Override
47+
public boolean isDynamic() {
48+
return true;
49+
}
50+
51+
@Override
52+
public AbstractGroup deepCopy() {
53+
return new EntryTypeGroup(getName(), getHierarchicalContext(), getEntryType());
54+
}
55+
}

0 commit comments

Comments
 (0)