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 18ebf1bf authored by Nicolas Carion's avatar Nicolas Carion

We can define group types more properly

parent a9b96114
......@@ -40,6 +40,14 @@ enum MonitorId { NoMonitor = 0x01, ClipMonitor = 0x02, ProjectMonitor = 0x04, Re
const int DefaultThumbHeight = 100;
}
enum class GroupType {
Normal,
Selection, // in that case, the group is used to emulate a selection
AVSplit // in that case, the group links the audio and video of the same clip
};
enum class ObjectType { TimelineClip, TimelineComposition, TimelineTrack, BinClip, NoItem };
using ObjectId = std::pair<ObjectType, int>;
......
......@@ -33,21 +33,17 @@ GroupsModel::GroupsModel(std::weak_ptr<TimelineItemModel> parent)
{
}
Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids, bool temporarySelection, int parent)
Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids, GroupType type, int parent)
{
QWriteLocker locker(&m_lock);
return [gid, ids, parent, temporarySelection, this]() {
return [gid, ids, parent, type, this]() {
createGroupItem(gid);
if (temporarySelection) {
Q_ASSERT(m_selectionGroup == -1);
m_selectionGroup = gid;
}
if (parent != -1) {
setGroup(gid, parent);
}
Q_ASSERT(m_groupIds.count(gid) == 0);
m_groupIds.insert(gid);
m_groupIds.insert({gid, type});
auto ptr = m_parent.lock();
if (ptr) {
......@@ -60,7 +56,7 @@ Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids,
std::transform(ids.begin(), ids.end(), std::inserter(roots, roots.begin()), [&](int id) { return getRootId(id); });
for (int id : roots) {
setGroup(getRootId(id), gid);
if (!temporarySelection && ptr->isClip(id)) {
if (type != GroupType::Selection && ptr->isClip(id)) {
QModelIndex ix = ptr->makeClipIndexFromID(id);
ptr->dataChanged(ix, ix, {TimelineItemModel::GroupedRole});
}
......@@ -69,7 +65,7 @@ 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, bool force)
int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type, bool force)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(!ids.empty());
......@@ -78,7 +74,7 @@ int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &
return *(ids.begin());
}
int gid = TimelineModel::getNextId();
auto operation = groupItems_lambda(gid, ids, temporarySelection);
auto operation = groupItems_lambda(gid, ids, type);
if (operation()) {
auto reverse = destructGroupItem_lambda(gid);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
......@@ -87,7 +83,7 @@ int GroupsModel::groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &
return -1;
}
bool GroupsModel::ungroupItem(int id, Fun &undo, Fun &redo, bool temporarySelection)
bool GroupsModel::ungroupItem(int id, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
int gid = getRootId(id);
......@@ -96,7 +92,7 @@ bool GroupsModel::ungroupItem(int id, Fun &undo, Fun &redo, bool temporarySelect
return false;
}
return destructGroupItem(gid, true, undo, redo, temporarySelection);
return destructGroupItem(gid, true, undo, redo);
}
void GroupsModel::createGroupItem(int id)
......@@ -132,25 +128,26 @@ Fun GroupsModel::destructGroupItem_lambda(int id)
}
m_downLink.erase(id);
m_upLink.erase(id);
if (m_selectionGroup == id) {
m_selectionGroup = -1;
}
return true;
};
}
bool GroupsModel::destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo, bool temporarySelection)
bool GroupsModel::destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_upLink.count(id) > 0);
int parent = m_upLink[id];
auto old_children = m_downLink[id];
auto old_type = GroupType::Selection;
if (m_groupIds.count(id) > 0) {
old_type = m_groupIds[id];
}
auto operation = destructGroupItem_lambda(id);
if (operation()) {
auto reverse = groupItems_lambda(id, old_children, temporarySelection, parent);
auto reverse = groupItems_lambda(id, old_children, old_type, parent);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
if (parent != -1 && m_downLink[parent].empty() && deleteOrphan) {
return destructGroupItem(parent, true, undo, redo, temporarySelection);
return destructGroupItem(parent, true, undo, redo);
}
return true;
}
......@@ -243,7 +240,6 @@ void GroupsModel::setGroup(int id, int groupId)
QWriteLocker locker(&m_lock);
Q_ASSERT(m_upLink.count(id) > 0);
Q_ASSERT(groupId == -1 || m_downLink.count(groupId) > 0);
Q_ASSERT(groupId == -1 || m_selectionGroup != id);
Q_ASSERT(id != groupId);
removeFromGroup(id);
m_upLink[id] = groupId;
......@@ -331,7 +327,7 @@ bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo)
for (int gid : to_delete) {
Q_ASSERT(m_downLink[gid].size() == 0);
res = destructGroupItem(gid, false, undo, redo, false);
res = destructGroupItem(gid, false, undo, redo);
if (!res) {
bool undone = undo();
Q_ASSERT(undone);
......@@ -379,7 +375,7 @@ bool GroupsModel::split(int id, const std::function<bool(int)> &criterion, Fun &
// 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);
destructGroupItem(leaf, true, undo, redo);
}
// we artificially recreate the leaves
......@@ -454,7 +450,7 @@ bool GroupsModel::split(int id, const std::function<bool(int)> &criterion, Fun &
for (int elem : new_groups[selected]) {
group.insert(elem < -1 ? created_id[elem] : elem);
}
int gid = groupItems(group, undo, redo, false, true);
int gid = groupItems(group, undo, redo, GroupType::Normal, true);
created_id[selected] = gid;
new_groups.erase(selected);
}
......@@ -499,7 +495,7 @@ bool GroupsModel::processCopy(int gid, std::unordered_map<int, int> &mapping, Fu
targetGroup.insert(mapping.at(child));
}
qDebug() << "processCopy" << gid << "success of child" << ok;
if (ok && gid != m_selectionGroup) {
if (ok && m_groupIds[gid] != GroupType::Selection) {
int id = groupItems(targetGroup, undo, redo);
qDebug() << "processCopy" << gid << "created id" << id;
if (id != -1) {
......
......@@ -22,6 +22,7 @@
#ifndef GROUPMODEL_H
#define GROUPMODEL_H
#include "definitions.h"
#include "undohelper.hpp"
#include <QReadWriteLock>
#include <memory>
......@@ -33,6 +34,7 @@ class TimelineItemModel;
/* @brief This class represents the group hiearchy. This is basically a tree structure
In this class, we consider that a groupItem is either a clip or a group
*/
class GroupsModel
{
......@@ -46,13 +48,14 @@ public:
@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
@param type indicates the type of group we create
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, bool force = false);
int groupItems(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal, bool force = false);
protected:
/* Lambda version */
Fun groupItems_lambda(int gid, const std::unordered_set<int> &ids, bool temporarySelection = false, int parent = -1);
Fun groupItems_lambda(int gid, const std::unordered_set<int> &ids, GroupType type = GroupType::Normal, int parent = -1);
public:
/* Deletes the topmost group containing given element
......@@ -62,7 +65,7 @@ public:
@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
*/
bool ungroupItem(int id, Fun &undo, Fun &redo, bool temporarySelection = false);
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
......@@ -149,7 +152,7 @@ protected:
@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
*/
bool destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo, bool temporarySelection = false);
bool destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo);
/* Lambda version */
Fun destructGroupItem_lambda(int id);
......@@ -173,10 +176,9 @@ 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)
std::unordered_map<int, GroupType> m_groupIds; // this keeps track of "real" groups (non-leaf elements), and their types
mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
int m_selectionGroup{-1}; // this is the id of the group that is used to simulate a selection
};
#endif
......@@ -489,7 +489,7 @@ bool TimelineItemModel::loadGroups(const QString &groupsData)
}
}
if (validGroup) {
int newGroupId = requestClipsGroup(ids, false, false);
int newGroupId = requestClipsGroup(ids, false, GroupType::Normal);
processedIds.insert(previousId, newGroupId);
}
i++;
......
......@@ -800,26 +800,26 @@ bool TimelineModel::requestItemResize(int itemId, int size, bool right, bool log
return result;
}
int TimelineModel::requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo, bool temporarySelection)
int TimelineModel::requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo, GroupType type)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (m_temporarySelectionGroup > -1) {
requestClipUngroup(m_temporarySelectionGroup, undo, redo, true);
requestClipUngroup(m_temporarySelectionGroup, undo, redo);
m_temporarySelectionGroup = -1;
}
int result = requestClipsGroup(ids, undo, redo, temporarySelection);
if (temporarySelection) {
int result = requestClipsGroup(ids, undo, redo, type);
if (type == GroupType::Selection) {
m_temporarySelectionGroup = result;
}
if (result > -1 && logUndo && !temporarySelection) {
if (result > -1 && logUndo && type != GroupType::Selection) {
PUSH_UNDO(undo, redo, i18n("Group clips"));
}
return result;
}
int TimelineModel::requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection)
int TimelineModel::requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type)
{
#ifdef LOGGING
std::stringstream group;
......@@ -847,8 +847,8 @@ int TimelineModel::requestClipsGroup(const std::unordered_set<int> &ids, Fun &un
return -1;
}
}
int groupId = m_groups->groupItems(ids, undo, redo, temporarySelection);
if (temporarySelection && *(ids.begin()) == groupId) {
int groupId = m_groups->groupItems(ids, undo, redo, type);
if (type == GroupType::Selection && *(ids.begin()) == groupId) {
// only one element selected, no group created
return -1;
}
......@@ -870,9 +870,9 @@ bool TimelineModel::requestClipUngroup(int id, bool logUndo)
return result;
}
bool TimelineModel::requestClipUngroup(int id, Fun &undo, Fun &redo, bool temporarySelection)
bool TimelineModel::requestClipUngroup(int id, Fun &undo, Fun &redo)
{
return m_groups->ungroupItem(id, undo, redo, temporarySelection);
return m_groups->ungroupItem(id, undo, redo);
}
bool TimelineModel::requestTrackInsertion(int position, int &id, const QString &trackName, bool audioTrack)
......
......@@ -22,6 +22,7 @@
#ifndef TIMELINEMODEL_H
#define TIMELINEMODEL_H
#include "definitions.h"
#include "undohelper.hpp"
#include <assert.h>
#include "definitions.h"
......@@ -370,8 +371,8 @@ public:
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
*/
int requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo = true, bool temporarySelection = false);
int requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, bool temporarySelection = false);
int requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo = true, GroupType type = GroupType::Normal);
int requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal);
/* @brief Destruct the topmost group containing clip
This action is undoable
......@@ -380,7 +381,7 @@ public:
*/
bool requestClipUngroup(int id, bool logUndo = true);
/* Same function, but accumulates undo and redo*/
bool requestClipUngroup(int id, Fun &undo, Fun &redo, bool temporarySelection = false);
bool requestClipUngroup(int id, Fun &undo, Fun &redo);
/* @brief Create a track at given position
This action is undoable
......
......@@ -106,7 +106,7 @@ void TimelineController::addSelection(int newSelection)
m_selection.selectedClips << newSelection;
std::unordered_set<int> ids;
ids.insert(m_selection.selectedClips.cbegin(), m_selection.selectedClips.cend());
m_model->requestClipsGroup(ids, true, true);
m_model->requestClipsGroup(ids, true, GroupType::Selection);
emit selectionChanged();
if (!m_selection.selectedClips.isEmpty())
......@@ -193,7 +193,7 @@ void TimelineController::setSelection(const QList<int> &newSelection, int trackI
if (!m_selection.selectedClips.isEmpty()) {
std::unordered_set<int> ids;
ids.insert(m_selection.selectedClips.cbegin(), m_selection.selectedClips.cend());
m_model->requestClipsGroup(ids, true, true);
m_model->requestClipsGroup(ids, true, GroupType::Selection);
emitSelectedFromSelection();
}
else {
......@@ -580,7 +580,7 @@ void TimelineController::selectItems(QVariantList arg, int startFrame, int endFr
for (int x: clipsToSelect) {
m_selection.selectedClips << x;
}
m_model->requestClipsGroup(clipsToSelect, true, true);
m_model->requestClipsGroup(clipsToSelect, true, GroupType::Selection);
emit selectionChanged();
}
......
......@@ -1674,10 +1674,10 @@ TEST_CASE("Advanced trimming operations", "[Trimming]")
REQUIRE(timeline->requestClipMove(cid5, tid2, l));
REQUIRE(timeline->requestClipMove(cid6, tid2, 2*l));
REQUIRE(timeline->requestClipMove(cid7, tid1, 200));
int gid1 =timeline->requestClipsGroup(std::unordered_set<int>({cid1, cid4}), true, false);
int gid2 =timeline->requestClipsGroup(std::unordered_set<int>({cid2, cid5}), true, false);
int gid3 =timeline->requestClipsGroup(std::unordered_set<int>({cid3, cid6}), true, false);
int gid4 =timeline->requestClipsGroup(std::unordered_set<int>({cid1, cid2, cid3, cid4, cid5, cid6, cid7}), true, false);
int gid1 =timeline->requestClipsGroup(std::unordered_set<int>({cid1, cid4}), true, GroupType::Normal);
int gid2 =timeline->requestClipsGroup(std::unordered_set<int>({cid2, cid5}), true, GroupType::Normal);
int gid3 =timeline->requestClipsGroup(std::unordered_set<int>({cid3, cid6}), true, GroupType::Normal);
int gid4 =timeline->requestClipsGroup(std::unordered_set<int>({cid1, cid2, cid3, cid4, cid5, cid6, cid7}), true, GroupType::Normal);
auto state = [&](){
REQUIRE(timeline->checkConsistency());
int p = 0;
......
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