übernahme Code Shortcut

This commit is contained in:
georg0480
2026-01-31 15:28:10 +01:00
parent 6f4d6b9301
commit ef46c21291
1787 changed files with 1126465 additions and 0 deletions

View File

@@ -0,0 +1,415 @@
/*
* Copyright (c) 2021-2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filtercommands.h"
#include "Logger.h"
#include "controllers/filtercontroller.h"
#include "mainwindow.h"
#include "mltcontroller.h"
#include "qmltypes/qmlapplication.h"
class FindProducerParser : public Mlt::Parser
{
private:
QUuid m_uuid;
Mlt::Producer m_producer;
public:
FindProducerParser(QUuid uuid)
: Mlt::Parser()
, m_uuid(uuid)
{}
Mlt::Producer producer() { return m_producer; }
int on_start_filter(Mlt::Filter *) { return 0; }
int on_start_producer(Mlt::Producer *producer)
{
if (MLT.uuid(*producer) == m_uuid) {
m_producer = producer;
return 1;
}
return 0;
}
int on_end_producer(Mlt::Producer *) { return 0; }
int on_start_playlist(Mlt::Playlist *playlist) { return on_start_producer(playlist); }
int on_end_playlist(Mlt::Playlist *) { return 0; }
int on_start_tractor(Mlt::Tractor *tractor) { return on_start_producer(tractor); }
int on_end_tractor(Mlt::Tractor *) { return 0; }
int on_start_multitrack(Mlt::Multitrack *) { return 0; }
int on_end_multitrack(Mlt::Multitrack *) { return 0; }
int on_start_track() { return 0; }
int on_end_track() { return 0; }
int on_end_filter(Mlt::Filter *) { return 0; }
int on_start_transition(Mlt::Transition *) { return 0; }
int on_end_transition(Mlt::Transition *) { return 0; }
int on_start_chain(Mlt::Chain *chain) { return on_start_producer(chain); }
int on_end_chain(Mlt::Chain *) { return 0; }
int on_start_link(Mlt::Link *) { return 0; }
int on_end_link(Mlt::Link *) { return 0; }
};
static Mlt::Producer findProducer(const QUuid &uuid)
{
FindProducerParser graphParser(uuid);
if (MAIN.isMultitrackValid()) {
graphParser.start(*MAIN.multitrack());
if (graphParser.producer().is_valid()) {
return graphParser.producer();
}
}
if (MAIN.playlist() && MAIN.playlist()->count() > 0) {
graphParser.start(*MAIN.playlist());
if (graphParser.producer().is_valid()) {
return graphParser.producer();
}
}
Mlt::Producer producer(MLT.isClip() ? MLT.producer() : MLT.savedProducer());
if (producer.is_valid()) {
graphParser.start(producer);
if (graphParser.producer().is_valid()) {
return graphParser.producer();
}
}
return Mlt::Producer();
}
namespace Filter {
AddCommand::AddCommand(AttachedFiltersModel &model,
const QString &name,
Mlt::Service &service,
int row,
AddCommand::AddType type,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_producer(*model.producer())
, m_producerUuid(MLT.ensureHasUuid(m_producer))
, m_type(type)
{
if (m_type == AddCommand::AddSingle) {
setText(QObject::tr("Add %1 filter").arg(name));
} else {
setText(QObject::tr("Add %1 filter set").arg(name));
}
m_rows.push_back(row);
m_services.push_back(service);
}
void AddCommand::redo()
{
LOG_DEBUG() << text() << m_rows[0];
Mlt::Producer producer = m_producer;
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
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 (AddSetLast == m_type)
MLT.adjustFilters(producer, adjustFrom);
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
}
void AddCommand::undo()
{
LOG_DEBUG() << text() << m_rows[0];
Mlt::Producer producer(findProducer(m_producerUuid));
Q_ASSERT(producer.is_valid());
// Remove the services in reverse order
for (int i = m_rows.size() - 1; i >= 0; i--) {
m_model.doRemoveService(producer, m_rows[i]);
}
}
bool AddCommand::mergeWith(const QUndoCommand *other)
{
AddCommand *that = const_cast<AddCommand *>(static_cast<const AddCommand *>(other));
if (!that || that->id() != id()) {
LOG_ERROR() << "Invalid merge";
return false;
}
if (m_type != AddSet || !(that->m_type == AddSet || that->m_type == AddSetLast)) {
// Only merge services from the same filter set
return false;
}
m_type = that->m_type;
m_rows.push_back(that->m_rows.front());
m_services.push_back(that->m_services.front());
return true;
}
RemoveCommand::RemoveCommand(AttachedFiltersModel &model,
const QString &name,
Mlt::Service &service,
int row,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_row(row)
, m_producer(*model.producer())
, m_producerUuid(MLT.ensureHasUuid(m_producer))
, m_service(service)
{
setText(QObject::tr("Remove %1 filter").arg(name));
}
void RemoveCommand::redo()
{
LOG_DEBUG() << text() << m_row;
Mlt::Producer producer = m_producer;
if (!producer.is_valid()) {
producer = findProducer(m_producerUuid);
}
Q_ASSERT(producer.is_valid());
m_model.doRemoveService(producer, m_row);
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
}
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());
m_model.doAddService(producer, m_service, m_row);
}
MoveCommand::MoveCommand(
AttachedFiltersModel &model, const QString &name, int fromRow, int toRow, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_fromRow(fromRow)
, m_toRow(toRow)
, m_producer(*model.producer())
, m_producerUuid(MLT.ensureHasUuid(m_producer))
{
setText(QObject::tr("Move %1 filter").arg(name));
}
void MoveCommand::redo()
{
LOG_DEBUG() << text() << "from" << m_fromRow << "to" << m_toRow;
Mlt::Producer producer = m_producer;
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 (m_producer.is_valid()) {
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
}
}
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);
}
}
DisableCommand::DisableCommand(
AttachedFiltersModel &model, const QString &name, int row, bool disabled, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_row(row)
, m_producer(*model.producer())
, m_producerUuid(MLT.ensureHasUuid(m_producer))
, m_disabled(disabled)
{
if (disabled) {
setText(QObject::tr("Disable %1 filter").arg(name));
} else {
setText(QObject::tr("Enable %1 filter").arg(name));
}
}
void DisableCommand::redo()
{
LOG_DEBUG() << text() << m_row;
Mlt::Producer producer = m_producer;
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 (m_producer.is_valid()) {
// Only hold the producer reference for the first redo and lookup by UUID thereafter.
m_producer = Mlt::Producer();
}
}
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);
}
}
bool DisableCommand::mergeWith(const QUndoCommand *other)
{
// TODO: This doesn't always provide expected results.
// If you toggle twice and then undo, you get the opposite of the original state.
// It would make sense to merge three toggles in a row, but not two.
// Do not implement for now.
return false;
/*
DisableCommand *that = const_cast<DisableCommand *>(static_cast<const DisableCommand *>(other));
if (!that || that->id() != id())
return false;
m_disabled = that->m_disabled;
setText(that->text());
return true;
*/
}
PasteCommand::PasteCommand(AttachedFiltersModel &model,
const QString &filterProducerXml,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_xml(filterProducerXml)
, m_producerUuid(MLT.ensureHasUuid(*model.producer()))
{
setText(QObject::tr("Paste filters"));
m_beforeXml = MLT.XML(model.producer());
}
void PasteCommand::redo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
Mlt::Profile profile(kDefaultMltProfile);
Mlt::Producer filtersProducer(profile, "xml-string", m_xml.toUtf8().constData());
if (filtersProducer.is_valid() && filtersProducer.filter_count() > 0) {
MLT.pasteFilters(&producer, &filtersProducer);
}
emit QmlApplication::singleton().filtersPasted(&producer);
}
void PasteCommand::undo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
// Remove all filters
for (int i = 0; i < producer.filter_count(); i++) {
Mlt::Filter *filter = producer.filter(i);
if (filter && filter->is_valid() && !filter->get_int("_loader")
&& !filter->get_int(kShotcutHiddenProperty)) {
producer.detach(*filter);
i--;
}
delete filter;
}
// Restore the "before" filters
Mlt::Profile profile(kDefaultMltProfile);
Mlt::Producer filtersProducer(profile, "xml-string", m_beforeXml.toUtf8().constData());
if (filtersProducer.is_valid() && filtersProducer.filter_count() > 0) {
MLT.pasteFilters(&producer, &filtersProducer);
}
emit QmlApplication::singleton().filtersPasted(&producer);
}
UndoParameterCommand::UndoParameterCommand(const QString &name,
FilterController *controller,
int row,
Mlt::Properties &before,
const QString &desc,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_filterController(controller)
, m_row(row)
, m_producerUuid(MLT.ensureHasUuid(*controller->attachedModel()->producer()))
, m_firstRedo(true)
{
if (desc.isEmpty()) {
setText(QObject::tr("Change %1 filter").arg(name));
} else {
setText(QObject::tr("Change %1 filter: %2").arg(name, desc));
}
m_before.inherit(before);
Mlt::Service *service = controller->attachedModel()->getService(m_row);
m_after.inherit(*service);
}
void UndoParameterCommand::update(const QString &propertyName)
{
Mlt::Service *service = m_filterController->attachedModel()->getService(m_row);
m_after.pass_property(*service, propertyName.toUtf8().constData());
}
void UndoParameterCommand::redo()
{
LOG_DEBUG() << text();
if (m_firstRedo) {
m_firstRedo = false;
} else {
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (producer.is_valid() && m_filterController) {
Mlt::Service service = m_filterController->attachedModel()->doGetService(producer,
m_row);
service.inherit(m_after);
m_filterController->onUndoOrRedo(service);
}
}
}
void UndoParameterCommand::undo()
{
LOG_DEBUG() << text();
Mlt::Producer producer = findProducer(m_producerUuid);
Q_ASSERT(producer.is_valid());
if (producer.is_valid() && m_filterController) {
Mlt::Service service = m_filterController->attachedModel()->doGetService(producer, m_row);
service.inherit(m_before);
m_filterController->onUndoOrRedo(service);
}
}
bool UndoParameterCommand::mergeWith(const QUndoCommand *other)
{
UndoParameterCommand *that = const_cast<UndoParameterCommand *>(
static_cast<const UndoParameterCommand *>(other));
LOG_DEBUG() << "this filter" << m_row << "that filter" << that->m_row;
if (that->id() != id() || that->m_row != m_row || that->m_producerUuid != m_producerUuid
|| that->text() != text())
return false;
m_after = that->m_after;
return true;
}
} // namespace Filter

View File

@@ -0,0 +1,243 @@
/*
* Copyright (c) 2021-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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILTERCOMMANDS_H
#define FILTERCOMMANDS_H
#include "models/attachedfiltersmodel.h"
#include <MltProducer.h>
#include <MltService.h>
#include <QString>
#include <QUndoCommand>
#include <QUuid>
class QmlMetadata;
class FilterController;
namespace Filter {
enum {
UndoIdAdd = 300,
UndoIdMove,
UndoIdDisable,
UndoIdChangeParameter,
UndoIdChangeAddKeyframe,
UndoIdChangeRemoveKeyframe,
UndoIdChangeKeyframe,
};
class AddCommand : public QUndoCommand
{
public:
typedef enum {
AddSingle,
AddSet,
AddSetLast,
} AddType;
AddCommand(AttachedFiltersModel &model,
const QString &name,
Mlt::Service &service,
int row,
AddCommand::AddType type = AddCommand::AddSingle,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdAdd; }
bool mergeWith(const QUndoCommand *other);
private:
AttachedFiltersModel &m_model;
std::vector<int> m_rows;
std::vector<Mlt::Service> m_services;
Mlt::Producer m_producer;
QUuid m_producerUuid;
AddType m_type;
};
class RemoveCommand : public QUndoCommand
{
public:
RemoveCommand(AttachedFiltersModel &model,
const QString &name,
Mlt::Service &service,
int row,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
AttachedFiltersModel &m_model;
int m_index;
int m_row;
Mlt::Producer m_producer;
QUuid m_producerUuid;
Mlt::Service m_service;
};
class MoveCommand : public QUndoCommand
{
public:
MoveCommand(AttachedFiltersModel &model,
const QString &name,
int fromRow,
int toRow,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdMove; }
private:
AttachedFiltersModel &m_model;
int m_fromRow;
int m_toRow;
Mlt::Producer m_producer;
QUuid m_producerUuid;
};
class DisableCommand : public QUndoCommand
{
public:
DisableCommand(AttachedFiltersModel &model,
const QString &name,
int row,
bool disabled,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdDisable; }
bool mergeWith(const QUndoCommand *other);
private:
AttachedFiltersModel &m_model;
int m_row;
Mlt::Producer m_producer;
QUuid m_producerUuid;
bool m_disabled;
};
class PasteCommand : public QUndoCommand
{
public:
PasteCommand(AttachedFiltersModel &model,
const QString &filterProducerXml,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
AttachedFiltersModel &m_model;
QString m_xml;
QString m_beforeXml;
QUuid m_producerUuid;
};
class UndoParameterCommand : public QUndoCommand
{
public:
UndoParameterCommand(const QString &name,
FilterController *controller,
int row,
Mlt::Properties &before,
const QString &desc = QString(),
QUndoCommand *parent = 0);
void update(const QString &propertyName);
void redo();
void undo();
protected:
int id() const { return UndoIdChangeParameter; }
bool mergeWith(const QUndoCommand *other);
private:
int m_row;
QUuid m_producerUuid;
Mlt::Properties m_before;
Mlt::Properties m_after;
FilterController *m_filterController;
bool m_firstRedo;
};
class UndoAddKeyframeCommand : public UndoParameterCommand
{
public:
UndoAddKeyframeCommand(const QString &name,
FilterController *controller,
int row,
Mlt::Properties &before)
: UndoParameterCommand(name, controller, row, before, QObject::tr("add keyframe"))
{}
protected:
int id() const { return UndoIdChangeAddKeyframe; }
bool mergeWith(const QUndoCommand *other) { return false; }
};
class UndoRemoveKeyframeCommand : public UndoParameterCommand
{
public:
UndoRemoveKeyframeCommand(const QString &name,
FilterController *controller,
int row,
Mlt::Properties &before)
: UndoParameterCommand(name, controller, row, before, QObject::tr("remove keyframe"))
{}
protected:
int id() const { return UndoIdChangeRemoveKeyframe; }
bool mergeWith(const QUndoCommand *other) { return false; }
};
class UndoModifyKeyframeCommand : public UndoParameterCommand
{
public:
UndoModifyKeyframeCommand(const QString &name,
FilterController *controller,
int row,
Mlt::Properties &before,
int paramIndex,
int keyframeIndex)
: UndoParameterCommand(name, controller, row, before, QObject::tr("modify keyframe"))
, m_paramIndex(paramIndex)
, m_keyframeIndex(keyframeIndex)
{}
protected:
int id() const { return UndoIdChangeRemoveKeyframe; }
bool mergeWith(const QUndoCommand *other)
{
auto *that = dynamic_cast<const UndoModifyKeyframeCommand *>(other);
if (!that || m_paramIndex != that->m_paramIndex || m_keyframeIndex != that->m_keyframeIndex)
return false;
else
return UndoParameterCommand::mergeWith(other);
}
private:
int m_paramIndex;
int m_keyframeIndex;
};
} // namespace Filter
#endif // FILTERCOMMANDS_H

View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2021 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "markercommands.h"
#include "Logger.h"
namespace Markers {
DeleteCommand::DeleteCommand(MarkersModel &model, const Marker &delMarker, int index)
: QUndoCommand(0)
, m_model(model)
, m_delMarker(delMarker)
, m_index(index)
{
setText(QObject::tr("Delete marker: %1").arg(m_delMarker.text));
}
void DeleteCommand::redo()
{
m_model.doRemove(m_index);
}
void DeleteCommand::undo()
{
m_model.doInsert(m_index, m_delMarker);
}
AppendCommand::AppendCommand(MarkersModel &model, const Marker &newMarker, int index)
: QUndoCommand(0)
, m_model(model)
, m_newMarker(newMarker)
, m_index(index)
{
setText(QObject::tr("Add marker: %1").arg(m_newMarker.text));
}
void AppendCommand::redo()
{
m_model.doAppend(m_newMarker);
}
void AppendCommand::undo()
{
m_model.doRemove(m_index);
}
UpdateCommand::UpdateCommand(MarkersModel &model,
const Marker &newMarker,
const Marker &oldMarker,
int index)
: QUndoCommand(0)
, m_model(model)
, m_newMarker(newMarker)
, m_oldMarker(oldMarker)
, m_index(index)
{
if (m_newMarker.text == m_oldMarker.text && m_newMarker.color == m_oldMarker.color) {
setText(QObject::tr("Move marker: %1").arg(m_oldMarker.text));
} else {
setText(QObject::tr("Edit marker: %1").arg(m_oldMarker.text));
}
}
void UpdateCommand::redo()
{
m_model.doUpdate(m_index, m_newMarker);
}
void UpdateCommand::undo()
{
m_model.doUpdate(m_index, m_oldMarker);
}
bool UpdateCommand::mergeWith(const QUndoCommand *other)
{
const UpdateCommand *that = static_cast<const UpdateCommand *>(other);
LOG_DEBUG() << "this index" << m_index << "that index" << that->m_index;
if (that->id() != id() || that->m_index != m_index)
return false;
bool merge = false;
if (that->m_newMarker.text == m_oldMarker.text && that->m_newMarker.color == m_oldMarker.color) {
// Only start/end change. Merge with previous move command.
merge = true;
} else if (that->m_newMarker.end == m_oldMarker.end
&& that->m_newMarker.start == m_oldMarker.start) {
// Only text/color change. Merge with previous edit command.
merge = true;
}
if (!merge)
return false;
m_newMarker = that->m_newMarker;
return true;
}
ClearCommand::ClearCommand(MarkersModel &model, QList<Marker> &clearMarkers)
: QUndoCommand(0)
, m_model(model)
, m_clearMarkers(clearMarkers)
{
setText(QObject::tr("Clear markers"));
}
void ClearCommand::redo()
{
m_model.doClear();
}
void ClearCommand::undo()
{
m_model.doReplace(m_clearMarkers);
}
} // namespace Markers

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2021-2023 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MARKERCOMMANDS_H
#define MARKERCOMMANDS_H
#include "models/markersmodel.h"
#include <QUndoCommand>
namespace Markers {
enum {
UndoIdUpdate = 200,
};
class DeleteCommand : public QUndoCommand
{
public:
DeleteCommand(MarkersModel &model, const Marker &delMarker, int index);
void redo();
void undo();
private:
MarkersModel &m_model;
Marker m_delMarker;
int m_index;
};
class AppendCommand : public QUndoCommand
{
public:
AppendCommand(MarkersModel &model, const Marker &newMarker, int index);
void redo();
void undo();
private:
MarkersModel &m_model;
Marker m_newMarker;
int m_index;
};
class UpdateCommand : public QUndoCommand
{
public:
UpdateCommand(MarkersModel &model, const Marker &newMarker, const Marker &oldMarker, int index);
void redo();
void undo();
protected:
int id() const { return UndoIdUpdate; }
bool mergeWith(const QUndoCommand *other);
private:
MarkersModel &m_model;
Marker m_newMarker;
Marker m_oldMarker;
int m_index;
};
class ClearCommand : public QUndoCommand
{
public:
ClearCommand(MarkersModel &model, QList<Marker> &clearMarkers);
void redo();
void undo();
private:
MarkersModel &m_model;
QList<Marker> m_clearMarkers;
};
} // namespace Markers
#endif // MARKERCOMMANDS_H

View File

@@ -0,0 +1,555 @@
/*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "playlistcommands.h"
#include "Logger.h"
#include "docks/playlistdock.h"
#include "mainwindow.h"
#include "mltcontroller.h"
#include "shotcut_mlt_properties.h"
#include <QTreeWidget>
namespace Playlist {
AppendCommand::AppendCommand(PlaylistModel &model,
const QString &xml,
bool emitModified,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_xml(xml)
, m_emitModified(emitModified)
{
setText(QObject::tr("Append playlist item %1").arg(m_model.rowCount() + 1));
}
void AppendCommand::redo()
{
LOG_DEBUG() << "";
Mlt::Producer producer(MLT.profile(), "xml-string", m_xml.toUtf8().constData());
m_model.append(producer, m_emitModified);
if (m_uuid.isNull()) {
m_uuid = MLT.ensureHasUuid(producer);
} else {
MLT.setUuid(producer, m_uuid);
}
}
void AppendCommand::undo()
{
LOG_DEBUG() << "";
m_model.remove(m_model.rowCount() - 1);
}
InsertCommand::InsertCommand(PlaylistModel &model, const QString &xml, int row, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_xml(xml)
, m_row(row)
{
setText(QObject::tr("Insert playist item %1").arg(row + 1));
}
void InsertCommand::redo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_xml.toUtf8().constData());
m_model.insert(producer, m_row);
if (m_uuid.isNull()) {
m_uuid = MLT.ensureHasUuid(producer);
} else {
MLT.setUuid(producer, m_uuid);
}
}
void InsertCommand::undo()
{
LOG_DEBUG() << "row" << m_row;
m_model.remove(m_row);
}
UpdateCommand::UpdateCommand(PlaylistModel &model, const QString &xml, int row, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_newXml(xml)
, m_row(row)
{
setText(QObject::tr("Update playlist item %1").arg(row + 1));
QScopedPointer<Mlt::ClipInfo> info(m_model.playlist()->clip_info(row));
info->producer->set_in_and_out(info->frame_in, info->frame_out);
m_oldXml = MLT.XML(info->producer);
}
void UpdateCommand::redo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_newXml.toUtf8().constData());
m_model.update(m_row, producer);
if (m_uuid.isNull()) {
m_uuid = MLT.ensureHasUuid(producer);
} else {
MLT.setUuid(producer, m_uuid);
}
}
void UpdateCommand::undo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_oldXml.toUtf8().constData());
m_model.update(m_row, producer);
}
bool UpdateCommand::mergeWith(const QUndoCommand *other)
{
const UpdateCommand *that = static_cast<const UpdateCommand *>(other);
LOG_DEBUG() << "this row" << m_row << "that row" << that->m_row;
if (that->id() != id() || that->m_row != m_row)
return false;
m_newXml = that->m_newXml;
return true;
}
RemoveCommand::RemoveCommand(PlaylistModel &model, int row, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_row(row)
{
QScopedPointer<Mlt::ClipInfo> info(m_model.playlist()->clip_info(row));
info->producer->set_in_and_out(info->frame_in, info->frame_out);
m_xml = MLT.XML(info->producer);
setText(QObject::tr("Remove playlist item %1").arg(row + 1));
m_uuid = MLT.ensureHasUuid(*info->producer);
}
void RemoveCommand::redo()
{
LOG_DEBUG() << "row" << m_row;
m_model.remove(m_row);
}
void RemoveCommand::undo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_xml.toUtf8().constData());
m_model.insert(producer, m_row);
MLT.setUuid(producer, m_uuid);
}
ClearCommand::ClearCommand(PlaylistModel &model, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
{
m_xml = MLT.XML(m_model.playlist());
setText(QObject::tr("Clear playlist"));
for (int i = 0; i < m_model.playlist()->count(); i++) {
Mlt::Producer clip(m_model.playlist()->get_clip(i));
if (clip.is_valid()) {
m_uuids << MLT.ensureHasUuid(clip.parent());
}
}
}
void ClearCommand::redo()
{
LOG_DEBUG() << "";
m_model.clear();
}
void ClearCommand::undo()
{
LOG_DEBUG() << "";
Mlt::Producer *producer = new Mlt::Producer(MLT.profile(),
"xml-string",
m_xml.toUtf8().constData());
if (producer->is_valid()) {
producer->set("resource", "<playlist>");
if (!MLT.setProducer(producer)) {
m_model.load();
for (int i = 0; i < m_model.playlist()->count(); i++) {
Mlt::Producer clip(m_model.playlist()->get_clip(i));
if (clip.is_valid() && i < m_uuids.size()) {
MLT.setUuid(clip.parent(), m_uuids[i]);
}
}
MLT.pause();
MAIN.seekPlaylist(0);
}
} else {
LOG_ERROR() << "failed to restore playlist from XML";
}
}
MoveCommand::MoveCommand(PlaylistModel &model, int from, int to, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_from(from)
, m_to(to)
{
setText(QObject::tr("Move item from %1 to %2").arg(from + 1).arg(to + 1));
}
void MoveCommand::redo()
{
LOG_DEBUG() << "from" << m_from << "to" << m_to;
m_model.move(m_from, m_to);
}
void MoveCommand::undo()
{
LOG_DEBUG() << "from" << m_from << "to" << m_to;
m_model.move(m_to, m_from);
}
SortCommand::SortCommand(PlaylistModel &model, int column, Qt::SortOrder order, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_column(column)
, m_order(order)
{
m_xml = MLT.XML(m_model.playlist());
QString columnName = m_model.headerData(m_column, Qt::Horizontal, Qt::DisplayRole).toString();
setText(QObject::tr("Sort playlist by %1").arg(columnName));
for (int i = 0; i < m_model.playlist()->count(); i++) {
Mlt::Producer clip(m_model.playlist()->get_clip(i));
if (clip.is_valid()) {
m_uuids << MLT.ensureHasUuid(clip.parent());
}
}
}
void SortCommand::redo()
{
LOG_DEBUG() << m_column;
m_model.sort(m_column, m_order);
}
void SortCommand::undo()
{
LOG_DEBUG() << "";
Mlt::Producer *producer = new Mlt::Producer(MLT.profile(),
"xml-string",
m_xml.toUtf8().constData());
if (producer->is_valid()) {
producer->set("resource", "<playlist>");
if (!MLT.setProducer(producer)) {
m_model.load();
for (int i = 0; i < m_model.playlist()->count(); i++) {
Mlt::Producer clip(m_model.playlist()->get_clip(i));
if (clip.is_valid() && i < m_uuids.size()) {
MLT.setUuid(clip.parent(), m_uuids[i]);
}
}
MLT.pause();
MAIN.seekPlaylist(0);
}
} else {
LOG_ERROR() << "failed to restore playlist from XML";
}
}
TrimClipInCommand::TrimClipInCommand(PlaylistModel &model, int row, int in, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_row(row)
, m_oldIn(in)
, m_newIn(in)
, m_out(-1)
{
setText(QObject::tr("Trim playlist item %1 in").arg(row + 1));
QScopedPointer<Mlt::ClipInfo> info(m_model.playlist()->clip_info(row));
if (info) {
m_oldIn = info->frame_in;
m_out = info->frame_out;
}
}
void TrimClipInCommand::redo()
{
LOG_DEBUG() << "row" << m_row << "in" << m_newIn;
m_model.setInOut(m_row, m_newIn, m_out);
}
void TrimClipInCommand::undo()
{
LOG_DEBUG() << "row" << m_row << "in" << m_oldIn;
m_model.setInOut(m_row, m_oldIn, m_out);
}
bool TrimClipInCommand::mergeWith(const QUndoCommand *other)
{
const TrimClipInCommand *that = static_cast<const TrimClipInCommand *>(other);
LOG_DEBUG() << "this row" << m_row << "that row" << that->m_row;
if (that->id() != id() || that->m_row != m_row)
return false;
m_newIn = that->m_newIn;
return true;
}
TrimClipOutCommand::TrimClipOutCommand(PlaylistModel &model, int row, int out, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_row(row)
, m_in(-1)
, m_oldOut(out)
, m_newOut(out)
{
setText(QObject::tr("Trim playlist item %1 out").arg(row + 1));
QScopedPointer<Mlt::ClipInfo> info(m_model.playlist()->clip_info(row));
if (info) {
m_in = info->frame_in;
m_oldOut = info->frame_out;
}
}
void TrimClipOutCommand::redo()
{
LOG_DEBUG() << "row" << m_row << "out" << m_newOut;
m_model.setInOut(m_row, m_in, m_newOut);
}
void TrimClipOutCommand::undo()
{
LOG_DEBUG() << "row" << m_row << "out" << m_oldOut;
m_model.setInOut(m_row, m_in, m_oldOut);
}
bool TrimClipOutCommand::mergeWith(const QUndoCommand *other)
{
const TrimClipOutCommand *that = static_cast<const TrimClipOutCommand *>(other);
LOG_DEBUG() << "this row" << m_row << "that row" << that->m_row;
if (that->id() != id() || that->m_row != m_row)
return false;
m_newOut = that->m_newOut;
return true;
}
ReplaceCommand::ReplaceCommand(PlaylistModel &model,
const QString &xml,
int row,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_newXml(xml)
, m_row(row)
{
setText(QObject::tr("Replace playlist item %1").arg(row + 1));
QScopedPointer<Mlt::ClipInfo> info(m_model.playlist()->clip_info(row));
info->producer->set_in_and_out(info->frame_in, info->frame_out);
m_uuid = MLT.ensureHasUuid(*info->producer);
m_oldXml = MLT.XML(info->producer);
}
void ReplaceCommand::redo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_newXml.toUtf8().constData());
m_model.update(m_row, producer, true);
}
void ReplaceCommand::undo()
{
LOG_DEBUG() << "row" << m_row;
Mlt::Producer producer(MLT.profile(), "xml-string", m_oldXml.toUtf8().constData());
m_model.update(m_row, producer, true);
MLT.setUuid(producer, m_uuid);
}
NewBinCommand::NewBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_binTree(tree)
, m_bin(bin)
{
setText(QObject::tr("Add new bin: %1").arg(bin));
auto props = m_model.playlist()->get_props(kShotcutBinsProperty);
if (props && props->is_valid()) {
m_oldBins.copy(*props, "");
}
}
void NewBinCommand::redo()
{
auto item = new QTreeWidgetItem(m_binTree, {m_bin});
auto icon = QIcon::fromTheme("folder", QIcon(":/icons/oxygen/32x32/places/folder.png"));
item->setIcon(0, icon);
PlaylistDock::sortBins(m_binTree);
emit m_binTree->itemSelectionChanged();
std::unique_ptr<Mlt::Properties> props(m_model.playlist()->get_props(kShotcutBinsProperty));
if (!props || !props->is_valid()) {
props.reset(new Mlt::Properties);
m_model.playlist()->set(kShotcutBinsProperty, *props);
}
for (int i = PlaylistDock::SmartBinCount; i < m_binTree->topLevelItemCount(); ++i) {
auto name = m_binTree->topLevelItem(i)->text(0);
props->set(QString::number(i).toLatin1().constData(), name.toUtf8().constData());
}
}
void NewBinCommand::undo()
{
m_model.playlist()->set(kShotcutBinsProperty, m_oldBins);
auto items = m_binTree->findItems(m_bin, Qt::MatchExactly);
if (!items.isEmpty())
delete items.first();
RenameBinCommand::rebuildBinList(m_model, m_binTree);
}
MoveToBinCommand::MoveToBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
const QList<int> &rows,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_binTree(tree)
, m_bin(bin)
{
setText(QObject::tr("Move %n item(s) to bin: %1", "", rows.size()).arg(bin));
for (const auto row : rows) {
auto clip = m_model.playlist()->get_clip(row);
if (clip && clip->is_valid() && clip->parent().is_valid()) {
m_oldData.append({row, clip->parent().get(kShotcutBinsProperty)});
}
}
}
void MoveToBinCommand::redo()
{
for (auto &old : m_oldData) {
m_model.setBin(old.row, m_bin);
}
}
void MoveToBinCommand::undo()
{
for (auto &old : m_oldData) {
m_model.setBin(old.row, old.bin);
}
}
RenameBinCommand::RenameBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
const QString &newName,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_binTree(tree)
, m_bin(bin)
, m_newName(newName)
{
if (newName.isEmpty()) {
setText(QObject::tr("Remove bin: %1").arg(bin));
} else {
setText(QObject::tr("Rename bin: %1").arg(newName));
}
}
void RenameBinCommand::redo()
{
auto items = m_binTree->findItems(m_bin, Qt::MatchExactly);
if (!items.isEmpty()) {
m_binTree->blockSignals(true);
items.first()->setSelected(false);
m_binTree->blockSignals(false);
if (m_newName.isEmpty()) {
// Remove
delete items.first();
// Remove bin property from playlist items
for (int i = 0; i < m_model.playlist()->count(); ++i) {
auto clip = m_model.playlist()->get_clip(i);
if (clip && clip->is_valid() && m_bin == clip->parent().get(kShotcutBinsProperty)) {
clip->parent().Mlt::Properties::clear(kShotcutBinsProperty);
m_removedRows << i;
}
}
m_model.renameBin(m_bin);
rebuildBinList(m_model, m_binTree);
// Select ALL bin
m_binTree->clearSelection();
} else {
// Rename
items.first()->setText(0, m_newName);
items.first()->setSelected(true);
m_model.renameBin(m_bin, m_newName);
rebuildBinList(m_model, m_binTree);
// Reselect bin
PlaylistDock::sortBins(m_binTree);
emit m_binTree->itemSelectionChanged();
}
}
}
void RenameBinCommand::undo()
{
auto items = m_binTree->findItems(m_newName, Qt::MatchExactly);
if (m_newName.isEmpty()) {
// Undo remove
auto item = new QTreeWidgetItem(m_binTree, {m_bin});
auto icon = QIcon::fromTheme("folder", QIcon(":/icons/oxygen/32x32/places/folder.png"));
item->setIcon(0, icon);
PlaylistDock::sortBins(m_binTree);
// Restore bin property on playlist items
for (auto row : m_removedRows) {
m_model.playlist()->get_clip(row)->parent().set(kShotcutBinsProperty,
m_bin.toUtf8().constData());
}
m_model.renameBin(m_bin);
rebuildBinList(m_model, m_binTree);
} else if (!items.isEmpty()) {
// Undo rename
m_binTree->blockSignals(true);
m_binTree->clearSelection();
m_binTree->blockSignals(false);
items.first()->setText(0, m_bin);
items.first()->setSelected(true);
m_model.renameBin(m_newName, m_bin);
rebuildBinList(m_model, m_binTree);
// Reselect bin
PlaylistDock::sortBins(m_binTree);
}
emit m_binTree->itemSelectionChanged();
}
void RenameBinCommand::rebuildBinList(PlaylistModel &model, QTreeWidget *binTree)
{
// Rebuild list of bins
std::unique_ptr<Mlt::Properties> props(model.playlist()->get_props(kShotcutBinsProperty));
for (int i = 0; i < props->count(); ++i) {
const auto name = QString::fromLatin1(props->get_name(i));
if (!name.startsWith('_') && !name.startsWith('.'))
props->clear(name.toLatin1().constData());
}
for (int i = PlaylistDock::SmartBinCount; i < binTree->topLevelItemCount(); ++i) {
auto name = binTree->topLevelItem(i)->text(0);
props->set(QString::number(i).toLatin1().constData(), name.toUtf8().constData());
}
}
} // namespace Playlist

View File

@@ -0,0 +1,254 @@
/*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLAYLISTCOMMANDS_H
#define PLAYLISTCOMMANDS_H
#include "models/playlistmodel.h"
#include <QString>
#include <QUndoCommand>
#include <QUuid>
class QTreeWidget;
namespace Playlist {
enum { UndoIdTrimClipIn = 0, UndoIdTrimClipOut, UndoIdUpdate };
class AppendCommand : public QUndoCommand
{
public:
AppendCommand(PlaylistModel &model,
const QString &xml,
bool emitModified = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QString m_xml;
bool m_emitModified;
QUuid m_uuid;
};
class InsertCommand : public QUndoCommand
{
public:
InsertCommand(PlaylistModel &model, const QString &xml, int row, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QString m_xml;
int m_row;
QUuid m_uuid;
};
class UpdateCommand : public QUndoCommand
{
public:
UpdateCommand(PlaylistModel &model, const QString &xml, int row, QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdUpdate; }
bool mergeWith(const QUndoCommand *other);
private:
PlaylistModel &m_model;
QString m_newXml;
QString m_oldXml;
int m_row;
QUuid m_uuid;
};
class RemoveCommand : public QUndoCommand
{
public:
RemoveCommand(PlaylistModel &model, int row, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QString m_xml;
int m_row;
QUuid m_uuid;
};
class MoveCommand : public QUndoCommand
{
public:
MoveCommand(PlaylistModel &model, int from, int to, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
int m_from;
int m_to;
};
class ClearCommand : public QUndoCommand
{
public:
ClearCommand(PlaylistModel &model, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QString m_xml;
QVector<QUuid> m_uuids;
};
class SortCommand : public QUndoCommand
{
public:
SortCommand(PlaylistModel &model, int column, Qt::SortOrder order, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
int m_column;
Qt::SortOrder m_order;
QString m_xml;
QVector<QUuid> m_uuids;
};
class TrimClipInCommand : public QUndoCommand
{
public:
TrimClipInCommand(PlaylistModel &model, int row, int in, QUndoCommand *parent = nullptr);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimClipIn; }
bool mergeWith(const QUndoCommand *other);
private:
PlaylistModel &m_model;
int m_row;
int m_oldIn;
int m_newIn;
int m_out;
};
class TrimClipOutCommand : public QUndoCommand
{
public:
TrimClipOutCommand(PlaylistModel &model, int row, int out, QUndoCommand *parent = nullptr);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimClipOut; }
bool mergeWith(const QUndoCommand *other);
private:
PlaylistModel &m_model;
int m_row;
int m_in;
int m_oldOut;
int m_newOut;
};
class ReplaceCommand : public QUndoCommand
{
public:
ReplaceCommand(PlaylistModel &model, const QString &xml, int row, QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QString m_newXml;
QString m_oldXml;
int m_row;
QUuid m_uuid;
};
class NewBinCommand : public QUndoCommand
{
public:
NewBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QTreeWidget *m_binTree;
QString m_bin;
Mlt::Properties m_oldBins;
};
class MoveToBinCommand : public QUndoCommand
{
public:
MoveToBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
const QList<int> &rows,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
PlaylistModel &m_model;
QTreeWidget *m_binTree;
QString m_bin;
typedef struct
{
int row;
QString bin;
} oldData;
QList<oldData> m_oldData;
};
class RenameBinCommand : public QUndoCommand
{
public:
RenameBinCommand(PlaylistModel &model,
QTreeWidget *tree,
const QString &bin,
const QString &newName = QString(),
QUndoCommand *parent = 0);
void redo();
void undo();
static void rebuildBinList(PlaylistModel &model, QTreeWidget *binTree);
private:
PlaylistModel &m_model;
QTreeWidget *m_binTree;
QString m_bin;
QString m_newName;
QList<int> m_removedRows;
};
} // namespace Playlist
#endif // PLAYLISTCOMMANDS_H

View File

@@ -0,0 +1,343 @@
/*
* Copyright (c) 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "subtitlecommands.h"
#include "Logger.h"
#include "mainwindow.h"
#include <QFileInfo>
namespace Subtitles {
InsertTrackCommand::InsertTrackCommand(SubtitlesModel &model,
const SubtitlesModel::SubtitleTrack &track,
int index)
: QUndoCommand(0)
, m_model(model)
, m_track(track)
, m_index(index)
{
setText(QObject::tr("Add subtitle track: %1").arg(m_track.name));
}
void InsertTrackCommand::redo()
{
LOG_DEBUG() << m_track.name;
m_model.doInsertTrack(m_track, m_index);
}
void InsertTrackCommand::undo()
{
m_model.doRemoveTrack(m_index);
}
RemoveTrackCommand::RemoveTrackCommand(SubtitlesModel &model, int trackIndex)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_saveTrack(m_model.getTrack(trackIndex))
{
setText(QObject::tr("Remove subtitle track: %1").arg(m_saveTrack.name));
int count = m_model.itemCount(m_trackIndex);
m_saveSubtitles.reserve(count);
for (int i = 0; i < count; i++) {
m_saveSubtitles.push_back(m_model.getItem(m_trackIndex, i));
}
}
void RemoveTrackCommand::redo()
{
m_model.doRemoveTrack(m_trackIndex);
}
void RemoveTrackCommand::undo()
{
m_model.doInsertTrack(m_saveTrack, m_trackIndex);
m_model.doInsertSubtitleItems(m_trackIndex, m_saveSubtitles);
}
EditTrackCommand::EditTrackCommand(SubtitlesModel &model,
const SubtitlesModel::SubtitleTrack &track,
int index)
: QUndoCommand(0)
, m_model(model)
, m_newTrack(track)
, m_index(index)
{
m_oldTrack = m_model.getTrack(index);
setText(QObject::tr("Edit subtitle track: %1").arg(m_newTrack.name));
}
void EditTrackCommand::redo()
{
LOG_DEBUG() << m_oldTrack.name;
m_model.doEditTrack(m_newTrack, m_index);
}
void EditTrackCommand::undo()
{
m_model.doEditTrack(m_oldTrack, m_index);
}
OverwriteSubtitlesCommand::OverwriteSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_newSubtitles(items)
{
if (m_newSubtitles.size() == 1) {
setText(QObject::tr("Add subtitle"));
} else {
setText(QObject::tr("Add %n subtitles", nullptr, m_newSubtitles.size()));
}
if (m_newSubtitles.size() <= 0) {
return;
}
// Save anything that will be removed
int64_t startPosition = m_newSubtitles[0].start;
int64_t endPosition = m_newSubtitles[m_newSubtitles.size() - 1].end;
int count = m_model.itemCount(m_trackIndex);
for (int i = 0; i < count; i++) {
auto item = m_model.getItem(m_trackIndex, i);
if ((item.start >= startPosition && item.start < endPosition)
|| (item.end > startPosition && item.end < endPosition)) {
m_saveSubtitles.push_back(item);
}
}
}
void OverwriteSubtitlesCommand::redo()
{
LOG_DEBUG() << m_newSubtitles.size();
if (m_newSubtitles.size() > 0) {
if (m_saveSubtitles.size() > 0) {
m_model.doRemoveSubtitleItems(m_trackIndex, m_saveSubtitles);
}
m_model.doInsertSubtitleItems(m_trackIndex, m_newSubtitles);
}
}
void OverwriteSubtitlesCommand::undo()
{
LOG_DEBUG() << m_newSubtitles.size();
if (m_newSubtitles.size() > 0) {
m_model.doRemoveSubtitleItems(m_trackIndex, m_newSubtitles);
if (m_saveSubtitles.size() > 0) {
m_model.doInsertSubtitleItems(m_trackIndex, m_saveSubtitles);
}
}
}
RemoveSubtitlesCommand::RemoveSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_items(items)
{
if (m_items.size() == 1) {
setText(QObject::tr("Remove subtitle"));
} else {
setText(QObject::tr("Remove %n subtitles", nullptr, m_items.size()));
}
}
void RemoveSubtitlesCommand::redo()
{
LOG_DEBUG() << m_items.size();
m_model.doRemoveSubtitleItems(m_trackIndex, m_items);
}
void RemoveSubtitlesCommand::undo()
{
LOG_DEBUG() << m_items.size();
if (m_items.size() > 0) {
m_model.doInsertSubtitleItems(m_trackIndex, m_items);
}
}
SetTextCommand::SetTextCommand(SubtitlesModel &model,
int trackIndex,
int itemIndex,
const QString &text)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_itemIndex(itemIndex)
, m_newText(text)
{
setText(QObject::tr("Edit subtitle text"));
m_oldText = QString::fromStdString(m_model.getItem(trackIndex, itemIndex).text);
}
void SetTextCommand::redo()
{
m_model.doSetText(m_trackIndex, m_itemIndex, m_newText);
}
void SetTextCommand::undo()
{
m_model.doSetText(m_trackIndex, m_itemIndex, m_oldText);
}
bool SetTextCommand::mergeWith(const QUndoCommand *other)
{
const SetTextCommand *that = static_cast<const SetTextCommand *>(other);
if (m_trackIndex != that->m_trackIndex || m_itemIndex != that->m_itemIndex) {
return false;
}
LOG_DEBUG() << "track" << m_trackIndex << "item" << m_itemIndex;
m_newText = that->m_newText;
return true;
}
SetStartCommand::SetStartCommand(SubtitlesModel &model,
int trackIndex,
int itemIndex,
int64_t msTime)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_itemIndex(itemIndex)
, m_newStart(msTime)
{
setText(QObject::tr("Change subtitle start"));
m_oldStart = m_model.getItem(trackIndex, itemIndex).start;
}
void SetStartCommand::redo()
{
int64_t endTime = m_model.getItem(m_trackIndex, m_itemIndex).end;
m_model.doSetTime(m_trackIndex, m_itemIndex, m_newStart, endTime);
}
void SetStartCommand::undo()
{
int64_t endTime = m_model.getItem(m_trackIndex, m_itemIndex).end;
m_model.doSetTime(m_trackIndex, m_itemIndex, m_oldStart, endTime);
}
bool SetStartCommand::mergeWith(const QUndoCommand *other)
{
const SetStartCommand *that = static_cast<const SetStartCommand *>(other);
if (m_trackIndex != that->m_trackIndex || m_itemIndex != that->m_itemIndex) {
return false;
}
LOG_DEBUG() << "track" << m_trackIndex << "item" << m_itemIndex;
m_newStart = that->m_newStart;
return true;
}
SetEndCommand::SetEndCommand(SubtitlesModel &model, int trackIndex, int itemIndex, int64_t msTime)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_itemIndex(itemIndex)
, m_newEnd(msTime)
{
setText(QObject::tr("Change subtitle end"));
m_oldEnd = m_model.getItem(trackIndex, itemIndex).end;
}
void SetEndCommand::redo()
{
int64_t startTime = m_model.getItem(m_trackIndex, m_itemIndex).start;
m_model.doSetTime(m_trackIndex, m_itemIndex, startTime, m_newEnd);
}
void SetEndCommand::undo()
{
int64_t startTime = m_model.getItem(m_trackIndex, m_itemIndex).start;
m_model.doSetTime(m_trackIndex, m_itemIndex, startTime, m_oldEnd);
}
bool SetEndCommand::mergeWith(const QUndoCommand *other)
{
const SetEndCommand *that = static_cast<const SetEndCommand *>(other);
if (m_trackIndex != that->m_trackIndex || m_itemIndex != that->m_itemIndex) {
return false;
}
LOG_DEBUG() << "track" << m_trackIndex << "item" << m_itemIndex;
m_newEnd = that->m_newEnd;
return true;
}
MoveSubtitlesCommand::MoveSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items,
int64_t msTime)
: QUndoCommand(0)
, m_model(model)
, m_trackIndex(trackIndex)
, m_oldSubtitles(items)
{
if (m_oldSubtitles.size() <= 0) {
return;
}
if (m_oldSubtitles.size() == 1) {
setText(QObject::tr("Move subtitle"));
} else {
setText(QObject::tr("Move %n subtitles", nullptr, m_oldSubtitles.size()));
}
// Create a list of subtitles with the new times
int64_t delta = msTime - m_oldSubtitles[0].start;
for (int i = 0; i < m_oldSubtitles.size(); i++) {
m_newSubtitles.push_back(m_oldSubtitles[i]);
m_newSubtitles[i].start += delta;
m_newSubtitles[i].end += delta;
}
}
void MoveSubtitlesCommand::redo()
{
LOG_DEBUG() << m_oldSubtitles.size();
m_model.doRemoveSubtitleItems(m_trackIndex, m_oldSubtitles);
m_model.doInsertSubtitleItems(m_trackIndex, m_newSubtitles);
}
void MoveSubtitlesCommand::undo()
{
LOG_DEBUG() << m_oldSubtitles.size();
m_model.doRemoveSubtitleItems(m_trackIndex, m_newSubtitles);
m_model.doInsertSubtitleItems(m_trackIndex, m_oldSubtitles);
}
bool MoveSubtitlesCommand::mergeWith(const QUndoCommand *other)
{
const MoveSubtitlesCommand *that = static_cast<const MoveSubtitlesCommand *>(other);
if (m_trackIndex != that->m_trackIndex) {
return false;
}
if (m_oldSubtitles.size() != that->m_oldSubtitles.size()) {
return false;
}
if (m_newSubtitles[0].start != that->m_oldSubtitles[0].start) {
return false;
}
LOG_DEBUG() << "track" << m_trackIndex;
m_newSubtitles = that->m_newSubtitles;
return true;
}
} // namespace Subtitles

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SUBTITLECOMMANDS_H
#define SUBTITLECOMMANDS_H
#include "models/subtitlesmodel.h"
#include <QUndoCommand>
namespace Subtitles {
enum {
UndoIdSubText = 400,
UndoIdSubStart,
UndoIdSubEnd,
UndoIdSubMove,
};
class InsertTrackCommand : public QUndoCommand
{
public:
InsertTrackCommand(SubtitlesModel &model, const SubtitlesModel::SubtitleTrack &track, int index);
void redo();
void undo();
private:
SubtitlesModel &m_model;
SubtitlesModel::SubtitleTrack m_track;
int m_index;
};
class RemoveTrackCommand : public QUndoCommand
{
public:
RemoveTrackCommand(SubtitlesModel &model, int trackIndex);
void redo();
void undo();
private:
SubtitlesModel &m_model;
int m_trackIndex;
SubtitlesModel::SubtitleTrack m_saveTrack;
QList<Subtitles::SubtitleItem> m_saveSubtitles;
};
class EditTrackCommand : public QUndoCommand
{
public:
EditTrackCommand(SubtitlesModel &model, const SubtitlesModel::SubtitleTrack &track, int index);
void redo();
void undo();
private:
SubtitlesModel &m_model;
SubtitlesModel::SubtitleTrack m_oldTrack;
SubtitlesModel::SubtitleTrack m_newTrack;
int m_index;
};
class OverwriteSubtitlesCommand : public QUndoCommand
{
public:
OverwriteSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items);
void redo();
void undo();
protected:
QList<Subtitles::SubtitleItem> m_newSubtitles;
private:
SubtitlesModel &m_model;
int m_trackIndex;
QList<Subtitles::SubtitleItem> m_saveSubtitles;
};
class RemoveSubtitlesCommand : public QUndoCommand
{
public:
RemoveSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items);
void redo();
void undo();
private:
SubtitlesModel &m_model;
int m_trackIndex;
QList<Subtitles::SubtitleItem> m_items;
};
class SetTextCommand : public QUndoCommand
{
public:
SetTextCommand(SubtitlesModel &model, int trackIndex, int itemIndex, const QString &text);
void redo();
void undo();
protected:
int id() const { return UndoIdSubText; }
bool mergeWith(const QUndoCommand *other);
private:
SubtitlesModel &m_model;
int m_trackIndex;
int m_itemIndex;
QString m_newText;
QString m_oldText;
};
class SetStartCommand : public QUndoCommand
{
public:
SetStartCommand(SubtitlesModel &model, int trackIndex, int itemIndex, int64_t msTime);
void redo();
void undo();
protected:
int id() const { return UndoIdSubStart; }
bool mergeWith(const QUndoCommand *other);
private:
SubtitlesModel &m_model;
int m_trackIndex;
int m_itemIndex;
int64_t m_newStart;
int64_t m_oldStart;
};
class SetEndCommand : public QUndoCommand
{
public:
SetEndCommand(SubtitlesModel &model, int trackIndex, int itemIndex, int64_t msTime);
void redo();
void undo();
protected:
int id() const { return UndoIdSubEnd; }
bool mergeWith(const QUndoCommand *other);
private:
SubtitlesModel &m_model;
int m_trackIndex;
int m_itemIndex;
int64_t m_newEnd;
int64_t m_oldEnd;
};
class MoveSubtitlesCommand : public QUndoCommand
{
public:
MoveSubtitlesCommand(SubtitlesModel &model,
int trackIndex,
const QList<Subtitles::SubtitleItem> &items,
int64_t msTime);
void redo();
void undo();
protected:
int id() const { return UndoIdSubMove; }
bool mergeWith(const QUndoCommand *other);
private:
SubtitlesModel &m_model;
int m_trackIndex;
QList<Subtitles::SubtitleItem> m_oldSubtitles;
QList<Subtitles::SubtitleItem> m_newSubtitles;
};
} // namespace Subtitles
#endif // SUBTITLECOMMANDS_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,892 @@
/*
* Copyright (c) 2013-2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COMMANDS_H
#define COMMANDS_H
#include "docks/timelinedock.h"
#include "models/markersmodel.h"
#include "models/multitrackmodel.h"
#include "undohelper.h"
#include <MltProducer.h>
#include <MltTransition.h>
#include <QObject>
#include <QString>
#include <QUndoCommand>
#include <QUuid>
#include <vector>
namespace Timeline {
enum {
UndoIdTrimClipIn = 100,
UndoIdTrimClipOut,
UndoIdFadeIn,
UndoIdFadeOut,
UndoIdTrimTransitionIn,
UndoIdTrimTransitionOut,
UndoIdAddTransitionByTrimIn,
UndoIdAddTransitionByTrimOut,
UndoIdUpdate,
UndoIdMoveClip,
UndoIdChangeGain,
};
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:
AppendCommand(MultitrackModel &model,
int trackIndex,
const QString &xml,
bool skipProxy = false,
bool seek = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
QString m_xml;
UndoHelper m_undoHelper;
bool m_skipProxy;
bool m_seek;
QVector<QUuid> m_uuids;
};
class InsertCommand : public QUndoCommand
{
public:
InsertCommand(MultitrackModel &model,
MarkersModel &markersModel,
int trackIndex,
int position,
const QString &xml,
bool seek = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
MarkersModel &m_markersModel;
int m_trackIndex;
int m_position;
QString m_xml;
QStringList m_oldTracks;
UndoHelper m_undoHelper;
bool m_seek;
bool m_rippleAllTracks;
bool m_rippleMarkers;
int m_markersShift;
QVector<QUuid> m_uuids;
};
class OverwriteCommand : public QUndoCommand
{
public:
OverwriteCommand(MultitrackModel &model,
int trackIndex,
int position,
const QString &xml,
bool seek = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_position;
QString m_xml;
UndoHelper m_undoHelper;
bool m_seek;
QVector<QUuid> m_uuids;
};
class LiftCommand : public QUndoCommand
{
public:
LiftCommand(MultitrackModel &model, int trackIndex, int clipIndex, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
UndoHelper m_undoHelper;
};
class RemoveCommand : public QUndoCommand
{
public:
RemoveCommand(MultitrackModel &model,
MarkersModel &markersModel,
int trackIndex,
int clipIndex,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
MarkersModel &m_markersModel;
int m_trackIndex;
int m_clipIndex;
UndoHelper m_undoHelper;
bool m_rippleAllTracks;
bool m_rippleMarkers;
int m_markerRemoveStart;
int m_markerRemoveEnd;
QList<Markers::Marker> m_markers;
};
class GroupCommand : public QUndoCommand
{
public:
GroupCommand(MultitrackModel &model, QUndoCommand *parent = 0);
void addToGroup(int trackIndex, int clipIndex);
void redo();
void undo();
private:
MultitrackModel &m_model;
QList<ClipPosition> m_clips;
QMap<ClipPosition, int> m_prevGroups;
};
class UngroupCommand : public QUndoCommand
{
public:
UngroupCommand(MultitrackModel &model, QUndoCommand *parent = 0);
void removeFromGroup(int trackIndex, int clipIndex);
void redo();
void undo();
private:
MultitrackModel &m_model;
QMap<ClipPosition, int> m_prevGroups;
};
class NameTrackCommand : public QUndoCommand
{
public:
NameTrackCommand(MultitrackModel &model,
int trackIndex,
const QString &name,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
QString m_name;
QString m_oldName;
};
class MergeCommand : public QUndoCommand
{
public:
MergeCommand(MultitrackModel &model, int trackIndex, int clipIndex, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
UndoHelper m_undoHelper;
};
class MuteTrackCommand : public QUndoCommand
{
public:
MuteTrackCommand(MultitrackModel &model, int trackIndex, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
bool m_oldValue;
};
class HideTrackCommand : public QUndoCommand
{
public:
HideTrackCommand(MultitrackModel &model, int trackIndex, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
bool m_oldValue;
};
class CompositeTrackCommand : public QUndoCommand
{
public:
CompositeTrackCommand(MultitrackModel &model,
int trackIndex,
bool value,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
bool m_value;
bool m_oldValue;
};
class LockTrackCommand : public QUndoCommand
{
public:
LockTrackCommand(MultitrackModel &model, int trackIndex, bool value, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
bool m_value;
bool m_oldValue;
};
class MoveClipCommand : public QUndoCommand
{
public:
MoveClipCommand(TimelineDock &timeline,
int trackDelta,
int positionDelta,
bool ripple,
QUndoCommand *parent = 0);
void addClip(int trackIndex, int clipIndex);
void redo();
void undo();
protected:
int id() const { return UndoIdMoveClip; }
bool mergeWith(const QUndoCommand *other);
private:
void redoMarkers();
TimelineDock &m_timeline;
MultitrackModel &m_model;
MarkersModel &m_markersModel;
struct Info
{
int trackIndex;
int clipIndex;
int frame_in;
int frame_out;
int start;
int group;
QUuid uuid;
Info()
: trackIndex(-1)
, clipIndex(-1)
, frame_in(-1)
, frame_out(-1)
, start(0)
, group(-1)
{}
};
int m_trackDelta;
int m_positionDelta;
bool m_ripple;
bool m_rippleAllTracks;
bool m_rippleMarkers;
UndoHelper m_undoHelper;
QMultiMap<int, Info> m_clips; // ordered by position
bool m_redo;
int m_earliestStart;
QList<Markers::Marker> m_markers;
};
class TrimCommand : public QUndoCommand
{
public:
explicit TrimCommand(QUndoCommand *parent = 0)
: QUndoCommand(parent)
{}
void setUndoHelper(UndoHelper *helper) { m_undoHelper.reset(helper); }
protected:
QScopedPointer<UndoHelper> m_undoHelper;
};
class TrimClipInCommand : public TrimCommand
{
public:
TrimClipInCommand(MultitrackModel &model,
MarkersModel &markersModel,
int trackIndex,
int clipIndex,
int delta,
bool ripple,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimClipIn; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
MarkersModel &m_markersModel;
int m_trackIndex;
int m_clipIndex;
int m_delta;
bool m_ripple;
bool m_rippleAllTracks;
bool m_rippleMarkers;
bool m_redo;
int m_markerRemoveStart;
int m_markerRemoveEnd;
QList<Markers::Marker> m_markers;
};
class TrimClipOutCommand : public TrimCommand
{
public:
TrimClipOutCommand(MultitrackModel &model,
MarkersModel &markersModel,
int trackIndex,
int clipIndex,
int delta,
bool ripple,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimClipOut; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
MarkersModel &m_markersModel;
int m_trackIndex;
int m_clipIndex;
int m_delta;
bool m_ripple;
bool m_rippleAllTracks;
bool m_rippleMarkers;
bool m_redo;
int m_markerRemoveStart;
int m_markerRemoveEnd;
QList<Markers::Marker> m_markers;
};
class SplitCommand : public QUndoCommand
{
public:
SplitCommand(MultitrackModel &model,
const std::vector<int> &trackIndex,
const std::vector<int> &clipIndex,
int position,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
std::vector<int> m_trackIndex;
std::vector<int> m_clipIndex;
int m_position;
UndoHelper m_undoHelper;
};
class FadeInCommand : public QUndoCommand
{
public:
FadeInCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int duration,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdFadeIn; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_duration;
int m_previous;
};
class FadeOutCommand : public QUndoCommand
{
public:
FadeOutCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int duration,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdFadeOut; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_duration;
int m_previous;
};
class AddTransitionCommand : public QUndoCommand
{
public:
AddTransitionCommand(TimelineDock &timeline,
int trackIndex,
int clipIndex,
int position,
bool ripple,
QUndoCommand *parent = 0);
void redo();
void undo();
int getTransitionIndex() const { return m_transitionIndex; }
private:
TimelineDock &m_timeline;
MultitrackModel &m_model;
MarkersModel &m_markersModel;
int m_trackIndex;
int m_clipIndex;
int m_position;
int m_transitionIndex;
bool m_ripple;
UndoHelper m_undoHelper;
bool m_rippleAllTracks;
bool m_rippleMarkers;
int m_markerOldStart;
int m_markerNewStart;
QList<Markers::Marker> m_markers;
};
class TrimTransitionInCommand : public TrimCommand
{
public:
TrimTransitionInCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int delta,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimTransitionIn; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_delta;
bool m_notify;
bool m_redo;
};
class TrimTransitionOutCommand : public TrimCommand
{
public:
TrimTransitionOutCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int delta,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdTrimTransitionOut; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_delta;
bool m_notify;
bool m_redo;
};
class AddTransitionByTrimInCommand : public TrimCommand
{
public:
AddTransitionByTrimInCommand(TimelineDock &timeline,
int trackIndex,
int clipIndex,
int duration,
int trimDelta,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdAddTransitionByTrimIn; }
bool mergeWith(const QUndoCommand *other);
private:
TimelineDock &m_timeline;
int m_trackIndex;
int m_clipIndex;
int m_duration;
int m_trimDelta;
bool m_notify;
bool m_redo;
};
class RemoveTransitionByTrimInCommand : public TrimCommand
{
public:
RemoveTransitionByTrimInCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int delta,
QString xml,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_delta;
QString m_xml;
bool m_redo;
};
class RemoveTransitionByTrimOutCommand : public TrimCommand
{
public:
RemoveTransitionByTrimOutCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int delta,
QString xml,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_delta;
QString m_xml;
bool m_redo;
};
class AddTransitionByTrimOutCommand : public TrimCommand
{
public:
AddTransitionByTrimOutCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
int duration,
int trimDelta,
bool redo = true,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdAddTransitionByTrimOut; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
int m_duration;
int m_trimDelta;
bool m_notify;
bool m_redo;
};
class AddTrackCommand : public QUndoCommand
{
public:
AddTrackCommand(MultitrackModel &model, bool isVideo, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
bool m_isVideo;
QUuid m_uuid;
};
class InsertTrackCommand : public QUndoCommand
{
public:
InsertTrackCommand(MultitrackModel &model,
int trackIndex,
TrackType trackType = PlaylistTrackType,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
TrackType m_trackType;
QUuid m_uuid;
};
class RemoveTrackCommand : public QUndoCommand
{
public:
RemoveTrackCommand(MultitrackModel &model, int trackIndex, QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
TrackType m_trackType;
QString m_trackName;
UndoHelper m_undoHelper;
QScopedPointer<Mlt::Producer> m_filtersProducer;
QUuid m_uuid;
};
class MoveTrackCommand : public QUndoCommand
{
public:
MoveTrackCommand(MultitrackModel &model,
int fromTrackIndex,
int toTrackIndex,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_fromTrackIndex;
int m_toTrackIndex;
};
class ChangeBlendModeCommand : public QObject, public QUndoCommand
{
Q_OBJECT
public:
ChangeBlendModeCommand(Mlt::Transition &transition,
const QString &propertyName,
const QString &mode,
QUndoCommand *parent = 0);
void redo();
void undo();
signals:
void modeChanged(QString &mode);
private:
Mlt::Transition m_transition;
QString m_propertyName;
QString m_newMode;
QString m_oldMode;
};
class UpdateCommand : public QUndoCommand
{
public:
UpdateCommand(TimelineDock &timeline,
int trackIndex,
int clipIndex,
int position,
QUndoCommand *parent = 0);
void setXmlAfter(const QString &xml);
void setPosition(int trackIndex, int clipIndex, int position);
void setRippleAllTracks(bool);
int trackIndex() const { return m_trackIndex; }
int clipIndex() const { return m_clipIndex; }
int position() const { return m_position; }
void redo();
void undo();
private:
TimelineDock &m_timeline;
int m_trackIndex;
int m_clipIndex;
int m_position;
QString m_xmlAfter;
bool m_isFirstRedo;
UndoHelper m_undoHelper;
bool m_ripple;
bool m_rippleAllTracks;
};
class DetachAudioCommand : public QUndoCommand
{
public:
DetachAudioCommand(TimelineDock &timeline,
int trackIndex,
int clipIndex,
int position,
const QString &xml,
QUndoCommand *parent = 0);
void redo();
void undo();
private:
TimelineDock &m_timeline;
int m_trackIndex;
int m_clipIndex;
int m_position;
int m_targetTrackIndex;
QString m_xml;
UndoHelper m_undoHelper;
bool m_trackAdded;
QUuid m_uuid;
};
class ReplaceCommand : public QUndoCommand
{
public:
ReplaceCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
const QString &xml,
QUndoCommand *parent = nullptr);
void redo();
void undo();
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
QString m_xml;
bool m_isFirstRedo;
UndoHelper m_undoHelper;
};
class AlignClipsCommand : public QUndoCommand
{
public:
AlignClipsCommand(MultitrackModel &model, QUndoCommand *parent = 0);
void addAlignment(QUuid uuid, int offset, double speedCompensation);
void redo();
void undo();
private:
MultitrackModel &m_model;
UndoHelper m_undoHelper;
bool m_redo;
struct Alignment
{
QUuid uuid;
int offset;
double speed;
};
QVector<Alignment> m_alignments;
};
class ApplyFiltersCommand : public QUndoCommand
{
public:
ApplyFiltersCommand(MultitrackModel &model,
const QString &filterProducerXml,
QUndoCommand *parent = 0);
void addClip(int trackIndex, int clipIndex);
void redo();
void undo();
private:
MultitrackModel &m_model;
QString m_xml;
QMap<ClipPosition, QString> m_prevFilters;
};
class ChangeGainCommand : public QUndoCommand
{
public:
ChangeGainCommand(MultitrackModel &model,
int trackIndex,
int clipIndex,
double gain,
QUndoCommand *parent = 0);
void redo();
void undo();
protected:
int id() const { return UndoIdChangeGain; }
bool mergeWith(const QUndoCommand *other);
private:
MultitrackModel &m_model;
int m_trackIndex;
int m_clipIndex;
double m_gain;
double m_previous;
};
} // namespace Timeline
#endif

454
src/commands/undohelper.cpp Normal file
View File

@@ -0,0 +1,454 @@
/*
* Copyright (c) 2015-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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "undohelper.h"
#include "Logger.h"
#include "mltcontroller.h"
#include "models/audiolevelstask.h"
#include "shotcut_mlt_properties.h"
#include <QScopedPointer>
#include <QUuid>
#ifdef UNDOHELPER_DEBUG
#define UNDOLOG LOG_DEBUG()
#else
#define UNDOLOG \
if (false) \
LOG_DEBUG()
#endif
UndoHelper::UndoHelper(MultitrackModel &model)
: m_model(model)
, m_hints(NoHints)
{}
void UndoHelper::recordBeforeState()
{
#ifdef UNDOHELPER_DEBUG
debugPrintState("Before state");
#endif
m_state.clear();
m_clipsAdded.clear();
m_insertedOrder.clear();
for (int i = 0; i < m_model.trackList().count(); ++i) {
int mltIndex = m_model.trackList()[i].mlt_index;
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(mltIndex));
Mlt::Playlist playlist(*trackProducer);
for (int j = 0; j < playlist.count(); ++j) {
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(j));
QUuid uid = MLT.ensureHasUuid(clip->parent());
if (clip->is_blank()) {
uid = MLT.ensureHasUuid(*clip);
}
m_insertedOrder << uid;
Info &info = m_state[uid];
if (!(m_hints & SkipXML))
info.xml = MLT.XML(&clip->parent());
Mlt::ClipInfo clipInfo;
playlist.clip_info(j, &clipInfo);
info.frame_in = clipInfo.frame_in;
info.frame_out = clipInfo.frame_out;
info.oldTrackIndex = i;
info.oldClipIndex = j;
info.isBlank = playlist.is_blank(j);
if (clipInfo.cut && clipInfo.cut->property_exists(kShotcutGroupProperty)) {
info.group = clipInfo.cut->get_int(kShotcutGroupProperty);
}
}
}
}
void UndoHelper::recordAfterState()
{
#ifdef UNDOHELPER_DEBUG
debugPrintState("After state");
#endif
QList<QUuid> clipsRemoved = m_state.keys();
m_clipsAdded.clear();
for (int i = 0; i < m_model.trackList().count(); ++i) {
int mltIndex = m_model.trackList()[i].mlt_index;
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(mltIndex));
Mlt::Playlist playlist(*trackProducer);
for (int j = 0; j < playlist.count(); ++j) {
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(j));
QUuid uid = MLT.ensureHasUuid(clip->parent());
if (clip->is_blank()) {
uid = MLT.ensureHasUuid(*clip);
}
/* Clips not previously in m_state are new */
if (!m_state.contains(uid)) {
UNDOLOG << "New clip at" << i << j;
m_clipsAdded << uid;
m_affectedTracks << i;
} else {
Info &info = m_state[uid];
info.changes = 0;
info.newTrackIndex = i;
info.newClipIndex = j;
/* Indices have changed; these are moved */
if (info.oldTrackIndex != info.newTrackIndex
|| info.oldClipIndex != info.newClipIndex) {
UNDOLOG << "Clip" << uid << "moved from" << info.oldTrackIndex
<< info.oldClipIndex << "to" << info.newTrackIndex << info.newClipIndex;
info.changes |= Moved;
m_affectedTracks << info.oldTrackIndex;
m_affectedTracks << info.newTrackIndex;
}
if (!(m_hints & SkipXML) && !info.isBlank) {
QString newXml = MLT.XML(&clip->parent());
if (info.xml != newXml) {
UNDOLOG << "Modified xml:" << uid;
info.changes |= XMLModified;
m_affectedTracks << i;
}
}
Mlt::ClipInfo newInfo;
playlist.clip_info(j, &newInfo);
/* Only in/out point changes are handled at this time. */
if (info.frame_in != newInfo.frame_in || info.frame_out != newInfo.frame_out) {
UNDOLOG << "In/out changed:" << uid;
info.changes |= ClipInfoModified;
info.in_delta = info.frame_in - newInfo.frame_in;
info.out_delta = newInfo.frame_out - info.frame_out;
m_affectedTracks << i;
}
}
clipsRemoved.removeOne(uid);
}
}
/* Clips that did not show up are removed from the timeline */
foreach (QUuid uid, clipsRemoved) {
UNDOLOG << "Clip removed:" << uid;
auto &info = m_state[uid];
info.changes = Removed;
m_affectedTracks << info.oldTrackIndex;
}
}
void UndoHelper::undoChanges()
{
#ifdef UNDOHELPER_DEBUG
debugPrintState("Before undo");
#endif
if (m_hints & RestoreTracks) {
restoreAffectedTracks();
emit m_model.modified();
#ifdef UNDOHELPER_DEBUG
debugPrintState("After undo");
#endif
return;
}
QMap<int, int> indexAdjustment;
/* We're walking through the list in the order of uids, which is the order in which the
* clips were laid out originally. As we go through the clips we make sure the clips behind
* the current index are as they were originally before we move on to the next one */
foreach (QUuid uid, m_insertedOrder) {
const Info &info = m_state[uid];
UNDOLOG << "Handling uid" << uid << "on track" << info.oldTrackIndex << "index"
<< info.oldClipIndex;
int trackIndex = m_model.trackList()[info.oldTrackIndex].mlt_index;
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(trackIndex));
Mlt::Playlist playlist(*trackProducer);
/* This is the index in the track we're currently restoring */
int currentIndex = qMin(info.oldClipIndex + indexAdjustment[trackIndex],
playlist.count() - 1);
/* Clips that were moved are simply searched for using the uid, and moved in place. We
* do not use the indices directly because they become invalid once the playlist is
* modified. */
if (info.changes & Moved) {
Q_ASSERT(info.newTrackIndex == info.oldTrackIndex
&& "cross-track moves are unsupported so far");
int clipCurrentlyAt = -1;
for (int i = 0; i < playlist.count(); ++i) {
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(i));
if (MLT.uuid(clip->parent()) == uid || MLT.uuid(*clip) == uid) {
clipCurrentlyAt = i;
break;
}
}
Q_ASSERT(clipCurrentlyAt != -1 && "Moved clip could not be found");
UNDOLOG << "Found clip with uid" << uid << "at index" << clipCurrentlyAt;
if (clipCurrentlyAt != info.oldClipIndex
&& (currentIndex < clipCurrentlyAt || currentIndex > clipCurrentlyAt + 1)) {
UNDOLOG << "moving from" << clipCurrentlyAt << "to" << currentIndex;
QModelIndex modelIndex = m_model.createIndex(clipCurrentlyAt, 0, info.oldTrackIndex);
m_model.beginMoveRows(modelIndex.parent(),
clipCurrentlyAt,
clipCurrentlyAt,
modelIndex.parent(),
currentIndex);
playlist.move(clipCurrentlyAt, currentIndex);
m_model.endMoveRows();
}
}
/* Removed clips are reinserted using their stored XML */
if (info.changes & Removed) {
QModelIndex modelIndex = m_model.createIndex(currentIndex, 0, info.oldTrackIndex);
m_model.beginInsertRows(modelIndex.parent(), currentIndex, currentIndex);
if (info.isBlank) {
playlist.insert_blank(currentIndex, info.frame_out - info.frame_in);
UNDOLOG << "inserting isBlank at " << currentIndex;
} else {
UNDOLOG << "inserting clip at " << currentIndex << uid;
Q_ASSERT(!(m_hints & SkipXML) && "Cannot restore clip without stored XML");
Q_ASSERT(!info.xml.isEmpty());
Mlt::Producer restoredClip(MLT.profile(),
"xml-string",
info.xml.toUtf8().constData());
if (restoredClip.type() == mlt_service_tractor_type) { // transition
restoredClip.set("mlt_type", "mlt_producer");
} else {
fixTransitions(playlist, currentIndex, restoredClip);
}
playlist.insert(restoredClip, currentIndex, info.frame_in, info.frame_out);
}
m_model.endInsertRows();
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(currentIndex));
Q_ASSERT(currentIndex < playlist.count());
Q_ASSERT(!clip.isNull());
if (info.isBlank) {
MLT.setUuid(*clip, uid);
} else {
MLT.setUuid(clip->parent(), uid);
}
if (info.group >= 0) {
clip->set(kShotcutGroupProperty, info.group);
}
AudioLevelsTask::start(clip->parent(), &m_model, modelIndex);
indexAdjustment[trackIndex]++;
}
/* Only in/out points handled so far */
if (info.changes & ClipInfoModified) {
int filterIn = MLT.filterIn(playlist, currentIndex);
int filterOut = MLT.filterOut(playlist, currentIndex);
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(currentIndex));
if (clip && clip->is_valid()) {
UNDOLOG << "resizing clip at" << currentIndex << "in" << info.frame_in << "out"
<< info.frame_out;
if (clip->parent().get_data("mlt_mix"))
clip->parent().set("mlt_mix", nullptr, 0);
if (clip->get_data("mix_in"))
clip->set("mix_in", nullptr, 0);
if (clip->get_data("mix_out"))
clip->set("mix_out", nullptr, 0);
playlist.resize_clip(currentIndex, info.frame_in, info.frame_out);
MLT.adjustClipFilters(clip->parent(),
filterIn,
filterOut,
info.in_delta,
info.out_delta,
info.in_delta);
}
QModelIndex modelIndex = m_model.createIndex(currentIndex, 0, info.oldTrackIndex);
QVector<int> roles;
roles << MultitrackModel::InPointRole;
roles << MultitrackModel::OutPointRole;
roles << MultitrackModel::DurationRole;
emit m_model.dataChanged(modelIndex, modelIndex, roles);
if (clip && clip->is_valid())
AudioLevelsTask::start(clip->parent(), &m_model, modelIndex);
}
}
/* Finally we walk through the tracks once more, removing clips that
* were added, and clearing the temporarily used uid property */
int trackIndex = 0;
foreach (const Track &track, m_model.trackList()) {
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(track.mlt_index));
Mlt::Playlist playlist(*trackProducer);
for (int i = playlist.count() - 1; i >= 0; --i) {
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(i));
QUuid uid = MLT.uuid(clip->parent());
if (clip->is_blank()) {
uid = MLT.uuid(*clip);
}
if (m_clipsAdded.removeOne(uid)) {
UNDOLOG << "Removing clip at" << i;
m_model.beginRemoveRows(m_model.index(trackIndex), i, i);
if (clip->parent().get_data("mlt_mix"))
clip->parent().set("mlt_mix", NULL, 0);
if (clip->get_data("mix_in"))
clip->set("mix_in", NULL, 0);
if (clip->get_data("mix_out"))
clip->set("mix_out", NULL, 0);
playlist.remove(i);
m_model.endRemoveRows();
}
}
trackIndex++;
}
emit m_model.modified();
#ifdef UNDOHELPER_DEBUG
debugPrintState("After undo");
#endif
}
void UndoHelper::setHints(OptimizationHints hints)
{
m_hints = hints;
}
void UndoHelper::debugPrintState(const QString &title)
{
LOG_DEBUG() << "timeline state:" << title << "{";
for (int i = 0; i < m_model.trackList().count(); ++i) {
int mltIndex = m_model.trackList()[i].mlt_index;
QString trackStr = QStringLiteral(" track %1 (mlt-idx %2):").arg(i).arg(mltIndex);
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(mltIndex));
Mlt::Playlist playlist(*trackProducer);
for (int j = 0; j < playlist.count(); ++j) {
Mlt::ClipInfo info;
playlist.clip_info(j, &info);
QUuid uid = MLT.uuid(*info.producer);
if (info.producer->is_blank() && info.cut) {
uid = MLT.uuid(*info.cut);
}
trackStr += QStringLiteral(" [ %5 %1 -> %2 (%3 frames) %4]")
.arg(info.frame_in)
.arg(info.frame_out)
.arg(info.frame_count)
.arg(info.cut->is_blank() ? "blank " : "clip")
.arg(uid.toString());
}
LOG_DEBUG() << qPrintable(trackStr);
}
LOG_DEBUG() << "}";
}
void UndoHelper::restoreAffectedTracks()
{
// Remove everything in the affected tracks.
for (const auto &trackIndex : std::as_const(m_affectedTracks)) {
if (trackIndex >= 0 && trackIndex < m_model.trackList().size()) {
auto mlt_index = m_model.trackList().at(trackIndex).mlt_index;
QScopedPointer<Mlt::Producer> producer(m_model.tractor()->track(mlt_index));
if (producer->is_valid()) {
Mlt::Playlist playlist(*producer.data());
m_model.beginRemoveRows(m_model.index(trackIndex), 0, playlist.count() - 1);
UNDOLOG << "clearing track" << trackIndex;
playlist.clear();
m_model.endRemoveRows();
}
}
}
for (const auto &uid : std::as_const(m_insertedOrder)) {
const Info &info = m_state[uid];
if (m_affectedTracks.contains(info.oldTrackIndex)) {
UNDOLOG << "Handling uid" << uid << "on track" << info.oldTrackIndex << "index"
<< info.oldClipIndex;
// Clips are restored using their stored XML.
int mltIndex = m_model.trackList()[info.oldTrackIndex].mlt_index;
QScopedPointer<Mlt::Producer> trackProducer(m_model.tractor()->track(mltIndex));
Mlt::Playlist playlist(*trackProducer);
auto currentIndex = playlist.count();
QModelIndex modelIndex = m_model.createIndex(currentIndex, 0, info.oldTrackIndex);
m_model.beginInsertRows(modelIndex.parent(), currentIndex, currentIndex);
if (info.isBlank) {
playlist.blank(info.frame_out - info.frame_in);
UNDOLOG << "appending blank at" << currentIndex << info.frame_out << info.frame_in;
} else {
UNDOLOG << "appending clip at" << currentIndex;
Q_ASSERT(!(m_hints & SkipXML) && "Cannot restore clip without stored XML");
Q_ASSERT(!info.xml.isEmpty());
Mlt::Producer restoredClip(MLT.profile(),
"xml-string",
info.xml.toUtf8().constData());
if (restoredClip.type() == mlt_service_tractor_type) { // transition
restoredClip.set("mlt_type", "mlt_producer");
}
playlist.append(restoredClip, info.frame_in, info.frame_out);
if (info.group >= 0) {
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(currentIndex));
clip->set(kShotcutGroupProperty, info.group);
}
}
m_model.endInsertRows();
QScopedPointer<Mlt::Producer> clip(playlist.get_clip(currentIndex));
Q_ASSERT(currentIndex < playlist.count());
Q_ASSERT(!clip.isNull());
if (info.isBlank) {
MLT.setUuid(*clip, uid);
} else {
MLT.setUuid(clip->parent(), uid);
}
AudioLevelsTask::start(clip->parent(), &m_model, modelIndex);
}
}
for (const auto &trackIndex : std::as_const(m_affectedTracks)) {
if (trackIndex >= 0 && trackIndex < m_model.trackList().size()) {
auto mlt_index = m_model.trackList().at(trackIndex).mlt_index;
QScopedPointer<Mlt::Producer> producer(m_model.tractor()->track(mlt_index));
if (producer->is_valid()) {
Mlt::Playlist playlist(*producer.data());
for (auto currentIndex = 0; currentIndex < playlist.count(); currentIndex++) {
Mlt::Producer clip = playlist.get_clip(currentIndex);
fixTransitions(playlist, currentIndex, clip);
}
}
}
}
}
void UndoHelper::fixTransitions(Mlt::Playlist playlist, int clipIndex, Mlt::Producer clip)
{
if (clip.is_blank()) {
return;
}
int transitionIndex = 0;
for (auto currentIndex : {clipIndex + 1, clipIndex - 1}) {
// Connect a transition on the right/left to the new producer.
Mlt::Producer producer(playlist.get_clip(currentIndex));
if (producer.is_valid() && producer.parent().get(kShotcutTransitionProperty)) {
Mlt::Tractor transition(producer.parent());
if (transition.is_valid()) {
QScopedPointer<Mlt::Producer> transitionClip(transition.track(transitionIndex));
if (transitionClip->is_valid()
&& transitionClip->parent().get_service() != clip.parent().get_service()) {
UNDOLOG << "Fixing transition at clip index" << currentIndex
<< "transition index" << transitionIndex;
transitionClip.reset(
clip.cut(transitionClip->get_in(), transitionClip->get_out()));
transition.set_track(*transitionClip.data(), transitionIndex);
}
}
}
transitionIndex++;
}
}

91
src/commands/undohelper.h Normal file
View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2015-2020 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UNDOHELPER_H
#define UNDOHELPER_H
#include "models/multitrackmodel.h"
#include <MltPlaylist.h>
#include <QList>
#include <QMap>
#include <QSet>
#include <QString>
class UndoHelper
{
public:
enum OptimizationHints { NoHints, SkipXML, RestoreTracks };
UndoHelper(MultitrackModel &model);
void recordBeforeState();
void recordAfterState();
void undoChanges();
void setHints(OptimizationHints hints);
QSet<int> affectedTracks() const { return m_affectedTracks; }
private:
void debugPrintState(const QString &title);
void restoreAffectedTracks();
void fixTransitions(Mlt::Playlist playlist, int clipIndex, Mlt::Producer clip);
enum ChangeFlags {
NoChange = 0x0,
ClipInfoModified = 0x1,
XMLModified = 0x2,
Moved = 0x4,
Removed = 0x8
};
struct Info
{
int oldTrackIndex;
int oldClipIndex;
int newTrackIndex;
int newClipIndex;
bool isBlank;
QString xml;
int frame_in;
int frame_out;
int in_delta;
int out_delta;
int group;
int changes;
Info()
: oldTrackIndex(-1)
, oldClipIndex(-1)
, newTrackIndex(-1)
, newClipIndex(-1)
, isBlank(false)
, frame_in(-1)
, frame_out(-1)
, in_delta(0)
, out_delta(0)
, changes(NoChange)
, group(-1)
{}
};
QMap<QUuid, Info> m_state;
QList<QUuid> m_clipsAdded;
QList<QUuid> m_insertedOrder;
QSet<int> m_affectedTracks;
MultitrackModel &m_model;
OptimizationHints m_hints;
};
#endif // UNDOHELPER_H