package org.jabref.gui.groups; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.function.Consumer; import javax.swing.Icon; import javax.swing.JTree; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoManager; import org.jabref.gui.IconTheme; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.AllEntriesGroup; import org.jabref.model.groups.ExplicitGroup; import org.jabref.model.groups.GroupEntryChanger; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.groups.KeywordGroup; import org.jabref.model.groups.SearchGroup; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class GroupTreeNodeViewModel implements Transferable, TreeNode { public static final DataFlavor FLAVOR; private static final Log LOGGER = LogFactory.getLog(GroupTreeNodeViewModel.class); private static final Icon GROUP_REFINING_ICON = IconTheme.JabRefIcon.GROUP_REFINING.getSmallIcon(); private static final Icon GROUP_INCLUDING_ICON = IconTheme.JabRefIcon.GROUP_INCLUDING.getSmallIcon(); private static final Icon GROUP_REGULAR_ICON = null; private static final DataFlavor[] FLAVORS; static { DataFlavor df = null; try { df = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=" + GroupTreeNode.class.getCanonicalName()); } catch (ClassNotFoundException e) { LOGGER.error("Creating DataFlavor failed. This should not happen.", e); } FLAVOR = df; FLAVORS = new DataFlavor[] {GroupTreeNodeViewModel.FLAVOR}; } private final GroupTreeNode node; public GroupTreeNodeViewModel(GroupTreeNode node) { this.node = node; } @Override public String toString() { final StringBuilder sb = new StringBuilder("GroupTreeNodeViewModel{"); sb.append("node=").append(node); sb.append('}'); return sb.toString(); } @Override public DataFlavor[] getTransferDataFlavors() { return GroupTreeNodeViewModel.FLAVORS; } @Override public boolean isDataFlavorSupported(DataFlavor someFlavor) { return someFlavor.equals(GroupTreeNodeViewModel.FLAVOR); } @Override public Object getTransferData(DataFlavor someFlavor) throws UnsupportedFlavorException, IOException { if (!isDataFlavorSupported(someFlavor)) { throw new UnsupportedFlavorException(someFlavor); } return this; } @Override public TreeNode getChildAt(int childIndex) { return node.getChildAt(childIndex).map(GroupTreeNodeViewModel::new).orElse(null); } @Override public int getChildCount() { return node.getNumberOfChildren(); } @Override public TreeNode getParent() { return node.getParent().map(GroupTreeNodeViewModel::new).orElse(null); } @Override public int getIndex(TreeNode child) { if (!(child instanceof GroupTreeNodeViewModel)) { return -1; } GroupTreeNodeViewModel childViewModel = (GroupTreeNodeViewModel) child; return node.getIndexOfChild(childViewModel.getNode()).orElse(-1); } @Override public boolean getAllowsChildren() { return true; } @Override public boolean isLeaf() { return node.isLeaf(); } @Override public Enumeration<GroupTreeNodeViewModel> children() { Iterable<GroupTreeNode> children = node.getChildren(); return new Enumeration<GroupTreeNodeViewModel>() { @Override public boolean hasMoreElements() { return children.iterator().hasNext(); } @Override public GroupTreeNodeViewModel nextElement() { return new GroupTreeNodeViewModel(children.iterator().next()); } }; } public GroupTreeNode getNode() { return node; } /** Expand this node and all its children. */ public void expandSubtree(JTree tree) { tree.expandPath(this.getTreePath()); for (GroupTreeNodeViewModel child : getChildren()) { child.expandSubtree(tree); } } public List<GroupTreeNodeViewModel> getChildren() { List<GroupTreeNodeViewModel> children = new ArrayList<>(); for (GroupTreeNode child : node.getChildren()) { children.add(new GroupTreeNodeViewModel(child)); } return children; } protected boolean printInItalics() { return node.getGroup().isDynamic(); } public String getDescription() { AbstractGroup group = node.getGroup(); String shortDescription = ""; boolean showDynamic = true; if (group instanceof ExplicitGroup) { shortDescription = GroupDescriptions.getShortDescriptionExplicitGroup((ExplicitGroup) group); } else if (group instanceof KeywordGroup) { shortDescription = GroupDescriptions.getShortDescriptionKeywordGroup((KeywordGroup) group, showDynamic); } else if (group instanceof SearchGroup) { shortDescription = GroupDescriptions.getShortDescription((SearchGroup) group, showDynamic); } else { shortDescription = GroupDescriptions.getShortDescriptionAllEntriesGroup(); } return "<html>" + shortDescription + "</html>"; } public TreePath getTreePath() { List<GroupTreeNode> pathToNode = node.getPathFromRoot(); return new TreePath(pathToNode.stream().map(GroupTreeNodeViewModel::new).toArray()); } public boolean canAddEntries(List<BibEntry> entries) { return (getNode().getGroup() instanceof GroupEntryChanger) && !getNode().getGroup().containsAll(entries); } public boolean canRemoveEntries(List<BibEntry> entries) { return (getNode().getGroup() instanceof GroupEntryChanger) && getNode().getGroup().containsAny(entries); } public void sortChildrenByName(boolean recursive) { getNode().sortChildren( (node1, node2) -> node1.getGroup().getName().compareToIgnoreCase(node2.getGroup().getName()), recursive); } @Override public boolean equals(Object o) { if (this == o) { return true; } if ((o == null) || (getClass() != o.getClass())) { return false; } GroupTreeNodeViewModel viewModel = (GroupTreeNodeViewModel) o; return node.equals(viewModel.node); } @Override public int hashCode() { return node.hashCode(); } public String getName() { return getNode().getGroup().getName(); } public boolean canBeEdited() { return getNode().getGroup() instanceof AllEntriesGroup; } public boolean canMoveUp() { return (getNode().getPreviousSibling() != null) && !(getNode().getGroup() instanceof AllEntriesGroup); } public boolean canMoveDown() { return (getNode().getNextSibling() != null) && !(getNode().getGroup() instanceof AllEntriesGroup); } public boolean canMoveLeft() { return !(getNode().getGroup() instanceof AllEntriesGroup) // TODO: Null! && !(getNode().getParent().get().getGroup() instanceof AllEntriesGroup); } public boolean canMoveRight() { return (getNode().getPreviousSibling() != null) && !(getNode().getGroup() instanceof AllEntriesGroup); } public void changeEntriesTo(List<BibEntry> entries, UndoManager undoManager) { AbstractGroup group = node.getGroup(); List<FieldChange> changesRemove = new ArrayList<>(); List<FieldChange> changesAdd = new ArrayList<>(); // Sort entries into current members and non-members of the group // Current members will be removed // Current non-members will be added List<BibEntry> toRemove = new ArrayList<>(entries.size()); List<BibEntry> toAdd = new ArrayList<>(entries.size()); for (BibEntry entry : entries) { // Sort according to current state of the entries if (group.contains(entry)) { toRemove.add(entry); } else { toAdd.add(entry); } } // If there are entries to remove if (!toRemove.isEmpty()) { changesRemove = removeEntriesFromGroup(toRemove); } // If there are entries to add if (!toAdd.isEmpty()) { changesAdd = addEntriesToGroup(toAdd); } // Remember undo information if (!changesRemove.isEmpty()) { AbstractUndoableEdit undoRemove = UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesRemove); if (!changesAdd.isEmpty() && (undoRemove != null)) { // we removed and added entries undoRemove.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesAdd)); } undoManager.addEdit(undoRemove); } else if (!changesAdd.isEmpty()) { undoManager.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesAdd)); } } public List<FieldChange> removeEntriesFromGroup(List<BibEntry> entries) { return node.removeEntriesFromGroup(entries); } public boolean isAllEntriesGroup() { return getNode().getGroup() instanceof AllEntriesGroup; } public void addNewGroup(AbstractGroup newGroup, CountingUndoManager undoManager) { GroupTreeNode newNode = GroupTreeNode.fromGroup(newGroup); this.getNode().addChild(newNode); UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(this, new GroupTreeNodeViewModel(newNode), UndoableAddOrRemoveGroup.ADD_NODE); undoManager.addEdit(undo); } /** * Adds the given entries to this node's group. */ public List<FieldChange> addEntriesToGroup(List<BibEntry> entries) { return node.addEntriesToGroup(entries); } public void subscribeToDescendantChanged(Consumer<GroupTreeNodeViewModel> subscriber) { getNode().subscribeToDescendantChanged(node -> subscriber.accept(new GroupTreeNodeViewModel(node))); } }