Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit ea1bc4da authored by Nicolas Carion's avatar Nicolas Carion

Function to split groups according to criterion + tests

parent c62e3247
......@@ -65,11 +65,11 @@ Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids,
};
}
int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection)
int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection, bool force)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(!ids.empty());
if (ids.size() == 1) {
if (ids.size() == 1 && !force) {
// We do not create a group with only one element. Instead, we return the id of that element
return *(ids.begin());
}
......@@ -267,43 +267,42 @@ std::unordered_map<int, std::unordered_set<int>>GroupsModel::groupsDataDownlink(
bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo)
{
// The idea is as follow: we start from the leaves, and go up to the root.
// In the process, if we find a node with only one children, we flag it for deletion
QWriteLocker locker(&m_lock);
Q_ASSERT(m_upLink.count(id) > 0);
auto leaves = getLeaves(id);
std::unordered_map<int, int> old_parents, new_parents;
std::vector<int> to_delete;
std::unordered_set<int> processed; // to avoid going twice along the same branch
for(int leaf : leaves) {
int current = m_upLink[leaf];
while (current != m_upLink[id] && m_downLink[current].size() == 1) {
to_delete.push_back(current);
int start = leaf;
while (current != m_upLink[id] && processed.count(current) == 0 ) {
processed.insert(current);
if (m_downLink[current].size() == 1) {
to_delete.push_back(current);
} else {
if (current != m_upLink[start]) {
old_parents[start] = m_upLink[start];
new_parents[start] = current;
}
start = current;
}
current = m_upLink[current];
}
if (current != m_upLink[leaf]) {
old_parents[leaf] = m_upLink[leaf];
new_parents[leaf] = current;
if (current != m_upLink[start]) {
old_parents[start] = m_upLink[start];
new_parents[start] = current;
}
}
Fun reverse = [this, old_parents]() {
auto parent_changer = [this](const std::unordered_map<int, int>& parents){
auto ptr = m_parent.lock();
if (!ptr) {
qDebug() << "Impossible to create group because the timeline is not available anymore";
return false;
}
for (const auto& group : old_parents) {
setGroup(group.first, group.second);
if (group.second == -1 && ptr->isClip(group.first)) {
QModelIndex ix = ptr->makeClipIndexFromID(group.first);
ptr->dataChanged(ix, ix, {TimelineItemModel::GroupedRole});
}
}
return true;
};
Fun operation = [this, new_parents]() {
auto ptr = m_parent.lock();
if (!ptr) {
qDebug() << "Impossible to create group because the timeline is not available anymore";
return false;
}
for (const auto& group : new_parents) {
for (const auto& group : parents) {
int old = m_upLink[group.first];
setGroup(group.first, group.second);
if (old == -1 && group.second != -1 && ptr->isClip(group.first)) {
......@@ -313,6 +312,8 @@ bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo)
}
return true;
};
Fun reverse = [this, old_parents, parent_changer]() { return parent_changer(old_parents); };
Fun operation = [this, new_parents, parent_changer]() { return parent_changer(new_parents); };
bool res = operation();
if (!res) {
bool undone = reverse();
......@@ -332,3 +333,101 @@ bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo)
}
return true;
}
bool GroupsModel::split(int id, std::function<bool(int)> criterion, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
// This function is valid only for roots (otherwise it is not clear what should be the new parent of the created tree)
Q_ASSERT(m_upLink[id] == -1);
// We do a BFS on the tree to copy it
// We store corresponding nodes
std::unordered_map<int, int> corresp; //keys are id in the original tree, values are temporary negative id assigned for creation of the new tree
corresp[-1] = -1;
// These are the nodes to be moved to new tree
std::vector<int > to_move;
// We store the groups (ie the nodes) that are going to be part of the new tree
// Keys are temporary id (negative) and values are the set of children (true ids in the case of leaves and temporary ids for other nodes)
std::unordered_map<int, std::unordered_set<int> > new_groups;
std::queue<int> queue;
queue.push(id);
int tempId = -10;
while (!queue.empty()) {
int current = queue.front();
queue.pop();
if (!isLeaf(current) || criterion(current)) {
if (isLeaf(current)) {
to_move.push_back(current);
new_groups[corresp[m_upLink[current]]].insert(current);
} else {
corresp[current] = tempId;
if (m_upLink[current] != -1)
new_groups[corresp[m_upLink[current]]].insert(tempId);
tempId--;
}
}
for (const int &child : m_downLink.at(current)) {
queue.push(child);
}
}
// First, we simulate deletion of elements that we have to remove from the original tree
// A side effect of this is that empty groups will be removed
for (const auto &leaf : to_move) {
destructGroupItem(leaf, true, undo, redo, false);
}
// we artificially recreate the leaves
Fun operation = [this, to_move]() {
for (const auto &leaf : to_move) {
createGroupItem(leaf);
}
return true;
};
Fun reverse = [this, to_move]() {
for (const auto &group : to_move) {
destructGroupItem(group);
}
return true;
};
bool res = operation();
if (!res) {
return false;
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
// We now regroup the items of the new tree to recreate hierarchy.
// This is equivalent to creating the tree bottom up (starting from the leaves)
// At each iteration, we create a new node by grouping together elements that are either leaves or already created nodes.
std::unordered_map<int, int> created_id; // to keep track of node that we create
while (!new_groups.empty()) {
int selected = INT_MAX;
for (const auto &group : new_groups) {
// we check that all children are already created
bool ok = true;
for (int elem : group.second) {
if (elem < -1 && created_id.count(elem) == 0) {
ok = false;
break;
}
}
if (ok) {
selected = group.first;
break;
}
}
Q_ASSERT(selected!=INT_MAX);
std::unordered_set<int> group;
for (int elem : new_groups[selected]) {
group.insert(elem < -1 ? created_id[elem] : elem);
}
int gid = groupItems(group, undo, redo, false, true);
created_id[selected] = gid;
new_groups.erase(selected);
}
mergeSingleGroups(id, undo, redo);
mergeSingleGroups(created_id[corresp[id]], undo, redo);
return res;
}
......@@ -48,7 +48,7 @@ public:
@param redo Lambda function containing the current redo queue. Will be updated with current operation
Returns the id of the new group, or -1 on error.
*/
int groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection = false);
int groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection = false, bool force = false);
protected:
/* Lambda version */
......@@ -85,6 +85,14 @@ public:
*/
bool mergeSingleGroups(int id, Fun &undo, Fun &redo);
/* @brief Split the group tree according to a given criterion
All the leaves satisfying the criterion are moved to the new tree, the other stay
Both tree are subsequently simplified to avoid weird structure.
@param id is the root of the tree
*/
bool split(int id, std::function<bool(int)> criterion, Fun &undo, Fun &redo);
/* @brief Get the overall father of a given groupItem
If the element has no father, it is returned as is.
@param id id of the groupitem
......
......@@ -776,4 +776,167 @@ TEST_CASE("Undo/redo", "[GroupsModel]")
redo();
test_tree2();
}
SECTION("MergeSingleGroups3"){
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE(groups.m_upLink.size() == 0);
for (int i = 0; i < 6; i++) {
groups.createGroupItem(i);
}
groups.setGroup(0,2);
groups.setGroup(1,0);
groups.setGroup(3,1);
groups.setGroup(4,1);
groups.setGroup(5,4);
auto test_tree = [&]() {
for (int i = 0; i < 6; i++) {
REQUIRE(groups.getRootId(i) == 2);
}
REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0,1,2,3,4,5}));
REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({4,3}));
REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({0}));
REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({5}));
REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({}));
};
test_tree();
REQUIRE(groups.mergeSingleGroups(2, undo, redo));
auto test_tree2 = [&]() {
REQUIRE(groups.getRootId(1) == 1);
REQUIRE(groups.getRootId(3) == 1);
REQUIRE(groups.getRootId(5) == 1);
REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({1,3,5}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({3,5}));
REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({}));
};
test_tree2();
undo();
test_tree();
redo();
test_tree2();
}
SECTION("Simple split Tree"){
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE(groups.m_upLink.size() == 0);
// This is a dummy split criterion
auto criterion = [](int a) { return a % 2 == 0;};
// We create a very simple tree
for (int i = 0; i < 3; i++) {
groups.createGroupItem(i);
}
groups.setGroup(1,0);
groups.setGroup(2,0);
auto test_tree = [&]() {
REQUIRE(groups.getRootId(0) == 0);
REQUIRE(groups.getRootId(1) == 0);
REQUIRE(groups.getRootId(2) == 0);
REQUIRE(groups.getSubtree(0) == std::unordered_set<int>({0,1,2}));
REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1,2}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({}));
};
test_tree();
REQUIRE(groups.split(0, criterion, undo, redo));
auto test_tree2 = [&]() {
REQUIRE(groups.getRootId(1) == 1);
REQUIRE(groups.getRootId(2) == 2);
REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({2}));
REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({1}));
REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({}));
};
test_tree2();
undo();
test_tree();
redo();
test_tree2();
}
SECTION("complex split Tree"){
Fun undo = []() { return true; };
Fun redo = []() { return true; };
REQUIRE(groups.m_upLink.size() == 0);
// This is a dummy split criterion
auto criterion = [](int a) { return a % 2 != 0;};
for (int i = 0; i < 9; i++) {
groups.createGroupItem(i);
}
groups.setGroup(0,3);
groups.setGroup(1,0);
groups.setGroup(3,2);
groups.setGroup(4,3);
groups.setGroup(5,8);
groups.setGroup(6,0);
groups.setGroup(7,8);
groups.setGroup(8,2);
auto test_tree = [&]() {
for (int i = 0; i < 9; i++) {
REQUIRE(groups.getRootId(i) == 2);
}
REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0,1,2,3,4,5,6,7,8}));
REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1,6}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({3, 8}));
REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({0,4}));
REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(6) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(7) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(8) == std::unordered_set<int>({5,7}));
};
test_tree();
REQUIRE(groups.split(2, criterion, undo, redo));
auto test_tree2 = [&]() {
REQUIRE(groups.getRootId(6) == 3);
REQUIRE(groups.getRootId(3) == 3);
REQUIRE(groups.getRootId(4) == 3);
REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3,4,6}));
REQUIRE(groups.getDirectChildren(6) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({6,4}));
// new tree
int newRoot = groups.getRootId(1);
REQUIRE(groups.getRootId(1) == newRoot);
REQUIRE(groups.getRootId(5) == newRoot);
REQUIRE(groups.getRootId(7) == newRoot);
int other = -1;
REQUIRE(groups.getDirectChildren(newRoot).size() == 2);
for (int c : groups.getDirectChildren(newRoot))
if (c != 1)
other = c;
REQUIRE(other != -1);
REQUIRE(groups.getSubtree(newRoot) == std::unordered_set<int>({1,5,7,newRoot, other}));
REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(7) == std::unordered_set<int>({}));
REQUIRE(groups.getDirectChildren(newRoot) == std::unordered_set<int>({1,other}));
REQUIRE(groups.getDirectChildren(other) == std::unordered_set<int>({5,7}));
};
test_tree2();
undo();
test_tree();
redo();
test_tree2();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment