Files
oak-editor/engine/undo/undostack.cpp
T
Mike-Solar f95590e924 engine: internals supporting the facade migration
UndoStack push_pre_executed, FolderAddChild-based true folder moves,
NodeViewDeleteCommand delete-then-reconnect support, event emission
points for the facade event bus, and related internal adjustments.
2026-07-26 22:43:09 +08:00

308 lines
6.7 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "undostack.h"
#include <QCoreApplication>
namespace olive
{
const int UndoStack::k_max_undo_commands = 200;
class EmptyCommand : public UndoCommand {
public:
EmptyCommand()
{
}
virtual Project *get_relevant_project() const override
{
return nullptr;
}
protected:
virtual void redo() override
{
}
virtual void undo() override
{
}
};
UndoStack::UndoStack()
{
undo_action_ = new QAction();
connect(undo_action_, &QAction::triggered, this, &UndoStack::undo);
redo_action_ = new QAction();
connect(redo_action_, &QAction::triggered, this, &UndoStack::redo);
clear();
update_actions();
}
UndoStack::~UndoStack()
{
clear();
delete undo_action_;
delete redo_action_;
}
void UndoStack::push(UndoCommand *command, const QString &name)
{
MultiUndoCommand *mcu = dynamic_cast<MultiUndoCommand *>(command);
if (mcu && mcu->child_count() == 0) {
delete command;
return;
}
// Clear any redoable commands
this->beginRemoveRows(QModelIndex(), commands_.size(),
commands_.size() + undone_commands_.size());
if (can_redo()) {
for (auto it = undone_commands_.cbegin(); it != undone_commands_.cend();
it++) {
delete (*it).command;
}
undone_commands_.clear();
}
this->endRemoveRows();
// Do command and push
this->beginInsertRows(QModelIndex(), commands_.size(), commands_.size());
command->redo_and_set_modified();
commands_.push_back({ command, name });
this->endInsertRows();
// Delete oldest
if (commands_.size() > k_max_undo_commands) {
this->beginRemoveRows(QModelIndex(), 0, 0);
delete commands_.front().command;
commands_.pop_front();
this->endRemoveRows();
}
update_actions();
}
void UndoStack::push_pre_executed(UndoCommand *command, const QString &name)
{
MultiUndoCommand *mcu = dynamic_cast<MultiUndoCommand *>(command);
if (mcu && mcu->child_count() == 0) {
delete command;
return;
}
// Clear any redoable commands
this->beginRemoveRows(QModelIndex(), commands_.size(),
commands_.size() + undone_commands_.size());
if (can_redo()) {
for (auto it = undone_commands_.cbegin(); it != undone_commands_.cend();
it++) {
delete (*it).command;
}
undone_commands_.clear();
}
this->endRemoveRows();
// Push without redoing: the caller already executed the children.
this->beginInsertRows(QModelIndex(), commands_.size(), commands_.size());
commands_.push_back({ command, name });
this->endInsertRows();
// Delete oldest
if (commands_.size() > k_max_undo_commands) {
this->beginRemoveRows(QModelIndex(), 0, 0);
delete commands_.front().command;
commands_.pop_front();
this->endRemoveRows();
}
update_actions();
}
void UndoStack::jump(size_t index)
{
while (commands_.size() > index) {
undo();
}
while (commands_.size() < index) {
redo();
}
}
void UndoStack::undo()
{
if (can_undo()) {
// Undo most recently done command
commands_.back().command->undo_and_set_modified();
// Place at the front of the "undone commands" list
undone_commands_.push_front(commands_.back());
// Remove undone command from the commands list
commands_.pop_back();
// Update actions
update_actions();
}
}
void UndoStack::redo()
{
if (can_redo()) {
// Redo most recently undone command
undone_commands_.front().command->redo_and_set_modified();
// Place at the back of the done commands list
commands_.push_back(undone_commands_.front());
// Remove done command from undone list
undone_commands_.pop_front();
// Update actions
update_actions();
}
}
void UndoStack::clear()
{
this->beginResetModel();
for (auto it = commands_.cbegin(); it != commands_.cend(); it++) {
delete (*it).command;
}
commands_.clear();
for (auto it = undone_commands_.cbegin(); it != undone_commands_.cend();
it++) {
delete (*it).command;
}
undone_commands_.clear();
this->endResetModel();
push(new EmptyCommand(), tr("New/Open Project"));
}
bool UndoStack::can_undo() const
{
return !commands_.empty() &&
!dynamic_cast<EmptyCommand *>(commands_.back().command);
}
void UndoStack::update_actions()
{
undo_action_->setEnabled(can_undo());
redo_action_->setEnabled(can_redo());
undo_action_->setText(
QCoreApplication::translate("UndoStack", "Undo %1")
.arg(can_undo() ? commands_.back().name : QString()));
redo_action_->setText(
QCoreApplication::translate("UndoStack", "Redo %1")
.arg(can_redo() ? undone_commands_.front().name : QString()));
emit index_changed(commands_.size());
}
int UndoStack::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return 2;
}
QVariant UndoStack::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0:
return index.row() + 1;
case 1: {
std::list<CommandEntry>::const_iterator it;
size_t real_index = index.row();
if (real_index < commands_.size()) {
it = commands_.cbegin();
} else {
real_index -= commands_.size();
it = undone_commands_.cbegin();
}
for (size_t i = 0; i < real_index; i++) {
it++;
}
const QString &name = (*it).name;
return (name.isEmpty()) ? tr("Command") : name;
}
}
} else if (role == Qt::ForegroundRole) {
if (size_t(index.row()) >= commands_.size()) {
return QVariant(QColor(Qt::gray));
}
}
return QVariant();
}
QModelIndex UndoStack::index(int row, int column,
const QModelIndex &parent) const
{
return createIndex(row, column, nullptr);
}
QModelIndex UndoStack::parent(const QModelIndex &index) const
{
return QModelIndex();
}
int UndoStack::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return commands_.size() + undone_commands_.size();
}
QVariant UndoStack::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role == Qt::DisplayRole) {
switch (section) {
case 0:
return QStringLiteral("Number");
case 1:
return QStringLiteral("Action");
}
}
return QVariant();
}
bool UndoStack::hasChildren(const QModelIndex &parent) const
{
return !parent.isValid();
}
}