Commit bb0f699a authored by Nicolas Carion's avatar Nicolas Carion

[Timeline2][Model] Undo grouping / ungrouping + more tests

parent 9a5cb80b
......@@ -29,42 +29,61 @@ GroupsModel::GroupsModel(std::weak_ptr<TimelineModel> parent) :
{
}
int GroupsModel::groupItems(const std::unordered_set<int>& ids)
Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int>& ids)
{
return [gid, ids, this](){
qDebug() << "grouping items in group"<<gid;
qDebug() << "Ids";
for(auto i : ids) qDebug() << i;
qDebug() << "roots";
for(auto i : ids) qDebug() << getRootId(i);
createGroupItem(gid);
Q_ASSERT(m_groupIds.count(gid) == 0);
m_groupIds.insert(gid);
if (auto ptr = m_parent.lock()) {
ptr->registerGroup(gid);
} else {
qDebug() << "Impossible to create group because the timeline is not available anymore";
Q_ASSERT(false);
}
std::unordered_set<int> roots;
std::transform(ids.begin(), ids.end(), std::inserter(roots, roots.begin()),
[&](int id){return getRootId(id);});
for (int id : roots) {
setGroup(getRootId(id), gid);
}
return true;
};
}
int GroupsModel::groupItems(const std::unordered_set<int>& ids, Fun &undo, Fun &redo)
{
Q_ASSERT(ids.size()>0);
if (ids.size() == 1) {
// We do not create a group with only one element. Instead, we return the id of that element
return *(ids.begin());
}
int gid = TimelineModel::getNextId();
createGroupItem(gid);
if (auto ptr = m_parent.lock()) {
ptr->registerGroup(gid);
} else {
qDebug() << "Impossible to create group because the timeline is not available anymore";
Q_ASSERT(false);
}
for (int id : ids) {
setGroup(getRootId(id), gid);
auto operation = groupItems_lambda(gid, ids);
if (operation()) {
auto reverse = destructGroupItem_lambda(gid, false);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return gid;
}
return gid;
return -1;
}
void GroupsModel::ungroupItem(int id)
bool GroupsModel::ungroupItem(int id, Fun& undo, Fun& redo)
{
int gid = getRootId(id);
if (gid == id) {
if (m_groupIds.count(gid) == 0) {
//element is not part of a group
return;
}
if (auto ptr = m_parent.lock()) {
ptr->deregisterGroup(gid);
} else {
qDebug() << "Impossible to ungroup item because the timeline is not available anymore";
Q_ASSERT(false);
return false;
}
destructGroupItem(gid);
return destructGroupItem(gid, true, undo, redo);
}
void GroupsModel::createGroupItem(int id)
......@@ -75,24 +94,43 @@ void GroupsModel::createGroupItem(int id)
m_downLink[id] = std::unordered_set<int>();
}
void GroupsModel::destructGroupItem(int id, bool deleteOrphan)
Fun GroupsModel::destructGroupItem_lambda(int id, bool deleteOrphan)
{
return [this, id, deleteOrphan]() {
if (m_groupIds.count(id) > 0) {
if(auto ptr = m_parent.lock()) {
ptr->deregisterGroup(id);
m_groupIds.erase(id);
} else {
qDebug() << "Impossible to ungroup item because the timeline is not available anymore";
Q_ASSERT(false);
}
}
removeFromGroup(id);
for (int child : m_downLink[id]) {
m_upLink[child] = -1;
}
m_downLink.erase(id);
m_upLink.erase(id);
return true;
};
}
bool GroupsModel::destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo)
{
Q_ASSERT(m_upLink.count(id) > 0);
int parent = m_upLink[id];
removeFromGroup(id);
for (int child : m_downLink[id]) {
m_upLink[child] = -1;
}
m_downLink.erase(id);
m_upLink.erase(id);
if (parent != -1 && m_downLink[parent].size() == 0 && deleteOrphan) {
if (auto ptr = m_parent.lock()) {
ptr->deregisterGroup(parent);
} else {
qDebug() << "Impossible to destruct item because the timeline is not available anymore";
Q_ASSERT(false);
auto old_children = m_downLink[id];
auto operation = destructGroupItem_lambda(id, deleteOrphan);
if (operation()) {
auto reverse = groupItems_lambda(id, old_children);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
if (parent != -1 && m_downLink[parent].size() == 0 && deleteOrphan) {
return destructGroupItem(parent, true, undo, redo);
}
destructGroupItem(parent, true);
return true;
}
return false;
}
int GroupsModel::getRootId(int id) const
......@@ -150,6 +188,8 @@ void GroupsModel::setGroup(int id, int groupId)
{
Q_ASSERT(m_upLink.count(id) > 0);
Q_ASSERT(m_downLink.count(groupId) > 0);
Q_ASSERT(id != groupId);
qDebug() << "Setting " << id <<"in group"<<groupId;
removeFromGroup(id);
m_upLink[id] = groupId;
m_downLink[groupId].insert(id);
......@@ -160,6 +200,7 @@ void GroupsModel::removeFromGroup(int id)
Q_ASSERT(m_upLink.count(id) > 0);
Q_ASSERT(m_downLink.count(id) > 0);
int parent = m_upLink[id];
qDebug() << "removing " << id <<"from group hierarchy. Is parent is"<<parent;
if (parent != -1) {
m_downLink[parent].erase(id);
}
......
......@@ -22,6 +22,7 @@
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include "undohelper.hpp"
class TimelineModel;
......@@ -38,14 +39,25 @@ public:
Note that if an item is already part of a group, its topmost group will be considered instead and added in the newly created group.
If only one id is provided, no group is created.
@param ids set containing the items to group.
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@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);
int groupItems(const std::unordered_set<int>& ids, Fun &undo, Fun& redo);
protected:
/* Lambda version */
Fun groupItems_lambda(int gid, const std::unordered_set<int>& ids);
public:
/* Deletes the topmost group containing given element
Note that if the element is not in a group, then it will not be touched.
Return true on success
@param id id of the groupitem
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
void ungroupItem(int id);
bool ungroupItem(int id, Fun& undo, Fun& redo);
/* @brief Create a groupItem in the hierarchy. Initially it is not part of a group
@param id id of the groupItem
......@@ -54,11 +66,18 @@ public:
/* @brief Destruct a groupItem in the hierarchy.
All its children will become their own roots
Return true on success
@param id id of the groupitem
@param deleteOrphan If this parameter is true, we recursively delete any group that become empty following the destruction
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
void destructGroupItem(int id, bool deleteOrphan = false);
bool destructGroupItem(int id, bool deleteOrphan, Fun& undo, Fun& redo);
protected:
/* Lambda version */
Fun destructGroupItem_lambda(int id, bool deleteOrphan);
public:
/* @brief Get the overall father of a given groupItem
@param id id of the groupitem
*/
......@@ -99,4 +118,6 @@ private:
std::unordered_map<int, int> m_upLink; //edges toward parent
std::unordered_map<int, std::unordered_set<int>> m_downLink; //edges toward children
std::unordered_set<int> m_groupIds; //this keeps track of "real" groups (non-leaf elements)
};
......@@ -328,14 +328,26 @@ bool TimelineModel::requestClipResize(int cid, int size, bool right)
}
void TimelineModel::groupClips(std::unordered_set<int>&& ids)
bool TimelineModel::requestGroupClips(const std::unordered_set<int>& ids)
{
m_groups->groupItems(std::forward<std::unordered_set<int>>(ids));
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
int gid = m_groups->groupItems(ids, undo, redo);
if (gid != -1) {
PUSH_UNDO(undo, redo, i18n("Group clips"));
}
return (gid != -1);
}
void TimelineModel::ungroupClip(int id)
bool TimelineModel::requestUngroupClip(int id)
{
m_groups->ungroupItem(id);
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
bool result = m_groups->ungroupItem(id, undo, redo);
if (result) {
PUSH_UNDO(undo, redo, i18n("Ungroup clips"));
}
return result;
}
void TimelineModel::registerTrack(std::unique_ptr<TrackModel>&& track, int pos)
......@@ -388,7 +400,10 @@ void TimelineModel::deregisterClip(int id)
//TODO send deletion order to the track containing the clip
Q_ASSERT(m_allClips.count(id) > 0);
m_allClips.erase(id);
m_groups->destructGroupItem(id, true);
//TODO this is temporary while UNDO for clip deletion is not implemented
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
m_groups->destructGroupItem(id, true, undo, redo);
}
void TimelineModel::deregisterGroup(int id)
......
......@@ -152,15 +152,17 @@ public:
bool requestClipResize(int cid, int size, bool right);
/* @brief Group together a set of ids
Returns true on success. If it fails, nothing is modified.
Typically, ids would be ids of clips, but for convenience, some of them can be ids of groups as well.
@param ids Set of ids to group
*/
void groupClips(std::unordered_set<int>&& ids);
bool requestGroupClips(const std::unordered_set<int>& ids);
/* @brief Destruct the topmost group containing clip
Returns true on success. If it fails, nothing is modified.
@param id of the clip to degroup (all clips belonging to the same group will be ungrouped as well)
*/
void ungroupClip(int id);
bool requestUngroupClip(int id);
/* @brief Get project duration
Returns the duration in frames
......
......@@ -44,6 +44,7 @@ void FunctionalUndoCommand::undo()
void FunctionalUndoCommand::redo()
{
if (m_undone) {
qDebug() << "REDOING " <<text();
bool res = m_redo();
Q_ASSERT(res);
}
......
......@@ -30,7 +30,7 @@ include_directories(
)
ADD_EXECUTABLE(runTests ${Tests_SRCS})
target_link_libraries(runTests Qt5::Core Qt5::Widgets)
target_link_libraries(runTests
target_link_libraries(runTests
${MLT_LIBRARIES}
${MLTPP_LIBRARIES}
${CMAKE_DL_LIBS}
......
This diff is collapsed.
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