From 121c01bd2f8cc111a80973582b9c910e03c021eb Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sun, 14 Jan 2024 19:49:43 -0600 Subject: [PATCH] Change border color for grouped clips Grouped clips have a quite boarder when they are selected. --- src/commands/timelinecommands.cpp | 155 +++++++++------------------- src/commands/timelinecommands.h | 35 +++++-- src/docks/timelinedock.cpp | 14 +-- src/models/multitrackmodel.cpp | 8 +- src/models/multitrackmodel.h | 5 +- src/qml/views/timeline/Clip.qml | 5 +- src/qml/views/timeline/Track.qml | 3 +- src/qml/views/timeline/timeline.qml | 4 +- 8 files changed, 98 insertions(+), 131 deletions(-) diff --git a/src/commands/timelinecommands.cpp b/src/commands/timelinecommands.cpp index 75679919e1..14366791a0 100644 --- a/src/commands/timelinecommands.cpp +++ b/src/commands/timelinecommands.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -326,72 +326,44 @@ GroupCommand::GroupCommand(MultitrackModel &model, QUndoCommand *parent) { } -void GroupCommand::addToGroup(Mlt::Producer &clip) +void GroupCommand::addToGroup(int trackIndex, int clipIndex) { - QUuid id = MLT.ensureHasUuid(clip); - m_uuids.insert(id); - m_clips.append(clip); - if (clip.property_exists(kShotcutGroupProperty)) { - m_prevGroups.insert(id, clip.get_int(kShotcutGroupProperty)); + auto clipInfo = m_model.getClipInfo(trackIndex, clipIndex); + if (clipInfo && clipInfo->cut && !clipInfo->cut->is_blank()) { + ClipPosition position(trackIndex, clipIndex); + m_clips.append(position); + if (clipInfo->cut->property_exists(kShotcutGroupProperty)) { + m_prevGroups.insert(position, clipInfo->cut->get_int(kShotcutGroupProperty)); + } } } void GroupCommand::redo() { int groupNumber = getUniqueGroupNumber(m_model); - if (m_clips.size() > 0) { - // Use the original producer objects the first time. - setText(QObject::tr("Group %n clips", nullptr, m_clips.size())); - for (auto &clip : m_clips) { - clip.set(kShotcutGroupProperty, groupNumber); - } - m_clips.clear(); - } else { - QSet tmpUuids = m_uuids; - for (int trackIndex = 0; trackIndex < m_model.trackList().size() - && tmpUuids.size() > 0; trackIndex++) { - int i = m_model.trackList().at(trackIndex).mlt_index; - QScopedPointer track(m_model.tractor()->track(i)); - if (track) { - Mlt::Playlist playlist(*track); - for (int clipIndex = 0; clipIndex < playlist.count() && tmpUuids.size() > 0; clipIndex++) { - QScopedPointer info(playlist.clip_info(clipIndex)); - if (info && info->cut) { - QUuid id = MLT.uuid(*info->cut); - if (!id.isNull() && tmpUuids.contains(id)) { - info->cut->set(kShotcutGroupProperty, groupNumber); - tmpUuids.remove(id); - } - } - } - } + setText(QObject::tr("Group %n clips", nullptr, m_clips.size())); + for (auto &clip : m_clips) { + auto clipInfo = m_model.getClipInfo(clip.trackIndex, clip.clipIndex); + if (clipInfo && clipInfo->cut) { + clipInfo->cut->set(kShotcutGroupProperty, groupNumber); + QModelIndex modelIndex = m_model.index(clip.clipIndex, 0, m_model.index(clip.trackIndex)); + emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::GroupRole); } } } void GroupCommand::undo() { - QSet tmpUuids = m_uuids; - for (int trackIndex = 0; trackIndex < m_model.trackList().size() - && tmpUuids.size() > 0; trackIndex++) { - int i = m_model.trackList().at(trackIndex).mlt_index; - QScopedPointer track(m_model.tractor()->track(i)); - if (track) { - Mlt::Playlist playlist(*track); - for (int clipIndex = 0; clipIndex < playlist.count() && tmpUuids.size() > 0; clipIndex++) { - QScopedPointer info(playlist.clip_info(clipIndex)); - if (info && info->cut) { - QUuid id = MLT.uuid(*info->cut); - if (!id.isNull() && m_uuids.contains(id)) { - if (m_prevGroups.contains(id)) { - info->cut->set(kShotcutGroupProperty, m_prevGroups[id]); - } else { - info->cut->Mlt::Properties::clear(kShotcutGroupProperty); - } - tmpUuids.remove(id); - } - } + for (auto &clip : m_clips) { + auto clipInfo = m_model.getClipInfo(clip.trackIndex, clip.clipIndex); + if (clipInfo && clipInfo->cut) { + if (m_prevGroups.contains(clip)) { + clipInfo->cut->set(kShotcutGroupProperty, m_prevGroups[clip]); + } else { + clipInfo->cut->Mlt::Properties::clear(kShotcutGroupProperty); } + QModelIndex modelIndex = m_model.index(clip.clipIndex, 0, m_model.index(clip.trackIndex)); + emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::GroupRole); } } } @@ -402,67 +374,38 @@ UngroupCommand::UngroupCommand(MultitrackModel &model, QUndoCommand *parent) { } -void UngroupCommand::removeFromGroup(Mlt::Producer &clip) +void UngroupCommand::removeFromGroup(int trackIndex, int clipIndex) { - if (clip.property_exists(kShotcutGroupProperty)) { - QUuid id = MLT.ensureHasUuid(clip); - m_uuids.insert(id); - m_clips.append(clip); - m_prevGroups.insert(id, clip.get_int(kShotcutGroupProperty)); + auto clipInfo = m_model.getClipInfo(trackIndex, clipIndex); + if (clipInfo && clipInfo->cut) { + ClipPosition position(trackIndex, clipIndex); + if (clipInfo->cut->property_exists(kShotcutGroupProperty)) { + m_prevGroups.insert(position, clipInfo->cut->get_int(kShotcutGroupProperty)); + } } } void UngroupCommand::redo() { - if (m_clips.size() > 0) { - // Use the original producer objects the first time. - setText(QObject::tr("Ungroup %n clips", nullptr, m_clips.size())); - for (auto &clip : m_clips) { - clip.Mlt::Properties::clear(kShotcutGroupProperty); - } - m_clips.clear(); - } else { - QSet tmpUuids = m_uuids; - for (int trackIndex = 0; trackIndex < m_model.trackList().size() - && tmpUuids.size() > 0; trackIndex++) { - int i = m_model.trackList().at(trackIndex).mlt_index; - QScopedPointer track(m_model.tractor()->track(i)); - if (track) { - Mlt::Playlist playlist(*track); - for (int clipIndex = 0; clipIndex < playlist.count() && tmpUuids.size() > 0; clipIndex++) { - QScopedPointer info(playlist.clip_info(clipIndex)); - if (info && info->cut) { - QUuid id = MLT.uuid(*info->cut); - if (!id.isNull() && m_uuids.contains(id)) { - info->cut->Mlt::Properties::clear(kShotcutGroupProperty); - tmpUuids.remove(id); - } - } - } - } + setText(QObject::tr("Ungroup %n clips", nullptr, m_prevGroups.size())); + for (auto &clip : m_prevGroups.keys()) { + auto clipInfo = m_model.getClipInfo(clip.trackIndex, clip.clipIndex); + if (clipInfo && clipInfo->cut) { + clipInfo->cut->Mlt::Properties::clear(kShotcutGroupProperty); + QModelIndex modelIndex = m_model.index(clip.clipIndex, 0, m_model.index(clip.trackIndex)); + emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::GroupRole); } } } void UngroupCommand::undo() { - auto tmpPrevGroups = m_prevGroups; - for (int trackIndex = 0; trackIndex < m_model.trackList().size() - && tmpPrevGroups.size() > 0; trackIndex++) { - int i = m_model.trackList().at(trackIndex).mlt_index; - QScopedPointer track(m_model.tractor()->track(i)); - if (track) { - Mlt::Playlist playlist(*track); - for (int clipIndex = 0; clipIndex < playlist.count() && tmpPrevGroups.size() > 0; clipIndex++) { - QScopedPointer info(playlist.clip_info(clipIndex)); - if (info && info->cut) { - QUuid id = MLT.uuid(*info->cut); - if (!id.isNull() && m_prevGroups.contains(id)) { - info->cut->set(kShotcutGroupProperty, m_prevGroups[id]); - tmpPrevGroups.remove(id); - } - } - } + for (auto &clip : m_prevGroups.keys()) { + auto clipInfo = m_model.getClipInfo(clip.trackIndex, clip.clipIndex); + if (clipInfo && clipInfo->cut) { + clipInfo->cut->set(kShotcutGroupProperty, m_prevGroups[clip]); + QModelIndex modelIndex = m_model.index(clip.clipIndex, 0, m_model.index(clip.trackIndex)); + emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::GroupRole); } } } @@ -725,6 +668,8 @@ void MoveClipCommand::redo() if (clipInfo && clipInfo->cut) { clipInfo->cut->set(kShotcutGroupProperty, clip.get(kGroupProperty)); } + QModelIndex modelIndex = m_model.index(clipIndex, 0, m_model.index(toTrack)); + emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::GroupRole); } } } @@ -1863,7 +1808,8 @@ void DetachAudioCommand::redo() } m_undoHelper.recordAfterState(); QModelIndex modelIndex = m_model.makeIndex(m_trackIndex, m_clipIndex); - emit m_model.dataChanged(modelIndex, modelIndex, QVector() << MultitrackModel::AudioIndexRole); + emit m_model.dataChanged(modelIndex, modelIndex, + QVector() << MultitrackModel::AudioIndexRole << MultitrackModel::GroupRole); } } } @@ -1882,7 +1828,8 @@ void DetachAudioCommand::undo() m_model.overwrite(m_trackIndex, originalClip, m_position, true); QModelIndex modelIndex = m_model.makeIndex(m_trackIndex, m_clipIndex); emit m_model.dataChanged(modelIndex, modelIndex, - QVector() << MultitrackModel::AudioIndexRole << MultitrackModel::AudioLevelsRole); + QVector() << MultitrackModel::AudioIndexRole << MultitrackModel::AudioLevelsRole << + MultitrackModel::GroupRole); } ReplaceCommand::ReplaceCommand(MultitrackModel &model, int trackIndex, int clipIndex, diff --git a/src/commands/timelinecommands.h b/src/commands/timelinecommands.h index 16c62feda0..0e267e7760 100644 --- a/src/commands/timelinecommands.h +++ b/src/commands/timelinecommands.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,6 +46,26 @@ enum { UndoIdMoveClip }; +struct ClipPosition { + ClipPosition(int track, int clip) + { + trackIndex = track; + clipIndex = clip; + } + + bool operator < (const ClipPosition &rhs) const + { + if (trackIndex == rhs.trackIndex) { + return clipIndex < rhs.clipIndex; + } else { + return trackIndex < rhs.trackIndex; + } + } + + int trackIndex; + int clipIndex; +}; + class AppendCommand : public QUndoCommand { public: @@ -136,28 +156,25 @@ class GroupCommand : public QUndoCommand { public: GroupCommand(MultitrackModel &model, QUndoCommand *parent = 0); - void addToGroup(Mlt::Producer &clip); + void addToGroup(int trackIndex, int clipIndex); void redo(); void undo(); private: MultitrackModel &m_model; - QList m_clips; - QSet m_uuids; - QMap m_prevGroups; + QList m_clips; + QMap m_prevGroups; }; class UngroupCommand : public QUndoCommand { public: UngroupCommand(MultitrackModel &model, QUndoCommand *parent = 0); - void removeFromGroup(Mlt::Producer &clip); + void removeFromGroup(int trackIndex, int clipIndex); void redo(); void undo(); private: MultitrackModel &m_model; - QList m_clips; - QSet m_uuids; - QMap m_prevGroups; + QMap m_prevGroups; }; class NameTrackCommand : public QUndoCommand diff --git a/src/docks/timelinedock.cpp b/src/docks/timelinedock.cpp index d8a1603445..5979b804bc 100644 --- a/src/docks/timelinedock.cpp +++ b/src/docks/timelinedock.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1355,20 +1355,14 @@ void TimelineDock::setupActions() // First clip is in a group. Need to ungroup Timeline::UngroupCommand *ungroupCommand = new Timeline::UngroupCommand(m_model); foreach (auto point, selectedClips) { - auto clipInfo = m_model.getClipInfo(point.y(), point.x()); - if (!clipInfo->cut->is_blank()) { - ungroupCommand->removeFromGroup(*clipInfo->cut); - } + ungroupCommand->removeFromGroup(point.y(), point.x()); } MAIN.undoStack()->push(ungroupCommand); } else { - // First clip is not in a group - ungroup + // First clip is not in a group - group Timeline::GroupCommand *groupCommand = new Timeline::GroupCommand(m_model); foreach (auto point, selectedClips) { - auto clipInfo = m_model.getClipInfo(point.y(), point.x()); - if (!clipInfo->cut->is_blank()) { - groupCommand->addToGroup(*clipInfo->cut); - } + groupCommand->addToGroup(point.y(), point.x()); } MAIN.undoStack()->push(groupCommand); } diff --git a/src/models/multitrackmodel.cpp b/src/models/multitrackmodel.cpp index e3a96bf2fa..44acf561be 100644 --- a/src/models/multitrackmodel.cpp +++ b/src/models/multitrackmodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -199,6 +199,11 @@ QVariant MultitrackModel::data(const QModelIndex &index, int role) const return isFiltered(info->producer); case AudioIndexRole: return QString::fromLatin1(info->producer->get("audio_index")); + case GroupRole: + if (info->cut->property_exists(kShotcutGroupProperty)) + return info->cut->get_int(kShotcutGroupProperty); + else + return -1; default: break; } @@ -325,6 +330,7 @@ QHash MultitrackModel::roleNames() const roles[IsTopAudioRole] = "isTopAudio"; roles[IsBottomAudioRole] = "isBottomAudio"; roles[AudioIndexRole] = "audioIndex"; + roles[GroupRole] = "group"; return roles; } diff --git a/src/models/multitrackmodel.h b/src/models/multitrackmodel.h index 2d17e76fa9..5d72413078 100644 --- a/src/models/multitrackmodel.h +++ b/src/models/multitrackmodel.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -77,7 +77,8 @@ class MultitrackModel : public QAbstractItemModel IsBottomVideoRole,/// track only IsTopAudioRole, /// track only IsBottomAudioRole,/// track only - AudioIndexRole /// clip only + AudioIndexRole, /// clip only + GroupRole, /// clip only }; explicit MultitrackModel(QObject *parent = 0); diff --git a/src/qml/views/timeline/Clip.qml b/src/qml/views/timeline/Clip.qml index b4e2a70f87..0ad229c195 100644 --- a/src/qml/views/timeline/Clip.qml +++ b/src/qml/views/timeline/Clip.qml @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ Rectangle { property string hash: '' property double speed: 1 property string audioIndex: '' + property int group: -1 property bool isTrackMute: false property bool elided: (width < 15) || (x + width < tracksFlickable.contentX) || (x > tracksFlickable.contentX + tracksFlickable.width) || (y + height < 0) || (y > tracksFlickable.contentY + tracksFlickable.contentHeight) @@ -98,7 +99,7 @@ Rectangle { return 'image://thumbnail/' + hash + '/' + mltService + '/' + clipResource + '#' + time; } - border.color: (selected || Drag.active || trackIndex != originalTrackIndex) ? 'red' : 'black' + border.color: (selected || Drag.active || trackIndex != originalTrackIndex) ? group < 0 ? 'red' : 'white' : 'black' border.width: isBlank && !selected ? 0 : 1 clip: true Drag.active: mouseArea.drag.active diff --git a/src/qml/views/timeline/Track.qml b/src/qml/views/timeline/Track.qml index 71f0252e48..c578ee7446 100644 --- a/src/qml/views/timeline/Track.qml +++ b/src/qml/views/timeline/Track.qml @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2022 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -95,6 +95,7 @@ Rectangle { hash: typeof model.hash !== 'undefined' ? model.hash : 0 speed: typeof model.speed !== 'undefined' ? model.speed : 1 audioIndex: typeof model.audioindex !== 'undefined' ? model.audioIndex : 0 + group: typeof model.group !== 'undefined' ? model.group : -1 selected: Logic.selectionContains(timeline.selection, trackIndex, index) isTrackMute: trackRoot.isMute onClicked: (clip, mouse) => { diff --git a/src/qml/views/timeline/timeline.qml b/src/qml/views/timeline/timeline.qml index 754a0e3054..083dd0203b 100644 --- a/src/qml/views/timeline/timeline.qml +++ b/src/qml/views/timeline/timeline.qml @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2023 Meltytech, LLC + * Copyright (c) 2013-2024 Meltytech, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -569,7 +569,7 @@ Rectangle { width: clipN ? clipN.width : 0 height: track ? track.height : 0 color: 'transparent' - border.color: 'red' + border.color: (clipN && clipN.group < 0) ? 'red' : 'white' visible: clipN && !clipN.Drag.active && clipN.trackIndex === clipN.originalTrackIndex } }