package org.jabref.gui.groups;
import java.util.List;
import org.jabref.gui.undo.AbstractUndoableJabRefEdit;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.groups.GroupTreeNode;
class UndoableAddOrRemoveGroup extends AbstractUndoableJabRefEdit {
/** Adding of a single node (group). */
public static final int ADD_NODE = 0;
/** Removal of a single node. Children, if any, are kept. */
public static final int REMOVE_NODE_KEEP_CHILDREN = 1;
/** Removal of a node and all of its children. */
public static final int REMOVE_NODE_AND_CHILDREN = 2;
/** The root of the global groups tree */
private final GroupTreeNodeViewModel m_groupsRootHandle;
/** The subtree that was added or removed */
private final GroupTreeNode m_subtreeBackup;
/**
* In case of removing a node but keeping all of its children, the number of
* children has to be stored.
*/
private final int m_subtreeRootChildCount;
/** The path to the edited subtree's root node */
private final List<Integer> m_pathToNode;
/**
* The type of the editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN,
* REMOVE_NODE_AND_CHILDREN)
*/
private final int m_editType;
/**
* Creates an object that can undo/redo an edit event.
*
* @param groupsRoot
* The global groups root.
* @param editType
* The type of editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN,
* REMOVE_NODE_AND_CHILDREN)
* @param editedNode
* The edited node (which was added or will be removed). The node
* must be a descendant of node <b>groupsRoot</b>! This means
* that, in case of adding, you first have to add it to the tree,
* then call this constructor. When removing, you first have to
* call this constructor, then remove the node.
*/
public UndoableAddOrRemoveGroup(GroupTreeNodeViewModel groupsRoot,
GroupTreeNodeViewModel editedNode, int editType) {
m_groupsRootHandle = groupsRoot;
m_editType = editType;
m_subtreeRootChildCount = editedNode.getChildCount();
// storing a backup of the whole subtree is not required when children
// are kept
m_subtreeBackup = editType != UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN ? editedNode.getNode()
.copySubtree() : GroupTreeNode.fromGroup(editedNode.getNode().getGroup().deepCopy());
// remember path to edited node. this cannot be stored as a reference,
// because the reference itself might change. the method below is more
// robust.
m_pathToNode = editedNode.getNode().getIndexedPathFromRoot();
}
@Override
public String getPresentationName() {
switch (m_editType) {
case ADD_NODE:
return Localization.lang("add group");
case REMOVE_NODE_KEEP_CHILDREN:
return Localization.lang("remove group (keep subgroups)");
case REMOVE_NODE_AND_CHILDREN:
return Localization.lang("remove group and subgroups");
default:
break;
}
return "? (" + Localization.lang("unknown edit") + ")";
}
@Override
public void undo() {
super.undo();
doOperation(true);
}
@Override
public void redo() {
super.redo();
doOperation(false);
}
private void doOperation(boolean undo) {
GroupTreeNode cursor = m_groupsRootHandle.getNode();
final int childIndex = m_pathToNode.get(m_pathToNode.size() - 1);
// traverse path up to but last element
for (int i = 0; i < (m_pathToNode.size() - 1); ++i) {
cursor = cursor.getChildAt(m_pathToNode.get(i)).get();
}
if (undo) {
switch (m_editType) {
case ADD_NODE:
cursor.removeChild(childIndex);
break;
case REMOVE_NODE_KEEP_CHILDREN:
// move all children to newNode, then add newNode
GroupTreeNode newNode = m_subtreeBackup.copySubtree();
for (int i = childIndex; i < (childIndex
+ m_subtreeRootChildCount); ++i) {
cursor.getChildAt(childIndex).get().moveTo(newNode);
}
newNode.moveTo(cursor, childIndex);
break;
case REMOVE_NODE_AND_CHILDREN:
m_subtreeBackup.copySubtree().moveTo(cursor, childIndex);
break;
default:
break;
}
} else { // redo
switch (m_editType) {
case ADD_NODE:
m_subtreeBackup.copySubtree().moveTo(cursor, childIndex);
break;
case REMOVE_NODE_KEEP_CHILDREN:
// remove node, then insert all children
GroupTreeNode removedNode = cursor
.getChildAt(childIndex).get();
cursor.removeChild(childIndex);
while (removedNode.getNumberOfChildren() > 0) {
removedNode.getFirstChild().get().moveTo(cursor, childIndex);
}
break;
case REMOVE_NODE_AND_CHILDREN:
cursor.removeChild(childIndex);
break;
default:
break;
}
}
}
}