Show offset labels for clip align

parent 26fdc09a
......@@ -44,6 +44,7 @@ ClipModel::ClipModel(const std::shared_ptr<TimelineModel> &parent, std::shared_p
, m_currentState(state)
, m_speed(speed)
, m_fakeTrack(-1)
, m_positionOffset(0)
{
m_producer->set("kdenlive:id", binClipId.toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
......@@ -726,3 +727,23 @@ void ClipModel::setSubPlaylistIndex(int index)
m_subPlaylistIndex = index;
}
void ClipModel::setOffset(int offset)
{
m_positionOffset = offset;
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::PositionOffsetRole});
}
}
void ClipModel::clearOffset()
{
if (m_positionOffset != 0) {
setOffset(0);
}
}
int ClipModel::getOffset() const
{
return m_positionOffset;
}
......@@ -195,6 +195,12 @@ protected:
bool isAudioOnly() const;
double getSpeed() const;
/** @brief Returns the clip offset (calculated in the model between 2 clips from same bin clip */
void setOffset(int offset);
/** @brief Clears the clip offset (calculated in the model between 2 clips from same bin clip */
void clearOffset();
int getOffset() const;
/*@brief This is a debug function to ensure the clip is in a valid state */
bool checkConsistency();
......@@ -220,6 +226,8 @@ protected:
// Fake track id, used when dragging in insert/overwrite mode
int m_fakeTrack;
int m_fakePosition;
// Temporary val to store offset between two clips with same bin id.
int m_positionOffset;
int m_subPlaylistIndex; // Tracks have two sub playlists to enable same track transitions, we store in which one this clip is.
};
......
......@@ -224,6 +224,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
roles[CanBeAudioRole] = "canBeAudio";
roles[CanBeVideoRole] = "canBeVideo";
roles[ReloadThumbRole] = "reloadThumb";
roles[PositionOffsetRole] = "positionOffset";
roles[ThumbsFormatRole] = "thumbsFormat";
roles[AudioRecordRole] = "audioRecord";
roles[TrackActiveRole] = "trackActive";
......@@ -333,6 +334,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
return clip->fadeOut();
case ReloadThumbRole:
return clip->forceThumbReload;
case PositionOffsetRole:
return clip->getOffset();
case SpeedRole:
return clip->getSpeed();
case GrabbedRole:
......
......@@ -2962,6 +2962,13 @@ bool TimelineModel::requestClearSelection(bool onDeletion)
}
if (isGroup(m_currentSelection)) {
if (m_groups->getType(m_currentSelection) == GroupType::Selection) {
// Reset offset display on clips
std::unordered_set<int> items = getCurrentSelection();
for (auto &id : items) {
if (isClip(id)) {
m_allClips[id]->clearOffset();
}
}
m_groups->destructGroupItem(m_currentSelection);
}
} else {
......@@ -3047,6 +3054,34 @@ bool TimelineModel::requestSetSelection(const std::unordered_set<int> &ids)
} else {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (ids.size() == 2) {
// Check if we selected 2 clips from the same master
QList<int> pairIds;
for(auto &id : roots) {
if (isClip(id)) {
pairIds << id;
}
}
if (pairIds.size() == 2 && getClipBinId(pairIds.at(0)) == getClipBinId(pairIds.at(1))) {
// Check if they have same bin id
// Both clips have same bin ID, display offset
int pos1 = getClipPosition(pairIds.at(0));
int pos2 = getClipPosition(pairIds.at(1));
if (pos2 > pos1) {
int offset = pos2 - pos1 - getClipIn(pairIds.at(1)) - getClipIn(pairIds.at(0));
if (offset != 0) {
m_allClips[pairIds.at(1)]->setOffset(offset);
m_allClips[pairIds.at(0)]->setOffset(-offset);
}
} else {
int offset = pos1 - pos2 - getClipIn(pairIds.at(0)) - getClipIn(pairIds.at(1));
if (offset != 0) {
m_allClips[pairIds.at(0)]->setOffset(offset);
m_allClips[pairIds.at(1)]->setOffset(-offset);
}
}
}
}
result = (m_currentSelection = m_groups->groupItems(ids, undo, redo, GroupType::Selection)) >= 0;
Q_ASSERT(m_currentSelection >= 0);
}
......
......@@ -147,6 +147,7 @@ public:
FileHashRole, /// clip only
SpeedRole, /// clip only
ReloadThumbRole, /// clip only
PositionOffsetRole,/// clip only
ItemATrack, /// composition only
ItemIdRole,
ThumbsFormatRole, /// track only
......
......@@ -49,6 +49,7 @@ Rectangle {
property int fadeIn: 0
property int fadeOut: 0
property int binId: 0
property int positionOffset: 0
property var parentTrack
property int trackIndex //Index in track repeater
property int clipId //Id of the clip in the model
......@@ -84,6 +85,11 @@ Rectangle {
}
}
function clearAndMove(offset) {
controller.requestClearSelection()
controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true);
}
onInPointChanged: {
if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
thumbsLoader.item.reload()
......@@ -326,6 +332,52 @@ Rectangle {
styleColor: 'black'
}
}
Rectangle {
// Offset info
id: offsetRect
color: 'darkgreen'
width: offsetLabel.width + radius
height: offsetLabel.height
radius: height/3
x: labelRect.width + 4
visible: labelRect.visible && positionOffset != 0
MouseArea {
id: offsetArea
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
clearAndMove(positionOffset)
}
ToolTip {
visible: offsetArea.containsMouse
font.pixelSize: root.baseUnit
delay: 1000
timeout: 5000
background: Rectangle {
color: activePalette.alternateBase
border.color: activePalette.light
}
contentItem: Label {
color: activePalette.text
text: i18n('Offset') + (positionOffset < 0 ? ( ': -' + timeline.timecode(-positionOffset)) : ': ' + timeline.timecode(positionOffset))
}
}
Text {
id: offsetLabel
text: positionOffset
font.pixelSize: root.baseUnit * 1.2
anchors {
horizontalCenter: parent.horizontalCenter
topMargin: 1
leftMargin: 1
}
color: 'white'
style: Text.Outline
styleColor: 'black'
}
}
}
Rectangle {
// effects
id: effectsRect
......@@ -486,7 +538,7 @@ Rectangle {
}
contentItem: Label {
color: activePalette.text
text: 'Click to add composition'
text: i18n('Click to add composition')
}
}
}
......@@ -536,7 +588,7 @@ Rectangle {
}
contentItem: Label {
color: activePalette.text
text: 'Click to add composition'
text: i18n('Click to add composition')
}
}
}
......
......@@ -104,6 +104,12 @@ Column{
value: model.fadeIn
when: loader.status == Loader.Ready && isClip(model.clipType)
}
Binding {
target: loader.item
property: "positionOffset"
value: model.positionOffset
when: loader.status == Loader.Ready && isClip(model.clipType)
}
Binding {
target: loader.item
property: "effectNames"
......
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