Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eac31ad
Refactor timeline undo to use track XML snapshots
ddennedy Aug 10, 2026
70ac2f0
bulk refresh consumer and clearing selection
ddennedy Aug 13, 2026
a4c7846
defer state recording to improve track scope handling
ddennedy Aug 14, 2026
b2a389a
Restore per-clip timeline undo on track XML snapshots
ddennedy Aug 16, 2026
73d69d2
Add profiling for undo changes performance in UndoHelper
ddennedy Aug 16, 2026
e45b115
Remove RestoreTracks on Lift, Remove, Split, Add Transition
ddennedy Aug 16, 2026
b732ee0
Remove RestoreTracks from Insert and Overwrite
ddennedy Aug 16, 2026
c3f77de
Verify clip order to make undo reliabile
ddennedy Aug 16, 2026
3ec80f6
Add progress dialog for timeline undo
ddennedy Aug 17, 2026
6e7dc9f
Remove temporary profiling code from UndoHelper
ddennedy Aug 17, 2026
2b715f0
Set undo helper text in TrimClipInCommand and TrimClipOutCommand
ddennedy Aug 17, 2026
5252cee
Remove mouseReleaseEvent override from HistoryUndoView
ddennedy Aug 17, 2026
48b04b4
Fix thread safety with progress dialog
ddennedy Aug 17, 2026
2191ddf
Enhance undo producer validation and clip identity across commands
ddennedy Sep 1, 2026
aeea29d
Snapshot and restore split clips in InsertCommand
ddennedy Sep 1, 2026
d9bb137
Merge branch 'master' into optimizied-timeline-undo
ddennedy Sep 2, 2026
ee13fe8
Preserve UUID for shadow blanks during track restoration in UndoHelper
ddennedy Sep 2, 2026
9163f45
Improve MoveClipCommand performance
ddennedy Sep 2, 2026
82a9878
clean up
ddennedy Sep 3, 2026
dc8b23c
Fix splt undo/redo affects filter in points
ddennedy Sep 5, 2026
b9b657b
Fix leftover clip after non-ripple multi-clip move undo
ddennedy Sep 5, 2026
98da56e
Fix MoveClipCommand undo for non-ripple moves
ddennedy Sep 5, 2026
f89fd1d
Prevent MLT consumer refresh on no-op history event
ddennedy Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_executable(shotcut WIN32 MACOSX_BUNDLE
docks/filesdock.cpp docks/filesdock.h
docks/filesdock.ui
docks/filtersdock.cpp docks/filtersdock.h
docks/historyundoview.cpp docks/historyundoview.h
docks/jobsdock.cpp docks/jobsdock.h
docks/jobsdock.ui
docks/keyframesdock.cpp docks/keyframesdock.h
Expand Down
88 changes: 59 additions & 29 deletions src/commands/filtercommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include "shotcut_mlt_properties.h"
#include "util.h"

#include <MltFilter.h>
#include <MltLink.h>
#include <QScopedPointer>

class FindProducerParser : public Mlt::Parser
Expand Down Expand Up @@ -93,6 +95,14 @@ static Mlt::Producer findProducer(const QUuid &uuid)
return Mlt::Producer();
}

static bool requireProducer(Mlt::Producer &producer, const QString &what)
{
if (producer.is_valid())
return true;
LOG_ERROR() << "Producer not found for" << what;
return false;
}

namespace Filter {

AddCommand::AddCommand(AttachedFiltersModel &model,
Expand All @@ -113,7 +123,11 @@ AddCommand::AddCommand(AttachedFiltersModel &model,
setText(QObject::tr("Add %1 filter set").arg(name));
}
m_rows.push_back(row);
m_services.push_back(service);
m_serviceNames.push_back(QString::fromUtf8(service.get("mlt_service")));
Mlt::Properties properties;
properties.inherit(service);
m_serviceProperties.push_back(properties);
m_serviceTypes.push_back(service.type());
}

void AddCommand::redo()
Expand All @@ -123,10 +137,21 @@ void AddCommand::redo()
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
int adjustFrom = producer.filter_count();
for (int i = 0; i < m_rows.size(); i++) {
m_model.doAddService(producer, m_services[i], m_rows[i]);
if (m_serviceTypes[i] == mlt_service_filter_type) {
Mlt::Filter filter(MLT.profile(), m_serviceNames[i].toUtf8().constData());
filter.inherit(m_serviceProperties[i]);
m_model.doAddService(producer, filter, m_rows[i]);
} else if (m_serviceTypes[i] == mlt_service_link_type) {
Mlt::Link link(m_serviceNames[i].toUtf8().constData());
link.inherit(m_serviceProperties[i]);
m_model.doAddService(producer, link, m_rows[i]);
} else {
LOG_ERROR() << "Unsupported service type" << m_serviceTypes[i];
}
}
if (AddSetLast == m_type)
MLT.adjustFilters(producer, adjustFrom);
Expand All @@ -138,7 +163,8 @@ void AddCommand::undo()
{
LOG_DEBUG() << text() << m_rows[0];
Mlt::Producer producer(findProducer(m_producerUuid));
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
// Remove the services in reverse order
for (int i = m_rows.size() - 1; i >= 0; i--) {
m_model.doRemoveService(producer, m_rows[i]);
Expand All @@ -158,7 +184,9 @@ bool AddCommand::mergeWith(const QUndoCommand *other)
}
m_type = that->m_type;
m_rows.push_back(that->m_rows.front());
m_services.push_back(that->m_services.front());
m_serviceNames.push_back(that->m_serviceNames.front());
m_serviceProperties.push_back(that->m_serviceProperties.front());
m_serviceTypes.push_back(that->m_serviceTypes.front());
return true;
}

Expand All @@ -184,7 +212,8 @@ void RemoveCommand::redo()
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
m_model.doRemoveService(producer, m_row);
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
Expand All @@ -195,7 +224,8 @@ void RemoveCommand::undo()
Q_ASSERT(m_service.is_valid());
LOG_DEBUG() << text() << m_row;
Mlt::Producer producer(findProducer(m_producerUuid));
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
m_model.doAddService(producer, m_service, m_row);
}

Expand All @@ -218,10 +248,9 @@ void MoveCommand::redo()
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
if (producer.is_valid()) {
m_model.doMoveService(producer, m_fromRow, m_toRow);
}
if (!requireProducer(producer, text()))
return;
m_model.doMoveService(producer, m_fromRow, m_toRow);
if (m_producer.is_valid()) {
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
Expand All @@ -232,10 +261,9 @@ void MoveCommand::undo()
{
LOG_DEBUG() << text() << "from" << m_toRow << "to" << m_fromRow;
Mlt::Producer producer(findProducer(m_producerUuid));
Q_ASSERT(producer.is_valid());
if (producer.is_valid()) {
m_model.doMoveService(producer, m_toRow, m_fromRow);
}
if (!requireProducer(producer, text()))
return;
m_model.doMoveService(producer, m_toRow, m_fromRow);
}

DisableCommand::DisableCommand(
Expand All @@ -261,10 +289,9 @@ void DisableCommand::redo()
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
if (producer.is_valid()) {
m_model.doSetDisabled(producer, m_row, m_disabled);
}
if (!requireProducer(producer, text()))
return;
m_model.doSetDisabled(producer, m_row, m_disabled);
if (m_producer.is_valid()) {
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
Expand All @@ -275,10 +302,9 @@ void DisableCommand::undo()
{
LOG_DEBUG() << text() << m_row;
Mlt::Producer producer(findProducer(m_producerUuid));
Q_ASSERT(producer.is_valid());
if (producer.is_valid()) {
m_model.doSetDisabled(producer, m_row, !m_disabled);
}
if (!requireProducer(producer, text()))
return;
m_model.doSetDisabled(producer, m_row, !m_disabled);
}

bool DisableCommand::mergeWith(const QUndoCommand *other)
Expand Down Expand Up @@ -314,7 +340,8 @@ void PasteCommand::redo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
Mlt::Profile profile(kDefaultMltProfile);
Mlt::Producer filtersProducer(profile, "xml-string", m_xml.toUtf8().constData());
if (filtersProducer.is_valid() && filtersProducer.filter_count() > 0) {
Expand All @@ -327,7 +354,8 @@ void PasteCommand::undo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (!requireProducer(producer, text()))
return;
// Remove all filters
for (int i = 0; i < producer.filter_count(); i++) {
Mlt::Filter *filter = producer.filter(i);
Expand Down Expand Up @@ -388,8 +416,9 @@ void UndoParameterCommand::redo()
m_firstRedo = false;
} else {
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (producer.is_valid() && m_filterController) {
if (!requireProducer(producer, text()))
return;
if (m_filterController) {
Mlt::Service service = m_filterController->attachedModel()->doGetService(producer,
m_row);
service.inherit(m_after);
Expand All @@ -402,8 +431,9 @@ void UndoParameterCommand::undo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (producer.is_valid() && m_filterController) {
if (!requireProducer(producer, text()))
return;
if (m_filterController) {
Mlt::Service service = m_filterController->attachedModel()->doGetService(producer, m_row);
service.inherit(m_before);
m_filterController->onUndoOrRedo(service);
Expand Down
8 changes: 5 additions & 3 deletions src/commands/filtercommands.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2024 Meltytech, LLC
* Copyright (c) 2021-2026 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
Expand All @@ -21,7 +21,7 @@
#include "models/attachedfiltersmodel.h"

#include <MltProducer.h>
#include <MltService.h>
#include <MltProperties.h>
#include <QString>
#include <QUndoCommand>
#include <QUuid>
Expand Down Expand Up @@ -65,7 +65,9 @@ class AddCommand : public QUndoCommand
private:
AttachedFiltersModel &m_model;
std::vector<int> m_rows;
std::vector<Mlt::Service> m_services;
std::vector<QString> m_serviceNames;
std::vector<Mlt::Properties> m_serviceProperties;
std::vector<mlt_service_type> m_serviceTypes;
Mlt::Producer m_producer;
QUuid m_producerUuid;
AddType m_type;
Expand Down
Loading