Various mix and transition fixes.

Fixes #851
parent 1b506986
<!DOCTYPE kpartgui>
<transition tag="luma" id="luma" type="hidden">
<transition tag="luma" id="luma">
<name>Luma</name>
<description>Applies a stationary transition between the current and next frames.</description>
<author>Dan Dennedy</author>
......
......@@ -73,6 +73,8 @@ AssetPanel::AssetPanel(QWidget *parent)
connect(m_switchCompoButton, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, [&]() {
if (m_transitionWidget->stackOwner().first == ObjectType::TimelineComposition) {
emit switchCurrentComposition(m_transitionWidget->stackOwner().second, m_switchCompoButton->currentData().toString());
} else if (m_mixWidget->isVisible()) {
emit switchCurrentComposition(m_mixWidget->stackOwner().second, m_switchCompoButton->currentData().toString());
}
});
m_switchCompoButton->setToolTip(i18n("Change composition type"));
......@@ -146,6 +148,7 @@ AssetPanel::AssetPanel(QWidget *parent)
connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::reloadEffect, this, &AssetPanel::reloadEffect);
connect(m_transitionWidget, &TransitionStackView::seekToTransPos, this, &AssetPanel::seekToPos);
connect(m_mixWidget, &MixStackView::seekToTransPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::updateEnabledState, this, [this]() { m_enableStackButton->setActive(m_effectStackWidget->isStackEnabled()); });
}
......@@ -158,8 +161,7 @@ void AssetPanel::showTransition(int tid, const std::shared_ptr<AssetParameterMod
return;
}
clear();
QString transitionId = transitionModel->getAssetId();
m_switchCompoButton->setCurrentIndex(m_switchCompoButton->findData(transitionId));
m_switchCompoButton->setCurrentIndex(m_switchCompoButton->findData(transitionModel->getAssetId()));
m_switchAction->setVisible(true);
m_titleAction->setVisible(false);
m_assetTitle->clear();
......@@ -181,12 +183,13 @@ void AssetPanel::showMix(int cid, const std::shared_ptr<AssetParameterModel> &tr
return;
}
clear();
m_switchAction->setVisible(false);
m_switchAction->setVisible(true);
m_titleAction->setVisible(false);
m_assetTitle->clear();
m_mixWidget->setVisible(true);
m_timelineButton->setVisible(false);
m_enableStackButton->setVisible(false);
m_switchCompoButton->setCurrentIndex(m_switchCompoButton->findData(transitionModel->getAssetId()));
m_mixWidget->setModel(transitionModel, QSize(), true);
}
......@@ -269,11 +272,16 @@ void AssetPanel::clearAssetPanel(int itemId)
ObjectId id = m_effectStackWidget->stackOwner();
if (id.first == ObjectType::TimelineClip && id.second == itemId) {
clear();
} else {
id = m_transitionWidget->stackOwner();
if (id.first == ObjectType::TimelineComposition && id.second == itemId) {
clear();
}
return;
}
id = m_transitionWidget->stackOwner();
if (id.first == ObjectType::TimelineComposition && id.second == itemId) {
clear();
return;
}
id = m_mixWidget->stackOwner();
if (id.first == ObjectType::TimelineMix && id.second == itemId) {
clear();
}
}
......@@ -303,6 +311,7 @@ void AssetPanel::updatePalette()
setStyleSheet(styleSheet);
m_transitionWidget->setStyleSheet(styleSheet);
m_effectStackWidget->setStyleSheet(styleSheet);
m_mixWidget->setStyleSheet(styleSheet);
}
// static
......@@ -387,22 +396,25 @@ void AssetPanel::processSplitEffect(bool enable)
void AssetPanel::showKeyframes(bool enable)
{
if (m_transitionWidget->isVisible()) {
pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable);
} else {
if (m_effectStackWidget->isVisible()) {
pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable);
} else if (m_transitionWidget->isVisible()) {
pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable);
}
}
ObjectId AssetPanel::effectStackOwner()
{
if (m_effectStackWidget->isVisible()) {
return m_effectStackWidget->stackOwner();
}
if (m_transitionWidget->isVisible()) {
return m_transitionWidget->stackOwner();
}
if (!m_effectStackWidget->isVisible()) {
return ObjectId(ObjectType::NoItem, -1);
if (m_mixWidget->isVisible()) {
return m_mixWidget->stackOwner();
}
return m_effectStackWidget->stackOwner();
return ObjectId(ObjectType::NoItem, -1);
}
bool AssetPanel::addEffect(const QString &effectId)
......
......@@ -452,6 +452,13 @@ int Core::getItemPosition(const ObjectId &id)
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second);
}
break;
case ObjectType::TimelineMix:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMixInOut(id.second).first;
} else {
qDebug()<<"// ERROR QUERYING NON CLIP PROPERTIES\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
}
break;
case ObjectType::BinClip:
case ObjectType::TimelineTrack:
case ObjectType::Master:
......@@ -477,6 +484,7 @@ int Core::getItemIn(const ObjectId &id)
qDebug()<<"// ERROR QUERYING NON CLIP PROPERTIES\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
}
break;
case ObjectType::TimelineMix:
case ObjectType::TimelineComposition:
case ObjectType::BinClip:
case ObjectType::TimelineTrack:
......@@ -536,6 +544,14 @@ int Core::getItemDuration(const ObjectId &id)
case ObjectType::TimelineTrack:
case ObjectType::Master:
return m_mainWindow->getCurrentTimeline()->controller()->duration() - 1;
case ObjectType::TimelineMix:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
std::pair<int, int> mixInOut = m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMixInOut(id.second);
return (mixInOut.second - mixInOut.first);
} else {
qDebug()<<"// ERROR QUERYING NON CLIP PROPERTIES\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
}
break;
default:
qDebug() << "ERROR: unhandled object type";
}
......@@ -548,6 +564,7 @@ int Core::getItemTrack(const ObjectId &id)
switch (id.first) {
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
case ObjectType::TimelineMix:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second);
break;
default:
......@@ -561,6 +578,7 @@ void Core::refreshProjectItem(const ObjectId &id)
if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
switch (id.first) {
case ObjectType::TimelineClip:
case ObjectType::TimelineMix:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
}
......
......@@ -388,6 +388,7 @@ void MainWindow::init()
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
case ObjectType::Master:
case ObjectType::TimelineMix:
m_projectMonitor->requestSeek(pos);
break;
case ObjectType::BinClip:
......
......@@ -5122,6 +5122,29 @@ std::unordered_set<int> TimelineModel::getAllTracksIds() const
void TimelineModel::switchComposition(int cid, const QString &compoId)
{
Fun undo = []() {return true; };
Fun redo = []() { return true; };
if (isClip(cid)) {
// We are working on a mix
requestClearSelection(true);
int tid = getClipTrackId(cid);
MixInfo mixData = getTrackById_const(tid)->getMixInfo(cid).first;
getTrackById(tid)->switchMix(cid, compoId, undo, redo);
Fun local_update = [cid, mixData, this]() {
requestMixSelection(cid);
int in = mixData.secondClipInOut.first;
int out = mixData.firstClipInOut.second;
emit invalidateZone(in, out);
checkRefresh(in, out);
return true;
};
PUSH_LAMBDA(local_update, redo);
PUSH_FRONT_LAMBDA(local_update, undo);
if (redo()) {
pCore->pushUndo(undo, redo, i18n("Change composition"));
}
return;
}
Q_ASSERT(isComposition(cid));
std::shared_ptr<CompositionModel> compo = m_allCompositions.at(cid);
int currentPos = compo->getPosition();
......@@ -5129,8 +5152,6 @@ void TimelineModel::switchComposition(int cid, const QString &compoId)
int currentTrack = compo->getCurrentTrackId();
int a_track = compo->getATrack();
int forcedTrack = compo->getForcedTrack();
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// Clear selection
requestClearSelection(true);
if (m_groups->isInGroup(cid)) {
......@@ -5171,13 +5192,13 @@ void TimelineModel::plantMix(int tid, Mlt::Transition *t)
bool TimelineModel::resizeStartMix(int cid, int duration, bool singleResize)
{
Q_ASSERT(isClip(cid));
int tid = m_allClips[cid]->getCurrentTrackId();
int tid = m_allClips.at(cid)->getCurrentTrackId();
if (tid > -1) {
std::pair<MixInfo, MixInfo> mixData = getTrackById_const(tid)->getMixInfo(cid);
if (mixData.first.firstClipId > -1) {
int clipToResize = mixData.first.firstClipId;
Q_ASSERT(isClip(clipToResize));
int updatedDuration = m_allClips[cid]->getPosition() + duration - m_allClips[clipToResize]->getPosition();
int updatedDuration = m_allClips.at(cid)->getPosition() + duration - m_allClips[clipToResize]->getPosition();
int result = requestItemResize(clipToResize, updatedDuration, true, true, 0, singleResize);
return result > -1;
}
......@@ -5185,6 +5206,19 @@ bool TimelineModel::resizeStartMix(int cid, int duration, bool singleResize)
return false;
}
std::pair<int, int> TimelineModel::getMixInOut(int cid) const
{
Q_ASSERT(isClip(cid));
int tid = m_allClips.at(cid)->getCurrentTrackId();
if (tid > -1) {
MixInfo mixData = getTrackById_const(tid)->getMixInfo(cid).first;
if (mixData.firstClipId > -1) {
return {mixData.secondClipInOut.first, mixData.firstClipInOut.second};
}
}
return {-1,-1};
}
void TimelineModel::setSubModel(std::shared_ptr<SubtitleModel> model)
{
m_subtitleModel = std::move(model);
......
......@@ -217,6 +217,7 @@ public:
Q_INVOKABLE int getCompositionPosition(int compoId) const;
int getSubtitlePosition(int subId) const;
int getCompositionPlaytime(int compoId) const;
std::pair<int, int> getMixInOut(int cid) const;
/* Returns an item position, item can be clip or composition */
Q_INVOKABLE int getItemPosition(int itemId) const;
......
......@@ -1292,6 +1292,13 @@ Fun TrackModel::requestCompositionDeletion_lambda(int compoId, bool updateView,
return [compoId, old_in, old_out, updateView, finalMove, this]() {
if (isLocked()) return false;
int old_clip_index = getRowfromComposition(compoId);
if (finalMove && m_allCompositions[compoId]->selected) {
m_allCompositions[compoId]->selected = false;
if (auto ptr = m_parent.lock()) {
// item was selected, unselect
ptr->requestClearSelection(true);
}
}
auto ptr = m_parent.lock();
if (updateView) {
ptr->_beginRemoveRows(ptr->makeTrackIndexFromID(getId()), old_clip_index, old_clip_index);
......@@ -1546,6 +1553,8 @@ bool TrackModel::requestRemoveMix(std::pair<int, int> clipIds, Fun &undo, Fun &r
}
if (result) {
PUSH_LAMBDA(local_redo, redo);
QString assetId = m_sameCompositions[clipIds.second]->getAssetId();
QVector<QPair<QString, QVariant>> params = m_sameCompositions[clipIds.second]->getAllParameters();
Fun replay = [this, clipIds, secondInPos, clipHasEndMix, src_track]() {
if (src_track == 1 && !clipHasEndMix) {
// Revert clip to playlist 0 since it has no mix
......@@ -1570,7 +1579,7 @@ bool TrackModel::requestRemoveMix(std::pair<int, int> clipIds, Fun &undo, Fun &r
return true;
};
replay();
Fun reverse = [this, clipIds, mixDuration, mixPosition, mixCutPos, secondInPos, clipHasEndMix, src_track]() {
Fun reverse = [this, clipIds, assetId, params, mixDuration, mixPosition, mixCutPos, secondInPos, clipHasEndMix, src_track]() {
// First restore correct playlist
if (src_track == 1 && !clipHasEndMix) {
// Revert clip to playlist 1
......@@ -1582,31 +1591,24 @@ bool TrackModel::requestRemoveMix(std::pair<int, int> clipIds, Fun &undo, Fun &r
movedClip->setMixDuration(mixDuration, mixCutPos);
// Insert mix transition
QString assetName;
std::unique_ptr<Mlt::Transition> t;
if (isAudioTrack()) {
t.reset(new Mlt::Transition(*ptr->getProfile(), "mix"));
t->set_in_and_out(mixPosition, mixPosition + mixDuration);
t->set("kdenlive:mixcut", mixCutPos);
t->set("start", -1);
t->set("accepts_blanks", 1);
if (src_track == 0) {
t->set("reverse", 1);
}
m_track->plant_transition(*t.get(), 0, 1);
assetName = QStringLiteral("mix");
} else {
t.reset(new Mlt::Transition(*ptr->getProfile(), "luma"));
t->set_in_and_out(mixPosition, mixPosition + mixDuration);
t->set("kdenlive:mixcut", mixCutPos);
if (src_track == 0) {
t->set("reverse", 1);
std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(assetId);
t->set_in_and_out(mixPosition, mixPosition + mixDuration);
t->set("kdenlive:mixcut", mixCutPos);
t->set("kdenlive_id", assetId.toUtf8().constData());
m_track->plant_transition(*t.get(), 0, 1);
QDomElement xml = TransitionsRepository::get()->getXml(assetId);
QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
for (int i = 0; i < xmlParams.count(); ++i) {
QDomElement currentParameter = xmlParams.item(i).toElement();
QString paramName = currentParameter.attribute(QStringLiteral("name"));
for (const auto &p : qAsConst(params)) {
if (p.first == paramName) {
currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
break;
}
}
m_track->plant_transition(*t.get(), 0, 1);
assetName = QStringLiteral("luma");
}
QDomElement xml = TransitionsRepository::get()->getXml(assetName);
std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(t), xml, assetName, {ObjectType::TimelineMix, clipIds.second}, QString()));
std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(t), xml, assetId, {ObjectType::TimelineMix, clipIds.second}, QString()));
m_sameCompositions[clipIds.second] = asset;
m_mixList.insert(clipIds.first, clipIds.second);
QModelIndex ix2 = ptr->makeClipIndexFromID(clipIds.second);
......@@ -1837,6 +1839,7 @@ bool TrackModel::requestClipMix(std::pair<int, int> clipIds, int mixDuration, bo
t.reset(new Mlt::Transition(*ptr->getProfile(), "luma"));
t->set_in_and_out(mixPosition, mixPosition + mixDuration);
t->set("kdenlive:mixcut", secondClipCut);
t->set("kdenlive_id", "luma");
if (dest_track == 0) {
t->set("reverse", 1);
}
......@@ -2031,6 +2034,7 @@ bool TrackModel::createMix(MixInfo info, bool isAudio)
} else {
t.reset(new Mlt::Transition(*ptr->getProfile(), "luma"));
t->set_in_and_out(in, out);
t->set("kdenlive_id", "luma");
if (reverse) {
t->set("reverse", 1);
}
......@@ -2072,6 +2076,7 @@ bool TrackModel::createMix(std::pair<int, int> clipIds, std::pair<int, int> mixD
assetName = QStringLiteral("mix");
} else {
t.reset(new Mlt::Transition(*ptr->getProfile(), "luma"));
t->set("kdenlive_id", "luma");
t->set_in_and_out(mixData.first, mixData.first + mixData.second);
if (reverse) {
t->set("reverse", 1);
......@@ -2184,7 +2189,10 @@ bool TrackModel::loadMix(Mlt::Transition *t)
if (cid1 < 0 || cid2 < 0) {
return false;
}
const QString assetId(t->get("mlt_service"));
QString assetId(t->get("kdenlive_id"));
if (assetId.isEmpty()) {
assetId = QString(t->get("mlt_service"));
}
std::unique_ptr<Mlt::Transition>tr(t);
QDomElement xml = TransitionsRepository::get()->getXml(assetId);
// Paste parameters from existing mix
......@@ -2226,3 +2234,57 @@ bool TrackModel::reAssignEndMix(int currentId, int newId)
m_mixList.insert(newId, mixedClip);
return true;
}
void TrackModel::switchMix(int cid, const QString composition, Fun &undo, Fun &redo)
{
// First remove exisiting mix
// lock MLT playlist so that we don't end up with invalid frames in monitor
const QString currentAsset = m_sameCompositions[cid]->getAssetId();
Fun local_redo = [this, cid, composition]() {
m_playlists[0].lock();
m_playlists[1].lock();
Mlt::Transition &transition = *static_cast<Mlt::Transition*>(m_sameCompositions[cid]->getAsset());
int in = transition.get_in();
int out = transition.get_out();
QScopedPointer<Mlt::Field> field(m_track->field());
field->lock();
field->disconnect_service(transition);
field->unlock();
m_sameCompositions.erase(cid);
if (auto ptr = m_parent.lock()) {
std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(composition);
t->set_in_and_out(in, out);
m_track->plant_transition(*t.get(), 0, 1);
QDomElement xml = TransitionsRepository::get()->getXml(composition);
std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(t), xml, composition, {ObjectType::TimelineMix, cid}, QString()));
m_sameCompositions[cid] = asset;
}
m_playlists[0].unlock();
m_playlists[1].unlock();
return true;
};
Fun local_undo = [this, cid, currentAsset]() {
m_playlists[0].lock();
m_playlists[1].lock();
Mlt::Transition &transition = *static_cast<Mlt::Transition*>(m_sameCompositions[cid]->getAsset());
int in = transition.get_in();
int out = transition.get_out();
QScopedPointer<Mlt::Field> field(m_track->field());
field->lock();
field->disconnect_service(transition);
field->unlock();
m_sameCompositions.erase(cid);
if (auto ptr = m_parent.lock()) {
std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(currentAsset);
t->set_in_and_out(in, out);
m_track->plant_transition(*t.get(), 0, 1);
QDomElement xml = TransitionsRepository::get()->getXml(currentAsset);
std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(t), xml, currentAsset, {ObjectType::TimelineMix, cid}, QString()));
m_sameCompositions[cid] = asset;
}
m_playlists[0].unlock();
m_playlists[1].unlock();
return true;
};
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
......@@ -134,6 +134,7 @@ public:
bool createMix(MixInfo info, bool isAudio);
/** @brief Change id of first clip in a mix (in case of clip cut) */
bool reAssignEndMix(int currentId, int newId);
void switchMix(int cid, const QString composition, Fun &undo, Fun &redo);
/** @brief Ensure we don't have unsynced mixes in the playlist (mixes without owner clip) */
void syncronizeMixes(bool finalMove);
/** @brief Switch a clip from one playlist to the other */
......
......@@ -489,6 +489,8 @@ void TimelineController::deleteSelectedClips()
// Check if a mix is selected
if (m_model->m_selectedMix > -1 && m_model->isClip(m_model->m_selectedMix)) {
m_model->removeMix(m_model->m_selectedMix);
m_model->clearAssetView(m_model->m_selectedMix);
m_model->requestClearSelection(true);
}
return;
}
......
......@@ -40,6 +40,7 @@ MixStackView::MixStackView(QWidget *parent)
void MixStackView::setModel(const std::shared_ptr<AssetParameterModel> &model, QSize frameSize, bool addSpacer)
{
AssetParameterView::setModel(model, frameSize, addSpacer);
m_model->setActive(true);
auto kfr = model->getKeyframeModel();
if (kfr) {
connect(kfr.get(), &KeyframeModelList::modelChanged, this, &AssetParameterView::slotRefresh);
......@@ -58,6 +59,7 @@ void MixStackView::setModel(const std::shared_ptr<AssetParameterModel> &model, Q
void MixStackView::unsetModel()
{
if (m_model) {
m_model->setActive(false);
auto kfr = m_model->getKeyframeModel();
if (kfr) {
disconnect(kfr.get(), &KeyframeModelList::modelChanged, this, &AssetParameterView::slotRefresh);
......
......@@ -66,6 +66,7 @@ void TransitionStackView::setModel(const std::shared_ptr<AssetParameterModel> &m
auto *lay = new QHBoxLayout;
m_trackBox = new QComboBox(this);
AssetParameterView::setModel(model, frameSize, addSpacer);
model->setActive(true);
refreshTracks();
QLabel *title = new QLabel(i18n("Composition track:"), this);
lay->addWidget(title);
......@@ -91,6 +92,7 @@ void TransitionStackView::setModel(const std::shared_ptr<AssetParameterModel> &m
void TransitionStackView::unsetModel()
{
if (m_model) {
m_model->setActive(false);
disconnect(m_model.get(), &AssetParameterModel::compositionTrackChanged, this, &TransitionStackView::checkCompoTrack);
auto kfr = m_model->getKeyframeModel();
if (kfr) {
......
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