Commit 0ca212ca authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Major speedup in clip selection that caused several seconds lag on large projects

parent fcbb7b7f
Pipeline #4112 passed with stage
in 28 minutes and 16 seconds
......@@ -548,8 +548,9 @@ void ClipModel::setCurrentTrackId(int tid, bool finalMove)
}
}
if (finalMove && tid != -1) {
if (finalMove && tid != -1 && m_lastTrackId != m_currentTrackId) {
refreshProducerFromBin(m_currentState);
m_lastTrackId = m_currentTrackId;
}
}
......@@ -733,6 +734,9 @@ void ClipModel::setOffset(int offset)
void ClipModel::setGrab(bool grab)
{
QWriteLocker locker(&m_lock);
if (grab == m_grabbed) {
return;
}
m_grabbed = grab;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
......@@ -740,6 +744,19 @@ void ClipModel::setGrab(bool grab)
}
}
void ClipModel::setSelected(bool sel)
{
QWriteLocker locker(&m_lock);
if (sel == selected) {
return;
}
selected = sel;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole});
}
}
void ClipModel::clearOffset()
{
if (m_positionOffset != 0) {
......
......@@ -100,6 +100,7 @@ public:
int getFakePosition() const;
void setFakePosition(int fid);
void setGrab(bool grab) override;
void setSelected(bool sel) override;
/* @brief Returns an XML representation of the clip with its effects */
QDomElement toXml(QDomDocument &document);
......@@ -233,6 +234,9 @@ protected:
int m_positionOffset;
int m_subPlaylistIndex; // Tracks have two sub playlists to enable same track transitions, we store in which one this clip is.
// Remember last set track, so that we don't unnecessarily refresh the producer when deleting and re-adding a clip on same track
int m_lastTrackId = -1;
};
#endif
......@@ -253,6 +253,9 @@ void CompositionModel::setInOut(int in, int out)
void CompositionModel::setGrab(bool grab)
{
QWriteLocker locker(&m_lock);
if (grab == m_grabbed) {
return;
}
m_grabbed = grab;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
......@@ -260,6 +263,19 @@ void CompositionModel::setGrab(bool grab)
}
}
void CompositionModel::setSelected(bool sel)
{
QWriteLocker locker(&m_lock);
if (sel == selected) {
return;
}
selected = sel;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole});
}
}
void CompositionModel::setCurrentTrackId(int tid, bool finalMove)
{
Q_UNUSED(finalMove);
......
......@@ -96,6 +96,7 @@ public:
/* @brief Returns an XML representation of the clip with its effects */
QDomElement toXml(QDomDocument &document);
void setGrab(bool grab) override;
void setSelected(bool sel) override;
protected:
Mlt::Transition *service() const override;
......
......@@ -85,7 +85,12 @@ Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set<int> &ids,
auto ptr = m_parent.lock();
if (!ptr) Q_ASSERT(false);
for (int id : roots) {
setGroup(getRootId(id), gid, type != GroupType::Selection);
if (type != GroupType::Selection) {
setGroup(getRootId(id), gid, true);
} else {
setGroup(getRootId(id), gid, false);
ptr->setSelected(id, true);
}
}
}
return true;
......
......@@ -81,6 +81,11 @@ public:
/* Set if the item is in grab state */
bool isGrabbed() const;
/* True if item is selected in timeline */
bool selected {false};
/* Set selected status */
virtual void setSelected(bool sel) = 0;
protected:
/* @brief Returns a pointer to the service. It may be used but do NOT store it*/
......
......@@ -99,4 +99,3 @@ template <typename Service> bool MoveableItem<Service>::isGrabbed() const
READ_LOCK();
return m_grabbed;
}
......@@ -246,7 +246,8 @@ bool TimelineFunctions::requestSpacerEndOperation(const std::shared_ptr<Timeline
// Start undoable command
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
int res = timeline->requestClipsGroup(clips, undo, redo, GroupType::Selection);
//int res = timeline->requestClipsGroup(clips, undo, redo, GroupType::Selection);
int res = timeline->m_groups->getRootId(itemId);
bool final = false;
if (res > -1 || clips.size() == 1) {
if (clips.size() > 1) {
......@@ -392,7 +393,6 @@ bool TimelineFunctions::removeSpace(const std::shared_ptr<TimelineItemModel> &ti
}
++it;
}
bool result = false;
if (!clips.empty()) {
int clipId = *clips.begin();
......
......@@ -231,6 +231,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
roles[EffectNamesRole] = "effectNames";
roles[EffectsEnabledRole] = "isStackEnabled";
roles[GrabbedRole] = "isGrabbed";
roles[SelectedRole] = "selected";
return roles;
}
......@@ -339,6 +340,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
return clip->getSpeed();
case GrabbedRole:
return clip->isGrabbed();
case SelectedRole:
return clip->selected;
default:
break;
}
......@@ -428,6 +431,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
}
case GrabbedRole:
return compo->isGrabbed();
case SelectedRole:
return compo->selected;
default:
break;
}
......
......@@ -1378,6 +1378,9 @@ bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, i
// Check if there is a track move
bool updatePositionOnly = false;
// Second step, reinsert clips at correct positions
int audio_delta, video_delta;
audio_delta = video_delta = delta_track;
if (delta_track == 0 && updateView) {
updateView = false;
allowViewRefresh = false;
......@@ -1400,69 +1403,88 @@ bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, i
};
}
// First, remove clips
std::unordered_map<int, int> old_track_ids, old_position, old_forced_track;
for (int item : sorted_clips) {
int old_trackId = getItemTrackId(item);
old_track_ids[item] = old_trackId;
if (old_trackId != -1) {
bool updateThisView = allowViewRefresh;
if (isClip(item)) {
ok = ok && getTrackById(old_trackId)->requestClipDeletion(item, updateThisView, finalMove, local_undo, local_redo, true);
old_position[item] = m_allClips[item]->getPosition();
} else {
// ok = ok && getTrackById(old_trackId)->requestCompositionDeletion(item, updateThisView, finalMove, local_undo, local_redo);
old_position[item] = m_allCompositions[item]->getPosition();
old_forced_track[item] = m_allCompositions[item]->getForcedTrack();
}
if (!ok) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
// First, remove clips
if (delta_track != 0) {
// We delete our clips only if changing track
for (int item : sorted_clips) {
int old_trackId = getItemTrackId(item);
old_track_ids[item] = old_trackId;
if (old_trackId != -1) {
bool updateThisView = allowViewRefresh;
if (isClip(item)) {
ok = ok && getTrackById(old_trackId)->requestClipDeletion(item, updateThisView, finalMove, local_undo, local_redo, true);
old_position[item] = m_allClips[item]->getPosition();
} else {
// ok = ok && getTrackById(old_trackId)->requestCompositionDeletion(item, updateThisView, finalMove, local_undo, local_redo);
old_position[item] = m_allCompositions[item]->getPosition();
old_forced_track[item] = m_allCompositions[item]->getForcedTrack();
}
if (!ok) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
}
}
// Second step, reinsert clips at correct positions
int audio_delta, video_delta;
audio_delta = video_delta = delta_track;
if (getTrackById(old_track_ids[itemId])->isAudioTrack()) {
// Master clip is audio, so reverse delta for video clips
video_delta = -delta_track;
} else {
audio_delta = -delta_track;
if (getTrackById(old_track_ids[itemId])->isAudioTrack()) {
// Master clip is audio, so reverse delta for video clips
video_delta = -delta_track;
} else {
audio_delta = -delta_track;
}
}
// We need to insert depending on the move direction to avoid confusing the view
// std::reverse(std::begin(sorted_clips), std::end(sorted_clips));
for (int item : sorted_clips) {
int current_track_id = old_track_ids[item];
int current_track_position = getTrackPosition(current_track_id);
int d = getTrackById(current_track_id)->isAudioTrack() ? audio_delta : video_delta;
int target_track_position = current_track_position + d;
bool updateThisView = allowViewRefresh;
if (target_track_position >= 0 && target_track_position < getTracksCount()) {
auto it = m_allTracks.cbegin();
std::advance(it, target_track_position);
int target_track = (*it)->getId();
int target_position = old_position[item] + delta_pos;
bool updateThisView = allowViewRefresh;
if (delta_track == 0) {
// Special case, we are moving on same track, avoid too many calculations
for (int item : sorted_clips) {
int current_track_id = getItemTrackId(item);
int target_position = getItemPosition(item) + delta_pos;
if (isClip(item)) {
ok = ok && requestClipMove(item, target_track, target_position, updateThisView, finalMove, finalMove, local_undo, local_redo, true);
ok = ok && requestClipMove(item, current_track_id, target_position, updateThisView, finalMove, finalMove, local_undo, local_redo, true);
} else {
ok = ok &&
requestCompositionMove(item, target_track, old_forced_track[item], target_position, updateThisView, finalMove, local_undo, local_redo);
requestCompositionMove(item, current_track_id, m_allCompositions[item]->getForcedTrack(), target_position, updateThisView, finalMove, local_undo, local_redo);
}
} else {
qDebug() << "// ABORTING; MOVE TRIED ON TRACK: " << target_track_position << "..\n..\n..";
ok = false;
}
if (!ok) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
} else {
// Track changed
for (int item : sorted_clips) {
int current_track_id = old_track_ids[item];
int current_track_position = getTrackPosition(current_track_id);
int d = getTrackById(current_track_id)->isAudioTrack() ? audio_delta : video_delta;
int target_track_position = current_track_position + d;
if (target_track_position >= 0 && target_track_position < getTracksCount()) {
auto it = m_allTracks.cbegin();
std::advance(it, target_track_position);
int target_track = (*it)->getId();
int target_position = old_position[item] + delta_pos;
if (isClip(item)) {
ok = ok && requestClipMove(item, target_track, target_position, updateThisView, finalMove, finalMove, local_undo, local_redo, true);
} else {
ok = ok &&
requestCompositionMove(item, target_track, old_forced_track[item], target_position, updateThisView, finalMove, local_undo, local_redo);
}
} else {
qDebug() << "// ABORTING; MOVE TRIED ON TRACK: " << target_track_position << "..\n..\n..";
ok = false;
}
if (!ok) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
}
if (updatePositionOnly) {
update_model();
......@@ -3133,13 +3155,11 @@ bool TimelineModel::requestClearSelection(bool onDeletion)
}
} else if (isClip(id)) {
m_allClips[id]->clearOffset();
if (m_allClips[id]->isGrabbed()) {
m_allClips[id]->setGrab(false);
}
m_allClips[id]->setGrab(false);
m_allClips[id]->setSelected(false);
} else if (isComposition(id)) {
if (m_allCompositions[id]->isGrabbed()) {
m_allCompositions[id]->setGrab(false);
}
m_allCompositions[id]->setGrab(false);
m_allCompositions[id]->setSelected(false);
}
if (m_groups->getType(m_currentSelection) == GroupType::Selection) {
m_groups->destructGroupItem(m_currentSelection);
......@@ -3147,13 +3167,11 @@ bool TimelineModel::requestClearSelection(bool onDeletion)
}
} else {
if (isClip(m_currentSelection)) {
if (m_allClips[m_currentSelection]->isGrabbed()) {
m_allClips[m_currentSelection]->setGrab(false);
}
m_allClips[m_currentSelection]->setGrab(false);
m_allClips[m_currentSelection]->setSelected(false);
} else if (isComposition(m_currentSelection)) {
if (m_allCompositions[m_currentSelection]->isGrabbed()) {
m_allCompositions[m_currentSelection]->setGrab(false);
}
m_allCompositions[m_currentSelection]->setGrab(false);
m_allCompositions[m_currentSelection]->setSelected(false);
}
Q_ASSERT(onDeletion || isClip(m_currentSelection) || isComposition(m_currentSelection));
}
......@@ -3234,6 +3252,7 @@ bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids)
m_currentSelection = -1;
} else if (roots.size() == 1) {
m_currentSelection = *(roots.begin());
setSelected(m_currentSelection, true);
} else {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
......@@ -3272,6 +3291,20 @@ bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids)
return result;
}
void TimelineModel::setSelected(int itemId, bool sel)
{
if (isClip(itemId)) {
m_allClips[itemId]->setSelected(sel);
} else if (isComposition(itemId)) {
m_allCompositions[itemId]->setSelected(sel);
} else if (isGroup(itemId)) {
auto leaves = m_groups->getLeaves(itemId);
for (auto &id : leaves) {
setSelected(id, true);
}
}
}
bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
......
......@@ -155,6 +155,7 @@ public:
EffectNamesRole, // track and clip only
EffectsEnabledRole, // track and clip only
GrabbedRole, /// clip+composition only
SelectedRole, /// clip+composition only
TrackActiveRole, /// track only
AudioRecordRole /// track only
};
......@@ -404,6 +405,9 @@ protected:
@param state: The desired clip state (original, audio/video only).
*/
bool requestClipCreation(const QString &binClipId, int &id, PlaylistState::ClipState state, double speed, Fun &undo, Fun &redo);
/* @brief Switch item selection status */
void setSelected(int itemId, bool sel);
public:
/* @brief Deletes the given clip or composition from the timeline.
......
......@@ -77,7 +77,7 @@ Column{
Binding {
target: loader.item
property: "selected"
value: loader.item ? root.timelineSelection.indexOf(loader.item.clipId) !== -1 : false
value: model.selected
when: loader.status == Loader.Ready && model.clipType != ProducerType.Track
}
Binding {
......
......@@ -244,8 +244,9 @@ void TimelineController::selectCurrentItem(ObjectType type, bool select, bool ad
QList<int> TimelineController::selection() const
{
if (!m_root) return QList<int>();
std::unordered_set<int> sel = m_model->getCurrentSelection();
QList<int> items;
for (int id : m_model->getCurrentSelection()) {
for (int id : sel) {
items << id;
}
return items;
......
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