Fix most issues with fades, integrate in undo/redo

parent cdf47845
......@@ -126,6 +126,36 @@ void AssetParameterModel::prepareKeyframes()
}
}
void AssetParameterModel::setParameter(const QString &name, const int value, bool update)
{
Q_ASSERT(m_asset->is_valid());
m_asset->set(name.toLatin1().constData(), value);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
if (update) {
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
qDebug()<<"// Warning, SOX effect, need unplug/replug";
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
} else {
emit modelChanged();
}
// Update timeline view if necessary
pCore->updateItemModel(m_ownerId, m_assetId);
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
}
void AssetParameterModel::setParameter(const QString &name, const QString &value, bool update)
{
Q_ASSERT(m_asset->is_valid());
......
......@@ -99,6 +99,7 @@ public:
/* @brief Set the parameter with given name to the given value
*/
Q_INVOKABLE void setParameter(const QString &name, const QString &value, bool update = true);
void setParameter(const QString &name, const int value, bool update = true);
Q_INVOKABLE void setParameter(const QString &name, double &value);
/* @brief Return all the parameters as pairs (parameter name, parameter value) */
......
......@@ -143,10 +143,100 @@ void EffectStackModel::appendEffect(const QString &effectId)
fadeOuts << effect->getId();
}
QString effectName = EffectsRepository::get()->getName(effectId);
Fun update = [this]() {
//TODO: only update if effect is fade or keyframe
pCore->updateItemKeyframes(m_ownerId);
return true;
};
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
PUSH_UNDO(undo, redo, i18n("Add effect %1", effectName));
}
// TODO: integrate in undo/redo, change active effect
pCore->updateItemKeyframes(m_ownerId);
}
bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int newIn, int duration, bool hasAudio, bool audioOnly, Fun &undo, Fun &redo, bool logUndo)
{
const int fadeInDuration = getFadePosition(true);
const int fadeOutDuration = getFadePosition(false);
QList<QModelIndex> indexes;
auto ptr = m_service.lock();
int out = newIn + duration;
for (int i = 0; i < rootItem->childCount(); ++i) {
if (fadeInDuration > 0 && fadeIns.contains(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId())) {
std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
int oldEffectIn = qMax(0, effect->filter().get_int("in"));
int oldEffectOut = effect->filter().get_int("out");
int effectDuration = qMin(oldEffectOut - oldEffectIn, duration);
indexes << getIndexFromItem(effect);
if (!adjustFromEnd && oldIn != newIn) {
// Clip start was resized, adjust effect in / out
Fun operation = [this, effect, newIn, effectDuration]() {
effect->setParameter(QStringLiteral("in"), newIn, false);
effect->setParameter(QStringLiteral("out"), effectDuration, false);
return true;
};
operation();
Fun reverse = [this, effect, oldEffectIn, oldEffectOut]() {
effect->setParameter(QStringLiteral("in"), oldEffectIn, false);
effect->setParameter(QStringLiteral("out"), oldEffectOut, false);
return true;
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
} else if (effectDuration < oldEffectOut - oldEffectIn || (logUndo && effect->filter().get_int("_refout") > 0)) {
// Clip length changed, shorter than effect length so resize
int referenceEffectOut = effect->filter().get_int("_refout");
if (referenceEffectOut <= 0) {
referenceEffectOut = oldEffectOut;
effect->filter().set("_refout", referenceEffectOut);
}
Fun operation = [this, effect, oldEffectIn, effectDuration]() {
effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, false);
return true;
};
if (operation() && logUndo) {
Fun reverse = [this, effect, referenceEffectOut]() {
effect->setParameter(QStringLiteral("out"), referenceEffectOut, false);
effect->filter().set("_refout", (char*) nullptr);
return true;
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
}
}
} else if (fadeOutDuration > 0 && fadeOuts.contains(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId())) {
std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
int effectDuration = qMin(fadeOutDuration, duration);
int newIn = out - effectDuration;
int oldIn = effect->filter().get_int("in");
int oldOut = effect->filter().get_int("out");
int referenceEffectIn = effect->filter().get_int("_refin");
if (referenceEffectIn <= 0) {
referenceEffectIn = oldIn;
effect->filter().set("_refin", referenceEffectIn);
}
Fun operation = [this, effect, newIn, out]() {
effect->setParameter(QStringLiteral("in"), newIn, false);
effect->setParameter(QStringLiteral("out"), out, false);
return true;
};
if (operation() && logUndo) {
Fun reverse = [this, effect, referenceEffectIn, oldOut]() {
effect->setParameter(QStringLiteral("in"), referenceEffectIn, false);
effect->setParameter(QStringLiteral("out"), oldOut, false);
effect->filter().set("_refin", (char*) nullptr);
return true;
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
}
indexes << getIndexFromItem(effect);
}
}
if (!indexes.isEmpty()) {
emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
}
return true;
}
bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade)
......@@ -171,12 +261,14 @@ bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audio
if (fadeIns.contains(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId())) {
std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
effect->filter().set("in", in);
duration = qMin(pCore->getItemDuration(m_ownerId), duration);
effect->filter().set("out", in + duration);
indexes << getIndexFromItem(effect);
}
}
if (!indexes.isEmpty()) {
emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
pCore->updateItemKeyframes(m_ownerId);
}
} else {
// Fade out
......@@ -199,12 +291,14 @@ bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audio
if (fadeOuts.contains(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId())) {
std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
effect->filter().set("out", out);
duration = qMin(pCore->getItemDuration(m_ownerId), duration);
effect->filter().set("in", out - duration);
indexes << getIndexFromItem(effect);
}
}
if (!indexes.isEmpty()) {
emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
pCore->updateItemKeyframes(m_ownerId);
}
}
return true;
......
......@@ -82,6 +82,7 @@ public:
int getActiveEffect() const;
/* @brief Adjust an effect duration (useful for fades) */
bool adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade);
bool adjustStackLength(bool adjustFromEnd, int oldIn, int newIn, int duration, bool hasAudio, bool audioOnly, Fun &undo, Fun &redo, bool logUndo);
void slotCreateGroup(std::shared_ptr<EffectItemModel> childEffect);
......
......@@ -87,19 +87,20 @@ ClipModel::~ClipModel()
{
}
bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo)
bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo)
{
QWriteLocker locker(&m_lock);
qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" << m_producer->get_length();
//qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" << m_producer->get_length();
if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) {
return false;
}
int delta = getPlaytime() - size;
if (delta == 0) {
return 0;
}
int in = m_producer->get_in();
int out = m_producer->get_out();
int old_in = in, old_out = out;
int fadeInDuration = m_effectStack->getFadePosition(true);
int fadeOutDuration = m_effectStack->getFadePosition(false);
// check if there is enough space on the chosen side
if (!right && in + delta < 0 && !m_endlessResize) {
return false;
......@@ -112,32 +113,26 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo)
} else {
in += delta;
}
qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
//qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
std::function<bool(void)> track_operation = []() { return true; };
std::function<bool(void)> track_reverse = []() { return true; };
int outPoint = out;
int inPoint = in;
if (m_endlessResize) {
outPoint = out - in;
inPoint = 0;
}
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, in, out, right);
track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right);
} else {
qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
Q_ASSERT(false);
}
}
Fun operation = [this, in, out, right, old_in, fadeInDuration, fadeOutDuration, track_operation]() {
Fun operation = [this, inPoint, outPoint, track_operation]() {
if (track_operation()) {
int outPoint = in < 0 ? out - in : out;
if (outPoint >= m_producer->get_length()) {
m_producer->parent().set("length", outPoint + 1);
m_producer->set("length", outPoint + 1);
}
m_producer->set_in_and_out(in < 0 ? 0 : in, outPoint);
if (right) {
if (fadeOutDuration > 0) {
adjustEffectLength(QStringLiteral("fadeout"), fadeOutDuration);
}
} else if (fadeInDuration > 0) {
adjustEffectLength(QStringLiteral("fadein"), fadeInDuration - in);
}
m_producer->set_in_and_out(inPoint, outPoint);
return true;
}
return false;
......@@ -155,6 +150,7 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo)
}
return false;
};
adjustEffectLength(right, old_in, inPoint, m_producer->get_playtime(), reverse, operation, logUndo);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
......@@ -243,10 +239,26 @@ bool ClipModel::removeFade(bool fromStart)
return true;
}
bool ClipModel::adjustEffectLength(const QString &effectName, int duration)
bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int duration, Fun &undo, Fun &redo, bool logUndo)
{
READ_LOCK();
m_effectStack->adjustStackLength(adjustFromEnd, oldIn, newIn, duration, hasAudio(), !isAudioOnly(), undo, redo, logUndo);
return true;
}
bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo)
{
READ_LOCK();
m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein"), hasAudio(), !isAudioOnly());
Fun operation = [this, duration, effectName]() {
return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein"), hasAudio(), !isAudioOnly());
};
if (operation() && originalDuration > 0) {
Fun reverse = [this, originalDuration, effectName]() {
return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein"), hasAudio(), !isAudioOnly());
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
}
return true;
}
......
......@@ -98,7 +98,9 @@ public:
bool copyEffect(std::shared_ptr<EffectStackModel> stackModel, int rowId);
bool importEffects(std::shared_ptr<EffectStackModel> stackModel);
bool removeFade(bool fromStart);
bool adjustEffectLength(const QString &effectName, int duration);
/** @brief Adjust effects duration. Should be called after each resize / cut operation */
bool adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int duration, Fun &undo, Fun &redo, bool logUndo);
bool adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo);
void passTimelineProperties(std::shared_ptr <ClipModel> other);
KeyframeModel *getKeyframeModel();
......@@ -123,7 +125,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 requestResize(int size, bool right, Fun &undo, Fun &redo) override;
bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) override;
/* @brief This function change the global (timeline-wise) enabled state of the effects
*/
......
......@@ -57,7 +57,7 @@ int CompositionModel::construct(const std::weak_ptr<TimelineModel> &parent, cons
return id;
}
bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo)
bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool /*logUndo*/)
{
QWriteLocker locker(&m_lock);
if (size <= 0) {
......
......@@ -95,7 +95,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 requestResize(int size, bool right, Fun &undo, Fun &redo) override;
bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) override;
private:
int a_track;
......
......@@ -90,7 +90,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
*/
virtual bool requestResize(int size, bool right, Fun &undo, Fun &redo) = 0;
virtual bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) = 0;
/* Updates the stored position of the item
This function is meant to be called by the trackmodel, not directly by the user.
......
......@@ -758,7 +758,7 @@ int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logU
#endif
QWriteLocker locker(&m_lock);
Q_ASSERT(isClip(itemId) || isComposition(itemId));
if (size <= 0) return false;
if (size <= 0) return -1;
if (snapDistance > 0) {
Fun temp_undo = []() { return true; };
Fun temp_redo = []() { return true; };
......@@ -816,7 +816,7 @@ bool TimelineModel::requestItemResize(int itemId, int size, bool right, bool log
};
bool result = false;
if (isClip(itemId)) {
result = m_allClips[itemId]->requestResize(size, right, local_undo, local_redo);
result = m_allClips[itemId]->requestResize(size, right, local_undo, local_redo, logUndo);
} else {
Q_ASSERT(isComposition(itemId));
result = m_allCompositions[itemId]->requestResize(size, right, local_undo, local_redo);
......@@ -1143,10 +1143,15 @@ bool TimelineModel::copyClipEffect(int clipId, const QString &sourceId)
return m_allClips.at(clipId)->copyEffect(effectStack, itemRow);
}
bool TimelineModel::adjustEffectLength(int clipId, const QString &effectId, int duration)
bool TimelineModel::adjustEffectLength(int clipId, const QString &effectId, int duration, int initialDuration)
{
Q_ASSERT(m_allClips.count(clipId));
bool res = m_allClips.at(clipId)->adjustEffectLength(effectId, duration);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = m_allClips.at(clipId)->adjustEffectLength(effectId, duration, initialDuration, undo, redo);
if (res && initialDuration > 0) {
PUSH_UNDO(undo, redo, i18n("Adjust Fade"));
}
return res;
}
......
......@@ -197,7 +197,7 @@ public:
double getClipSpeed(int clipId) const;
bool removeFade(int clipId, bool fromStart);
Q_INVOKABLE bool copyClipEffect(int clipId, const QString &sourceId);
bool adjustEffectLength(int clipId, const QString &effectId, int duration);
bool adjustEffectLength(int clipId, const QString &effectId, int duration, int initialDuration);
/* @brief Returns the closest snap point within snapDistance
*/
......
......@@ -408,10 +408,10 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
};
int delta = m_allClips[clipId]->getPlaytime() - size;
qDebug() << "RESIZING CLIP: " << clipId << " FROM: " << delta;
if (delta == 0) {
return []() { return true; };
}
//qDebug() << "RESIZING CLIP: " << clipId << " FROM: " << delta;
if (delta > 0) { // we shrink clip
return [right, target_clip, target_track, clip_position, delta, in, out, clipId, update_snaps, this]() {
int target_clip_mutable = target_clip;
......@@ -443,6 +443,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
QScopedPointer<Mlt::Producer> clip(m_playlists[target_track].get_clip(target_clip));
if (out >= clip->get_length()) {
clip->parent().set("length", out + 1);
clip->parent().set("out", out);
clip->set("length", out + 1);
}
int err = m_playlists[target_track].resize_clip(target_clip, in, out);
......@@ -480,6 +481,7 @@ Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right
QScopedPointer<Mlt::Producer> clip(m_playlists[target_track].get_clip(target_clip_mutable));
if (out >= clip->get_length()) {
clip->parent().set("length", out + 1);
clip->parent().set("out", out);
clip->set("length", out + 1);
}
err = m_playlists[target_track].resize_clip(target_clip_mutable, in, out);
......
......@@ -499,18 +499,21 @@ Rectangle {
parent.anchors.horizontalCenter = fadeInTriangle.right
else
parent.anchors.left = fadeInTriangle.left
timeline.adjustFade(clipRoot.clipId, 'fadein', clipRoot.fadeIn, startFadeIn)
bubbleHelp.hide()
}
onPositionChanged: {
if (mouse.buttons === Qt.LeftButton) {
var delta = Math.round((parent.x - startX) / timeScale)
var duration = Math.max(0, startFadeIn + delta)
clipRoot.fadeIn = duration;
timeline.adjustFade(clipRoot.clipId, 'fadein', duration)
duration = Math.min(duration, clipRoot.clipDuration)
if (clipRoot.fadeIn != duration) {
clipRoot.fadeIn = duration
timeline.adjustFade(clipRoot.clipId, 'fadein', duration, -1)
}
// Show fade duration as time in a "bubble" help.
var s = timeline.timecode(Math.max(duration, 0))
bubbleHelp.show(clipRoot.x, parentTrack.y + clipRoot.height, s.substring(6))
bubbleHelp.show(clipRoot.x, parentTrack.y + clipRoot.height, s)
}
}
}
......@@ -589,18 +592,21 @@ Rectangle {
parent.anchors.horizontalCenter = fadeOutCanvas.left
else
parent.anchors.right = fadeOutCanvas.right
timeline.adjustFade(clipRoot.clipId, 'fadeout', clipRoot.fadeOut, startFadeOut)
bubbleHelp.hide()
}
onPositionChanged: {
if (mouse.buttons === Qt.LeftButton) {
var delta = Math.round((startX - parent.x) / timeScale)
var duration = Math.max(0, startFadeOut + delta)
clipRoot.fadeOut = duration
timeline.adjustFade(clipRoot.clipId, 'fadeout', duration)
duration = Math.min(duration, clipRoot.clipDuration)
if (clipRoot.fadeOut != duration) {
clipRoot.fadeOut = duration
timeline.adjustFade(clipRoot.clipId, 'fadeout', duration, -1)
}
// Show fade duration as time in a "bubble" help.
var s = timeline.timecode(Math.max(duration, 0))
bubbleHelp.show(clipRoot.x + clipRoot.width, parentTrack.y + clipRoot.height, s.substring(6))
bubbleHelp.show(clipRoot.x + clipRoot.width, parentTrack.y + clipRoot.height, s)
}
}
}
......
......@@ -763,13 +763,13 @@ void TimelineController::addEffectToCurrentClip(const QStringList &effectData)
}
}
void TimelineController::adjustFade(int cid, const QString &effectId, int duration)
void TimelineController::adjustFade(int cid, const QString &effectId, int duration, int initialDuration)
{
if (duration <= 0) {
// remove fade
m_model->removeFade(cid, effectId == QLatin1String("fadein"));
} else {
m_model->adjustEffectLength(cid, effectId, duration);
m_model->adjustEffectLength(cid, effectId, duration, initialDuration);
}
}
......
......@@ -220,7 +220,7 @@ public:
Q_INVOKABLE bool requestSpacerEndOperation(int clipId, int startPosition, int endPosition);
/* @brief Request a Fade in effect for clip
*/
Q_INVOKABLE void adjustFade(int cid, const QString &effectId, int duration);
Q_INVOKABLE void adjustFade(int cid, const QString &effectId, int duration, int initialDuration);
Q_INVOKABLE const QString getTrackNameFromMltIndex(int trackPos);
/* @brief Request inserting space in a track
......
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