übernahme Code Shortcut
This commit is contained in:
415
src/commands/filtercommands.cpp
Normal file
415
src/commands/filtercommands.cpp
Normal 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
|
||||
243
src/commands/filtercommands.h
Normal file
243
src/commands/filtercommands.h
Normal 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
|
||||
128
src/commands/markercommands.cpp
Normal file
128
src/commands/markercommands.cpp
Normal 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
|
||||
89
src/commands/markercommands.h
Normal file
89
src/commands/markercommands.h
Normal 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
|
||||
555
src/commands/playlistcommands.cpp
Normal file
555
src/commands/playlistcommands.cpp
Normal 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
|
||||
254
src/commands/playlistcommands.h
Normal file
254
src/commands/playlistcommands.h
Normal 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
|
||||
343
src/commands/subtitlecommands.cpp
Normal file
343
src/commands/subtitlecommands.cpp
Normal 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
|
||||
188
src/commands/subtitlecommands.h
Normal file
188
src/commands/subtitlecommands.h
Normal 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
|
||||
2352
src/commands/timelinecommands.cpp
Normal file
2352
src/commands/timelinecommands.cpp
Normal file
File diff suppressed because it is too large
Load Diff
892
src/commands/timelinecommands.h
Normal file
892
src/commands/timelinecommands.h
Normal 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
454
src/commands/undohelper.cpp
Normal 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
91
src/commands/undohelper.h
Normal 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
|
||||
Reference in New Issue
Block a user