package org.jabref.gui.groups;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import javax.swing.SwingUtilities;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jabref.gui.AbstractViewModel;
import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.metadata.MetaData;
public class GroupTreeViewModel extends AbstractViewModel {
private final ObjectProperty<GroupNodeViewModel> rootGroup = new SimpleObjectProperty<>();
private final ObjectProperty<GroupNodeViewModel> selectedGroup = new SimpleObjectProperty<>();
private final StateManager stateManager;
private final DialogService dialogService;
private final TaskExecutor taskExecutor;
private final ObjectProperty<Predicate<GroupNodeViewModel>> filterPredicate = new SimpleObjectProperty<>();
private final StringProperty filterText = new SimpleStringProperty();
private Optional<BibDatabaseContext> currentDatabase;
private final Comparator<GroupTreeNode> compAlphabetIgnoreCase = (GroupTreeNode v1, GroupTreeNode v2) -> v1
.getName()
.compareToIgnoreCase(v2.getName());
public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, TaskExecutor taskExecutor) {
this.stateManager = Objects.requireNonNull(stateManager);
this.dialogService = Objects.requireNonNull(dialogService);
this.taskExecutor = Objects.requireNonNull(taskExecutor);
// Register listener
stateManager.activeDatabaseProperty()
.addListener((observable, oldValue, newValue) -> onActiveDatabaseChanged(newValue));
selectedGroup.addListener((observable, oldValue, newValue) -> onSelectedGroupChanged(newValue));
// Set-up bindings
filterPredicate
.bind(Bindings.createObjectBinding(() -> group -> group.isMatchedBy(filterText.get()), filterText));
// Init
onActiveDatabaseChanged(stateManager.activeDatabaseProperty().getValue());
}
public ObjectProperty<GroupNodeViewModel> rootGroupProperty() {
return rootGroup;
}
public ObjectProperty<GroupNodeViewModel> selectedGroupProperty() {
return selectedGroup;
}
public ObjectProperty<Predicate<GroupNodeViewModel>> filterPredicateProperty() {
return filterPredicate;
}
public StringProperty filterTextProperty() {
return filterText;
}
/**
* Gets invoked if the user selects a different group.
* We need to notify the {@link StateManager} about this change so that the main table gets updated.
*/
private void onSelectedGroupChanged(GroupNodeViewModel newValue) {
if (!currentDatabase.equals(stateManager.activeDatabaseProperty().getValue())) {
// Switch of database occurred -> do nothing
return;
}
currentDatabase.ifPresent(database -> {
if (newValue == null) {
stateManager.clearSelectedGroup(database);
} else {
stateManager.setSelectedGroup(database, newValue.getGroupNode());
}
});
}
/**
* Opens "New Group Dialog" and add the resulting group to the root
*/
public void addNewGroupToRoot() {
addNewSubgroup(rootGroup.get());
}
/**
* Gets invoked if the user changes the active database.
* We need to get the new group tree and update the view
*/
private void onActiveDatabaseChanged(Optional<BibDatabaseContext> newDatabase) {
if (newDatabase.isPresent()) {
GroupNodeViewModel newRoot = newDatabase
.map(BibDatabaseContext::getMetaData)
.flatMap(MetaData::getGroups)
.map(root -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, root))
.orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor));
rootGroup.setValue(newRoot);
stateManager.getSelectedGroup(newDatabase.get()).ifPresent(
selectedGroup -> this.selectedGroup.setValue(
new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup)));
}
currentDatabase = newDatabase;
}
/**
* Opens "New Group Dialog" and add the resulting group to the specified group
*/
public void addNewSubgroup(GroupNodeViewModel parent) {
SwingUtilities.invokeLater(() -> {
Optional<AbstractGroup> newGroup = dialogService.showCustomDialogAndWait(new GroupDialog());
newGroup.ifPresent(group -> {
GroupTreeNode newGroupNode = parent.addSubgroup(group);
// TODO: Add undo
//UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, new GroupTreeNodeViewModel(newGroupNode), UndoableAddOrRemoveGroup.ADD_NODE);
//panel.getUndoManager().addEdit(undo);
// TODO: Expand parent to make new group visible
//parent.expand();
dialogService.notify(Localization.lang("Added group \"%0\".", group.getName()));
});
});
}
/**
* Opens "Edit Group Dialog" and changes the given group to the edited one.
*/
public void editGroup(GroupNodeViewModel oldGroup) {
SwingUtilities.invokeLater(() -> {
Optional<AbstractGroup> newGroup = dialogService
.showCustomDialogAndWait(new GroupDialog(oldGroup.getGroupNode().getGroup()));
newGroup.ifPresent(group -> {
Platform.runLater(() -> {
// TODO: Keep assignments
boolean keepPreviousAssignments = dialogService.showConfirmationDialogAndWait(
Localization.lang("Change of Grouping Method"),
Localization.lang("Assign the original group's entries to this group?"));
// WarnAssignmentSideEffects.warnAssignmentSideEffects(newGroup, panel.frame());
boolean removePreviousAssignents = (oldGroup.getGroupNode().getGroup() instanceof ExplicitGroup)
&& (group instanceof ExplicitGroup);
List<FieldChange> addChange = oldGroup.getGroupNode().setGroup(
group,
keepPreviousAssignments,
removePreviousAssignents,
stateManager.getEntriesInCurrentDatabase());
// TODO: Add undo
// Store undo information.
// AbstractUndoableEdit undoAddPreviousEntries = null;
// UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, groupsRoot, node, newGroup);
// if (undoAddPreviousEntries == null) {
// panel.getUndoManager().addEdit(undo);
//} else {
// NamedCompound nc = new NamedCompound("Modify Group");
// nc.addEdit(undo);
// nc.addEdit(undoAddPreviousEntries);
// nc.end();/
// panel.getUndoManager().addEdit(nc);
//}
//if (!addChange.isEmpty()) {
// undoAddPreviousEntries = UndoableChangeEntriesOfGroup.getUndoableEdit(null, addChange);
//}
dialogService.notify(Localization.lang("Modified group \"%0\".", group.getName()));
});
});
});
}
public void removeSubgroups(GroupNodeViewModel group) {
boolean confirmation = dialogService.showConfirmationDialogAndWait(
Localization.lang("Remove subgroups"),
Localization.lang("Remove all subgroups of \"%0\"?", group.getDisplayName()));
if (confirmation) {
/// TODO: Add undo
//final UndoableModifySubtree undo = new UndoableModifySubtree(getGroupTreeRoot(), node, "Remove subgroups");
//panel.getUndoManager().addEdit(undo);
group.getGroupNode().removeAllChildren();
dialogService.notify(Localization.lang("Removed all subgroups of group \"%0\".", group.getDisplayName()));
}
}
public void removeGroupKeepSubgroups(GroupNodeViewModel group) {
boolean confirmation = dialogService.showConfirmationDialogAndWait(
Localization.lang("Remove group"),
Localization.lang("Remove group \"%0\"?", group.getDisplayName()));
if (confirmation) {
// TODO: Add undo
//final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN);
//panel.getUndoManager().addEdit(undo);
GroupTreeNode groupNode = group.getGroupNode();
groupNode.getParent()
.ifPresent(parent -> groupNode.moveAllChildrenTo(parent, parent.getIndexOfChild(groupNode).get()));
groupNode.removeFromParent();
dialogService.notify(Localization.lang("Removed group \"%0\".", group.getDisplayName()));
}
}
/**
* Removes the specified group and its subgroups (after asking for confirmation).
*/
public void removeGroupAndSubgroups(GroupNodeViewModel group) {
boolean confirmed = dialogService.showConfirmationDialogAndWait(
Localization.lang("Remove group and subgroups"),
Localization.lang("Remove group \"%0\" and its subgroups?", group.getDisplayName()),
Localization.lang("Remove"));
if (confirmed) {
// TODO: Add undo
//final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_AND_CHILDREN);
//panel.getUndoManager().addEdit(undo);
group.getGroupNode().removeFromParent();
dialogService.notify(Localization.lang("Removed group \"%0\" and its subgroups.", group.getDisplayName()));
}
}
public void addSelectedEntries(GroupNodeViewModel group) {
// TODO: Warn
// if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(node.getNode().getGroup(), panel.frame())) {
// return; // user aborted operation
List<FieldChange> addChange = group.getGroupNode().addEntriesToGroup(stateManager.getSelectedEntries());
// TODO: Add undo
// NamedCompound undoAll = new NamedCompound(Localization.lang("change assignment of entries"));
// if (!undoAdd.isEmpty()) { undo.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(node, undoAdd)); }
// panel.getUndoManager().addEdit(undoAll);
}
public void removeSelectedEntries(GroupNodeViewModel group) {
// TODO: warn if assignment has undesired side effects (modifies a field != keywords)
// if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(mNode.getNode().getGroup(), mPanel.frame())) {
// return; // user aborted operation
List<FieldChange> removeChange = group.getGroupNode().removeEntriesFromGroup(stateManager.getSelectedEntries());
// TODO: Add undo
// if (!undo.isEmpty()) {
// mPanel.getUndoManager().addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(mNode, undo));
}
public void sortAlphabeticallyRecursive(GroupNodeViewModel group) {
group.getGroupNode().sortChildren(compAlphabetIgnoreCase, true);
}
}