build: split the engine into liboakengine.so; worker drops the UI entirely

Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).

- oak-render-worker now links liboakengine instead of the whole
  libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
  objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
  macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
  ../app but backends now live in engine/; a stale pre-split liboakgl
  in the build tree got dlopened instead, re-initialized and later
  destroyed the interposed engine statics (full-suite segfault at
  DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
2026-07-20 03:23:28 +08:00
parent 026ff94b5e
commit 28c4426236
604 changed files with 243 additions and 172 deletions
-38
View File
@@ -1,38 +0,0 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2023 Olive Studios 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
timeline/timelinecommon.h
timeline/timelinecoordinate.h
timeline/timelinecoordinate.cpp
timeline/timelinemarker.h
timeline/timelinemarker.cpp
timeline/timelineundocommon.h
timeline/timelineundogeneral.cpp
timeline/timelineundogeneral.h
timeline/timelineundopointer.cpp
timeline/timelineundopointer.h
timeline/timelineundoripple.cpp
timeline/timelineundoripple.h
timeline/timelineundosplit.cpp
timeline/timelineundosplit.h
timeline/timelineundotrack.cpp
timeline/timelineundotrack.h
timeline/timelineworkarea.h
timeline/timelineworkarea.cpp
PARENT_SCOPE
)
-61
View File
@@ -1,61 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINECOMMON_H
#define OAK_TIMELINECOMMON_H
#include <olive/core/core.h>
#include "common/define.h"
using namespace olive::core;
namespace olive
{
class Block;
class Track;
class Timeline {
public:
enum MovementMode { k_none, k_move, k_trim_in, k_trim_out };
enum ThumbnailMode { k_thumbnail_off, k_thumbnail_in_out, k_thumbnail_on };
enum WaveformMode { k_waveforms_disabled, k_waveforms_enabled };
static bool is_a_trim_mode(MovementMode mode)
{
return mode == k_trim_in || mode == k_trim_out;
}
struct EditToInfo {
Track *track;
Rational nearest_time;
Block *nearest_block;
};
};
#define PLAYHEAD_COLOR palette().highlight().color()
}
#endif // OAK_TIMELINECOMMON_H
-67
View File
@@ -1,67 +0,0 @@
/***
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 "timelinecoordinate.h"
namespace olive
{
TimelineCoordinate::TimelineCoordinate()
: track_(Track::k_none, 0)
{
}
TimelineCoordinate::TimelineCoordinate(const Rational &frame,
const Track::Reference &track)
: frame_(frame)
, track_(track)
{
}
TimelineCoordinate::TimelineCoordinate(const Rational &frame,
const Track::Type &track_type,
const int &track_index)
: frame_(frame)
, track_(track_type, track_index)
{
}
const Rational &TimelineCoordinate::get_frame() const
{
return frame_;
}
const Track::Reference &TimelineCoordinate::get_track() const
{
return track_;
}
void TimelineCoordinate::set_frame(const Rational &frame)
{
frame_ = frame;
}
void TimelineCoordinate::set_track(const Track::Reference &track)
{
track_ = track;
}
}
-51
View File
@@ -1,51 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINECOORDINATE_H
#define OAK_TIMELINECOORDINATE_H
#include "node/output/track/track.h"
namespace olive
{
class TimelineCoordinate {
public:
TimelineCoordinate();
TimelineCoordinate(const Rational &frame, const Track::Reference &track);
TimelineCoordinate(const Rational &frame, const Track::Type &track_type,
const int &track_index);
const Rational &get_frame() const;
const Track::Reference &get_track() const;
void set_frame(const Rational &frame);
void set_track(const Track::Reference &track);
private:
Rational frame_;
Track::Reference track_;
};
}
#endif // OAK_TIMELINECOORDINATE_H
-433
View File
@@ -1,433 +0,0 @@
/***
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 "timelinemarker.h"
#include <QApplication>
#include "common/qtutils.h"
#include "common/xmlutils.h"
#include "config/config.h"
#include "node/project.h"
#include "ui/colorcoding.h"
namespace olive
{
TimelineMarker::TimelineMarker(QObject *parent)
: color_(OAK_CONFIG("MarkerColor").toInt())
{
setParent(parent);
}
TimelineMarker::TimelineMarker(int color, const TimeRange &time,
const QString &name, QObject *parent)
: time_(time)
, name_(name)
, color_(color)
{
setParent(parent);
}
void TimelineMarker::set_time(const TimeRange &time)
{
time_ = time;
emit time_changed(time_);
}
void TimelineMarker::set_time(const Rational &time)
{
set_time(TimeRange(time, time + time_.length()));
}
bool TimelineMarker::has_sibling_at_time(const Rational &t) const
{
TimelineMarker *m =
static_cast<TimelineMarkerList *>(parent())->get_marker_at_time(t);
return m && m != this;
}
void TimelineMarker::set_name(const QString &name)
{
name_ = name;
emit name_changed(name_);
}
void TimelineMarker::set_color(int c)
{
color_ = c;
emit color_changed(color_);
}
int TimelineMarker::get_marker_height(const QFontMetrics &fm)
{
return fm.height();
}
QRect TimelineMarker::draw(QPainter *p, const QPoint &pt, int max_right,
double scale, bool selected)
{
QFontMetrics fm = p->fontMetrics();
int marker_height = get_marker_height(fm);
int marker_width = QtUtils::q_font_metrics_width(fm, QStringLiteral("H"));
int half_width = marker_width / 2;
QColor c = QtUtils::to_q_color(ColorCoding::get_color(color()));
if (selected) {
p->setPen(Qt::white);
p->setBrush(c.lighter());
} else {
p->setPen(Qt::black);
p->setBrush(c);
}
int top = pt.y() - marker_height;
QTextOption op(Qt::AlignLeft | Qt::AlignVCenter);
op.setWrapMode(QTextOption::NoWrap);
if (time_.out() != time_.in()) {
QRect marker_rect(pt.x(), top, time_.length().to_double() * scale,
marker_height);
p->drawRect(marker_rect);
if (!name_.isEmpty()) {
p->setPen(
ColorCoding::get_ui_selector_color(ColorCoding::get_color(color_)));
p->drawText(marker_rect.adjusted(marker_width / 4, 0, 0, 0), name_,
op);
}
return marker_rect;
} else {
int half_marker_height = marker_height / 3;
int left = pt.x() - half_width;
int right = pt.x() + half_width;
int center_y = pt.y() - half_marker_height;
QPoint points[] = {
pt,
QPoint(left, center_y),
QPoint(left, top),
QPoint(right, top),
QPoint(right, center_y),
pt,
};
p->setRenderHint(QPainter::Antialiasing);
p->drawPolygon(points, 6);
if (!name_.isEmpty() && max_right != -1) {
QRect text_rect(right, top, max_right - right, marker_height);
int padding = QtUtils::q_font_metrics_width(p->fontMetrics(),
QStringLiteral(" "));
text_rect.adjust(padding, 0, -padding - half_width, 0);
p->setPen(qApp->palette().text().color());
p->drawText(text_rect, name_, op);
}
return QRect(left, top, marker_width, marker_height);
}
}
bool TimelineMarker::load(QXmlStreamReader *reader)
{
Rational in, out;
XMLAttributeLoop(reader, attr)
{
if (attr.name() == QStringLiteral("name")) {
this->set_name(attr.value().toString());
} else if (attr.name() == QStringLiteral("in")) {
in = Rational::from_string(attr.value().toString().toStdString());
} else if (attr.name() == QStringLiteral("out")) {
out = Rational::from_string(attr.value().toString().toStdString());
} else if (attr.name() == QStringLiteral("color")) {
this->set_color(attr.value().toInt());
}
}
this->set_time(TimeRange(in, out));
// This element has no inner text, so just skip it
reader->skipCurrentElement();
return true;
}
void TimelineMarker::save(QXmlStreamWriter *writer) const
{
writer->writeAttribute(QStringLiteral("name"), this->name());
writer->writeAttribute(
QStringLiteral("in"),
QString::fromStdString(this->time().in().to_string()));
writer->writeAttribute(
QStringLiteral("out"),
QString::fromStdString(this->time().out().to_string()));
writer->writeAttribute(QStringLiteral("color"),
QString::number(this->color()));
}
bool TimelineMarkerList::load(QXmlStreamReader *reader)
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == QStringLiteral("marker")) {
TimelineMarker *marker = new TimelineMarker(this);
if (!marker->load(reader)) {
return false;
}
} else {
reader->skipCurrentElement();
}
}
return true;
}
void TimelineMarkerList::save(QXmlStreamWriter *writer) const
{
for (auto it = this->cbegin(); it != this->cend(); it++) {
TimelineMarker *marker = *it;
writer->writeStartElement(QStringLiteral("marker"));
marker->save(writer);
writer->writeEndElement(); // marker
}
}
void TimelineMarkerList::childEvent(QChildEvent *e)
{
QObject::childEvent(e);
if (TimelineMarker *marker = dynamic_cast<TimelineMarker *>(e->child())) {
if (e->type() == QChildEvent::ChildAdded) {
connect(marker, &TimelineMarker::time_changed, this,
&TimelineMarkerList::handle_marker_time_change);
connect(marker, &TimelineMarker::time_changed, this,
&TimelineMarkerList::handle_marker_modification);
connect(marker, &TimelineMarker::name_changed, this,
&TimelineMarkerList::handle_marker_modification);
connect(marker, &TimelineMarker::color_changed, this,
&TimelineMarkerList::handle_marker_modification);
insert_into_list(marker);
emit marker_added(marker);
} else if (e->type() == QChildEvent::ChildRemoved) {
remove_from_list(marker);
disconnect(marker, &TimelineMarker::time_changed, this,
&TimelineMarkerList::handle_marker_time_change);
disconnect(marker, &TimelineMarker::time_changed, this,
&TimelineMarkerList::handle_marker_modification);
disconnect(marker, &TimelineMarker::name_changed, this,
&TimelineMarkerList::handle_marker_modification);
disconnect(marker, &TimelineMarker::color_changed, this,
&TimelineMarkerList::handle_marker_modification);
emit marker_removed(marker);
}
}
}
void TimelineMarkerList::insert_into_list(TimelineMarker *marker)
{
// Insertion sort by time to allow some loop optimizations
bool found = false;
for (auto it = markers_.begin(); it != markers_.end(); it++) {
TimelineMarker *m = *it;
Q_ASSERT(m->time().in() != marker->time().in());
if (m->time().in() > marker->time().in()) {
markers_.insert(it, marker);
found = true;
break;
}
}
if (!found) {
markers_.push_back(marker);
}
}
bool TimelineMarkerList::remove_from_list(TimelineMarker *marker)
{
auto it = std::find(markers_.begin(), markers_.end(), marker);
if (it != markers_.end()) {
markers_.erase(it);
return true;
}
return false;
}
void TimelineMarkerList::handle_marker_modification()
{
emit marker_modified(static_cast<TimelineMarker *>(sender()));
}
void TimelineMarkerList::handle_marker_time_change()
{
TimelineMarker *m = static_cast<TimelineMarker *>(sender());
auto it = std::find(markers_.begin(), markers_.end(), m);
if (it != markers_.end()) {
markers_.erase(it);
insert_into_list(m);
}
}
MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list,
const TimeRange &range, const QString &name,
int color)
: marker_list_(marker_list)
, added_marker_(nullptr)
{
added_marker_ = new TimelineMarker(color, range, name, &memory_manager_);
added_marker_->setParent(&memory_manager_);
}
MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list,
TimelineMarker *marker)
: marker_list_(marker_list)
, added_marker_(marker)
{
added_marker_->setParent(&memory_manager_);
}
Project *MarkerAddCommand::get_relevant_project() const
{
return Project::get_project_from_object(marker_list_);
}
void MarkerAddCommand::redo()
{
added_marker_->setParent(marker_list_);
}
void MarkerAddCommand::undo()
{
added_marker_->setParent(&memory_manager_);
}
MarkerRemoveCommand::MarkerRemoveCommand(TimelineMarker *marker)
: marker_(marker)
{
}
Project *MarkerRemoveCommand::get_relevant_project() const
{
return Project::get_project_from_object(marker_);
}
void MarkerRemoveCommand::redo()
{
marker_list_ = marker_->parent();
marker_->setParent(&memory_manager_);
}
void MarkerRemoveCommand::undo()
{
marker_->setParent(marker_list_);
}
MarkerChangeColorCommand::MarkerChangeColorCommand(TimelineMarker *marker,
int new_color)
: marker_(marker)
, new_color_(new_color)
{
}
Project *MarkerChangeColorCommand::get_relevant_project() const
{
return Project::get_project_from_object(marker_);
}
void MarkerChangeColorCommand::redo()
{
old_color_ = marker_->color();
marker_->set_color(new_color_);
}
void MarkerChangeColorCommand::undo()
{
marker_->set_color(old_color_);
}
MarkerChangeNameCommand::MarkerChangeNameCommand(TimelineMarker *marker,
QString new_name)
: marker_(marker)
, new_name_(new_name)
{
}
Project *MarkerChangeNameCommand::get_relevant_project() const
{
return Project::get_project_from_object(marker_);
}
void MarkerChangeNameCommand::redo()
{
old_name_ = marker_->name();
marker_->set_name(new_name_);
}
void MarkerChangeNameCommand::undo()
{
marker_->set_name(old_name_);
}
MarkerChangeTimeCommand::MarkerChangeTimeCommand(TimelineMarker *marker,
const TimeRange &time,
const TimeRange &old_time)
: marker_(marker)
, old_time_(old_time)
, new_time_(time)
{
}
Project *MarkerChangeTimeCommand::get_relevant_project() const
{
return Project::get_project_from_object(marker_);
}
void MarkerChangeTimeCommand::redo()
{
marker_->set_time(new_time_);
}
void MarkerChangeTimeCommand::undo()
{
marker_->set_time(old_time_);
}
}
-282
View File
@@ -1,282 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEMARKER_H
#define OAK_TIMELINEMARKER_H
#include <olive/core/core.h>
#include <QPainter>
#include <QString>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include "undo/undocommand.h"
using namespace olive::core;
namespace olive
{
class TimelineMarker : public QObject {
Q_OBJECT
public:
TimelineMarker(QObject *parent = nullptr);
TimelineMarker(int color, const TimeRange &time,
const QString &name = QString(), QObject *parent = nullptr);
const TimeRange &time() const
{
return time_;
}
void set_time(const TimeRange &time);
void set_time(const Rational &time);
bool has_sibling_at_time(const Rational &t) const;
const QString &name() const
{
return name_;
}
void set_name(const QString &name);
int color() const
{
return color_;
}
void set_color(int c);
static int get_marker_height(const QFontMetrics &fm);
QRect draw(QPainter *p, const QPoint &pt, int max_right, double scale,
bool selected);
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
signals:
void time_changed(const TimeRange &time);
void name_changed(const QString &name);
void color_changed(int c);
private:
TimeRange time_;
QString name_;
int color_;
};
class TimelineMarkerList : public QObject {
Q_OBJECT
public:
TimelineMarkerList(QObject *parent = nullptr)
: QObject(parent)
{
}
inline bool empty() const
{
return markers_.empty();
}
inline std::vector<TimelineMarker *>::iterator begin()
{
return markers_.begin();
}
inline std::vector<TimelineMarker *>::iterator end()
{
return markers_.end();
}
inline std::vector<TimelineMarker *>::const_iterator cbegin() const
{
return markers_.cbegin();
}
inline std::vector<TimelineMarker *>::const_iterator cend() const
{
return markers_.cend();
}
inline TimelineMarker *back() const
{
return markers_.back();
}
inline TimelineMarker *front() const
{
return markers_.front();
}
inline size_t size() const
{
return markers_.size();
}
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
TimelineMarker *get_marker_at_time(const Rational &t) const
{
for (auto it = markers_.cbegin(); it != markers_.cend(); it++) {
TimelineMarker *m = *it;
if (m->time().in() == t) {
return m;
}
}
return nullptr;
}
TimelineMarker *get_closest_marker_to_time(const Rational &t) const
{
TimelineMarker *closest = nullptr;
for (auto it = markers_.cbegin(); it != markers_.cend(); it++) {
TimelineMarker *m = *it;
Rational this_diff = qAbs(m->time().in() - t);
if (closest) {
Rational stored_diff = qAbs(closest->time().in() - t);
if (this_diff > stored_diff) {
// Since the list is organized by time, if the diff increases, assume we are only going
// to move further away from here and there's no need to check
break;
}
}
closest = m;
}
return closest;
}
signals:
void marker_added(TimelineMarker *marker);
void marker_removed(TimelineMarker *marker);
void marker_modified(TimelineMarker *marker);
protected:
virtual void childEvent(QChildEvent *e) override;
private:
void insert_into_list(TimelineMarker *m);
bool remove_from_list(TimelineMarker *m);
std::vector<TimelineMarker *> markers_;
private slots:
void handle_marker_modification();
void handle_marker_time_change();
};
class MarkerAddCommand : public UndoCommand {
public:
MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range,
const QString &name, int color);
MarkerAddCommand(TimelineMarkerList *marker_list, TimelineMarker *marker);
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineMarkerList *marker_list_;
TimelineMarker *added_marker_;
QObject memory_manager_;
};
class MarkerRemoveCommand : public UndoCommand {
public:
MarkerRemoveCommand(TimelineMarker *marker);
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineMarker *marker_;
QObject *marker_list_;
QObject memory_manager_;
};
class MarkerChangeColorCommand : public UndoCommand {
public:
MarkerChangeColorCommand(TimelineMarker *marker, int new_color);
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineMarker *marker_;
int old_color_;
int new_color_;
};
class MarkerChangeNameCommand : public UndoCommand {
public:
MarkerChangeNameCommand(TimelineMarker *marker, QString name);
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineMarker *marker_;
QString old_name_;
QString new_name_;
};
class MarkerChangeTimeCommand : public UndoCommand {
public:
MarkerChangeTimeCommand(TimelineMarker *marker, const TimeRange &time,
const TimeRange &old_time);
MarkerChangeTimeCommand(TimelineMarker *marker, const TimeRange &time)
: MarkerChangeTimeCommand(marker, time, marker->time())
{
}
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineMarker *marker_;
TimeRange old_time_;
TimeRange new_time_;
};
}
#endif // OAK_TIMELINEMARKER_H
-50
View File
@@ -1,50 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOCOMMON_H
#define OAK_TIMELINEUNDOCOMMON_H
#include "node/node.h"
#include "node/nodeundo.h"
namespace olive
{
inline bool node_can_be_removed(Node *n)
{
return n->output_connections().empty();
}
inline UndoCommand *create_remove_command(Node *n)
{
return new NodeRemoveWithExclusiveDependenciesAndDisconnect(n);
}
inline UndoCommand *create_and_run_remove_command(Node *n)
{
UndoCommand *command = create_remove_command(n);
command->redo_now();
return command;
}
}
#endif // OAK_TIMELINEUNDOCOMMON_H
-719
View File
@@ -1,719 +0,0 @@
/***
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 "timelineundogeneral.h"
#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
#include "node/factory.h"
#include "node/math/math/math.h"
#include "node/math/merge/merge.h"
#include "timelineundocommon.h"
#include "timelineundotrack.h"
namespace olive
{
//
// BlockResizeCommand
//
void BlockResizeCommand::redo()
{
old_length_ = block_->length();
block_->set_length_and_media_out(new_length_);
}
void BlockResizeCommand::undo()
{
block_->set_length_and_media_out(old_length_);
}
//
// BlockResizeWithMediaInCommand
//
void BlockResizeWithMediaInCommand::redo()
{
old_length_ = block_->length();
block_->set_length_and_media_in(new_length_);
}
void BlockResizeWithMediaInCommand::undo()
{
block_->set_length_and_media_in(old_length_);
}
//
// BlockSetMediaInCommand
//
void BlockSetMediaInCommand::redo()
{
old_media_in_ = block_->media_in();
block_->set_media_in(new_media_in_);
}
void BlockSetMediaInCommand::undo()
{
block_->set_media_in(old_media_in_);
}
//
// TimelineAddTrackCommand
//
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline,
bool automerge_tracks)
: timeline_(timeline)
, merge_(nullptr)
, position_command_(nullptr)
{
// Create new track
track_ = new Track();
track_->setParent(&memory_manager_);
// Determine what input to connect it to
QString relevant_input;
if (timeline_->type() == Track::k_video) {
relevant_input = Sequence::k_texture_input;
} else if (timeline_->type() == Track::k_audio) {
relevant_input = Sequence::k_samples_input;
}
// If we have an input to connect to, set it as our `direct` connection
if (!relevant_input.isEmpty()) {
direct_ = NodeInput(timeline_->parent(), relevant_input);
// If we're automerging and something is already connected, determine if/how to merge it
if (automerge_tracks && direct_.is_connected()) {
if (timeline_->type() == Track::k_video) {
// Use merge for video
merge_ = new MergeNode();
base_ = NodeInput(merge_, MergeNode::k_base_in);
blend_ = NodeInput(merge_, MergeNode::k_blend_in);
} else if (timeline_->type() == Track::k_audio) {
// Use math (add) for audio
merge_ = new MathNode();
base_ = NodeInput(merge_, MathNode::k_param_a_in);
blend_ = NodeInput(merge_, MathNode::k_param_b_in);
}
if (merge_) {
// If we got created a merge node, ensure it's parented
merge_->setParent(&memory_manager_);
}
}
}
}
void TimelineAddTrackCommand::redo()
{
// Get sequence
Sequence *sequence = timeline_->parent();
// Add track to sequence
track_->setParent(timeline_->get_parent_graph());
if (timeline_->get_track_count() > 0) {
track_->set_track_height(
timeline_->get_track_at(timeline_->get_track_count() - 1)
->get_track_height());
}
timeline_->array_append();
Node::connect_edge(track_,
timeline_->track_input(timeline_->array_size() - 1));
qreal position_factor = 0.5;
if (timeline_->type() == Track::k_video) {
position_factor = -position_factor;
}
bool create_pos_command =
(!position_command_ && (timeline_->type() == Track::k_video ||
timeline_->type() == Track::k_audio));
if (create_pos_command) {
position_command_ = new MultiUndoCommand();
}
// Add merge if applicable
if (merge_) {
// Determine what was previously connected
Node *previous_connection = direct_.get_connected_output();
// Add merge to graph
merge_->setParent(timeline_->get_parent_graph());
// Connect merge between what used to be here
Node::disconnect_edge(previous_connection, direct_);
Node::connect_edge(merge_, direct_);
Node::connect_edge(previous_connection, base_);
Node::connect_edge(track_, blend_);
if (create_pos_command) {
position_command_->add_child(new NodeSetPositionCommand(
track_, sequence,
sequence->get_node_position_in_context(sequence) +
QPointF(-1, -position_factor)));
position_command_->add_child(new NodeSetPositionCommand(
merge_, sequence,
sequence->get_node_position_in_context(sequence)));
position_command_->add_child(
new NodeSetPositionAndDependenciesRecursivelyCommand(
merge_, sequence,
sequence->get_node_position_in_context(sequence) +
QPointF(-1,
position_factor * timeline_->get_track_count())));
}
} else if (direct_.is_valid() && !direct_.is_connected()) {
// If no merge, we have a direct connection, and nothing else is connected, connect this
Node::connect_edge(track_, direct_);
if (create_pos_command) {
// Just position directly next to the context node
position_command_->add_child(new NodeSetPositionCommand(
track_, sequence,
sequence->get_node_position_in_context(sequence) +
QPointF(-1, position_factor)));
}
}
// Run position command if we created one
if (position_command_) {
position_command_->redo_now();
}
}
void TimelineAddTrackCommand::undo()
{
if (position_command_) {
position_command_->undo_now();
}
// Remove merge if applicable
if (merge_) {
Node *previous_connection = base_.get_connected_output();
Node::disconnect_edge(track_, blend_);
Node::disconnect_edge(previous_connection, base_);
Node::disconnect_edge(merge_, direct_);
Node::connect_edge(previous_connection, direct_);
merge_->setParent(&memory_manager_);
} else if (direct_.is_valid() && direct_.get_connected_output() == track_) {
Node::disconnect_edge(track_, direct_);
}
// Remove track
Node::disconnect_edge(track_,
timeline_->track_input(timeline_->array_size() - 1));
timeline_->array_remove_last();
track_->setParent(&memory_manager_);
}
//
// TransitionRemoveCommand
//
void TransitionRemoveCommand::redo()
{
track_ = block_->track();
out_block_ = block_->connected_out_block();
in_block_ = block_->connected_in_block();
Q_ASSERT(out_block_ || in_block_);
TimeRange invalidate_range(block_->in(), block_->out());
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() +
block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() +
block_->out_offset());
}
if (in_block_) {
Node::disconnect_edge(in_block_,
NodeInput(block_, TransitionBlock::k_in_block_input));
}
if (out_block_) {
Node::disconnect_edge(
out_block_, NodeInput(block_, TransitionBlock::k_out_block_input));
}
track_->ripple_remove_block(block_);
if (remove_from_graph_) {
if (!remove_command_) {
remove_command_ = create_remove_command(block_);
}
remove_command_->redo_now();
}
}
void TransitionRemoveCommand::undo()
{
if (remove_from_graph_) {
remove_command_->undo_now();
}
if (in_block_) {
track_->insert_block_before(block_, in_block_);
} else {
track_->insert_block_after(block_, out_block_);
}
if (in_block_) {
Node::connect_edge(in_block_,
NodeInput(block_, TransitionBlock::k_in_block_input));
}
if (out_block_) {
Node::connect_edge(out_block_,
NodeInput(block_, TransitionBlock::k_out_block_input));
}
// These if statements must be separated because in_offset and out_offset report different things
// if only one block is connected vs two. So we have to connect the blocks first before we have
// an accurate return value from these offset functions.
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() -
block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() -
block_->out_offset());
}
}
//
// TrackListInsertGaps
//
void TrackListInsertGaps::prepare()
{
// Determine if all tracks will be affected, which will allow us to make some optimizations
foreach (Track *track, track_list_->get_tracks()) {
if (track->is_locked()) {
continue;
}
working_tracks_.append(track);
}
QVector<Block *> blocks_to_split;
QVector<Block *> blocks_to_append_gap_to;
QVector<Track *> tracks_to_append_gap_to;
for (Track *track : qAsConst(working_tracks_)) {
for (Block *b : track->blocks()) {
if (dynamic_cast<GapBlock *>(b) && b->in() <= point_ &&
b->out() >= point_) {
// Found a gap at the location
gaps_to_extend_.append(b);
break;
} else if (dynamic_cast<ClipBlock *>(b) && b->out() >= point_) {
bool append_gap = true;
if (b->in() == point_) {
// The only reason we should be here is if this block is at the start of the track,
// in which case no split needs to occur
b = nullptr;
} else if (b->out() > point_) {
// Block must be split as well as having a gap appended to it
blocks_to_split.append(b);
} else if (!b->next()) {
// At the end of a track, no gap needs to be added at all
append_gap = false;
}
if (append_gap) {
tracks_to_append_gap_to.append(track);
blocks_to_append_gap_to.append(b);
}
break;
}
}
}
if (!blocks_to_split.isEmpty()) {
split_command_ =
new BlockSplitPreservingLinksCommand(blocks_to_split, { point_ });
}
for (int i = 0; i < blocks_to_append_gap_to.size(); i++) {
GapBlock *gap = new GapBlock();
gap->set_length_and_media_out(length_);
gap->setParent(&memory_manager_);
gaps_added_.append({ gap, blocks_to_append_gap_to.at(i),
tracks_to_append_gap_to.at(i) });
}
}
void TrackListInsertGaps::redo()
{
foreach (Block *gap, gaps_to_extend_) {
gap->set_length_and_media_out(gap->length() + length_);
}
if (split_command_) {
split_command_->redo_now();
}
foreach (auto add_gap, gaps_added_) {
add_gap.gap->setParent(add_gap.track->parent());
add_gap.track->insert_block_after(add_gap.gap, add_gap.before);
}
}
void TrackListInsertGaps::undo()
{
// Remove added gaps
foreach (auto add_gap, gaps_added_) {
add_gap.gap->track()->ripple_remove_block(add_gap.gap);
add_gap.gap->setParent(&memory_manager_);
}
// Un-split blocks
if (split_command_) {
split_command_->undo_now();
}
// Restore original length of gaps
foreach (Block *gap, gaps_to_extend_) {
gap->set_length_and_media_out(gap->length() - length_);
}
}
//
// TrackReplaceBlockWithGapCommand
//
void TrackReplaceBlockWithGapCommand::redo()
{
// Determine if this block is connected to any transitions that should also be removed by this operation
if (handle_transitions_ && transition_remove_commands_.isEmpty()) {
create_remove_transition_command_if_necessary(false);
create_remove_transition_command_if_necessary(true);
}
for (auto it = transition_remove_commands_.cbegin();
it != transition_remove_commands_.cend(); it++) {
(*it)->redo_now();
}
if (block_->next()) {
// Invalidate the range inhabited by this block
TimeRange invalidate_range(block_->in(), block_->out());
// Block has a next, which means it's NOT at the end of the sequence and thus requires a gap
Rational new_gap_length = block_->length();
Block *previous = block_->previous();
Block *next = block_->next();
bool previous_is_a_gap = dynamic_cast<GapBlock *>(previous);
bool next_is_a_gap = dynamic_cast<GapBlock *>(next);
if (previous_is_a_gap && next_is_a_gap) {
// Clip is preceded and followed by a gap, so we'll merge the two
existing_gap_ = static_cast<GapBlock *>(previous);
existing_merged_gap_ = static_cast<GapBlock *>(next);
new_gap_length += existing_merged_gap_->length();
track_->ripple_remove_block(existing_merged_gap_);
existing_merged_gap_->setParent(&memory_manager_);
} else if (previous_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock *>(previous);
} else if (next_is_a_gap) {
// Extend this gap to fill space left by block
existing_gap_ = static_cast<GapBlock *>(next);
}
if (existing_gap_) {
// Extend an existing gap
new_gap_length += existing_gap_->length();
existing_gap_->set_length_and_media_out(new_gap_length);
track_->ripple_remove_block(block_);
existing_gap_precedes_ = (existing_gap_ == previous);
} else {
// No gap exists to fill this space, create a new one and swap it in
if (!our_gap_) {
our_gap_ = new GapBlock();
our_gap_->set_length_and_media_out(new_gap_length);
}
our_gap_->setParent(track_->parent());
track_->replace_block(block_, our_gap_);
}
} else {
// Block is at the end of the track, simply remove it
Block *preceding = block_->previous();
track_->ripple_remove_block(block_);
// Determine if it's preceded by a gap, and remove that gap if so
if (dynamic_cast<GapBlock *>(preceding)) {
track_->ripple_remove_block(preceding);
preceding->setParent(&memory_manager_);
existing_merged_gap_ = static_cast<GapBlock *>(preceding);
}
}
}
void TrackReplaceBlockWithGapCommand::undo()
{
if (our_gap_ || existing_gap_) {
if (our_gap_) {
// We made this gap, simply swap our gap back
track_->replace_block(our_gap_, block_);
our_gap_->setParent(&memory_manager_);
} else {
// If we're here, assume that we extended an existing gap
Rational original_gap_length =
existing_gap_->length() - block_->length();
// If we merged two gaps together, restore the second one now
if (existing_merged_gap_) {
original_gap_length -= existing_merged_gap_->length();
existing_merged_gap_->setParent(track_->parent());
track_->insert_block_after(existing_merged_gap_, existing_gap_);
existing_merged_gap_ = nullptr;
}
// Restore original block
if (existing_gap_precedes_) {
track_->insert_block_after(block_, existing_gap_);
} else {
track_->insert_block_before(block_, existing_gap_);
}
// Restore gap's original length
existing_gap_->set_length_and_media_out(original_gap_length);
existing_gap_ = nullptr;
}
} else {
// Our gap and existing gap were both null, our block must have been at the end and thus
// required no gap extension/replacement
// However, we may have removed an unnecessary gap that preceded it
if (existing_merged_gap_) {
existing_merged_gap_->setParent(track_->parent());
track_->append_block(existing_merged_gap_);
existing_merged_gap_ = nullptr;
}
// Restore block
track_->append_block(block_);
}
for (auto it = transition_remove_commands_.crbegin();
it != transition_remove_commands_.crend(); it++) {
(*it)->undo_now();
}
}
void TrackReplaceBlockWithGapCommand::create_remove_transition_command_if_necessary(
bool next)
{
Block *relevant_block;
if (next) {
relevant_block = block_->next();
} else {
relevant_block = block_->previous();
}
TransitionBlock *transition_cast_test =
dynamic_cast<TransitionBlock *>(relevant_block);
if (transition_cast_test) {
if ((next && transition_cast_test->connected_out_block() == block_ &&
!transition_cast_test->connected_in_block()) ||
(!next && transition_cast_test->connected_in_block() == block_ &&
!transition_cast_test->connected_out_block())) {
TransitionRemoveCommand *command =
new TransitionRemoveCommand(transition_cast_test, true);
transition_remove_commands_.append(command);
}
}
}
void TimelineRemoveTrackCommand::prepare()
{
list_ = track_->sequence()->track_list(track_->type());
index_ = list_->get_array_index_from_cache_index(track_->index());
remove_command_ =
new NodeRemoveWithExclusiveDependenciesAndDisconnect(track_);
}
void TimelineRemoveTrackCommand::redo()
{
remove_command_->redo_now();
list_->parent()->input_array_remove(list_->track_input(), index_);
}
void TimelineRemoveTrackCommand::undo()
{
list_->parent()->input_array_insert(list_->track_input(), index_);
remove_command_->undo_now();
}
void TimelineAddDefaultTransitionCommand::prepare()
{
for (auto it = clips_.cbegin(); it != clips_.cend(); it++) {
ClipBlock *c = *it;
// Handle in transition
if (clips_.contains(static_cast<ClipBlock *>(c->previous()))) {
// Do nothing, assume this will be handled by a dual transition from that clip
} else if (dynamic_cast<GapBlock *>(c->previous()) || !c->previous()) {
// Create in transition
add_transition(c, k_in);
}
// Handle out transition
if (clips_.contains(static_cast<ClipBlock *>(c->next()))) {
add_transition(c, k_out_dual);
} else if (dynamic_cast<GapBlock *>(c->next()) || !c->next()) {
// Create out transition
add_transition(c, k_out);
}
}
}
void TimelineAddDefaultTransitionCommand::add_transition(
ClipBlock *c, CreateTransitionMode mode)
{
if (Track *t = c->track()) {
Node *p = nullptr;
if (t->type() == Track::k_video) {
p = NodeFactory::create_from_id(
OAK_CONFIG("DefaultVideoTransition").toString());
} else if (t->type() == Track::k_audio) {
p = NodeFactory::create_from_id(
OAK_CONFIG("DefaultAudioTransition").toString());
}
Rational transition_length =
OAK_CONFIG("DefaultTransitionLength").value<Rational>();
// Resize original clip
switch (mode) {
case k_in:
validate_transition_length(c, transition_length);
if (transition_length > 0) {
adjust_clip_length(c, transition_length, false);
}
break;
case k_out:
validate_transition_length(c, transition_length);
if (transition_length > 0) {
adjust_clip_length(c, transition_length, true);
}
break;
case k_out_dual: {
Rational half_length = transition_length / 2;
validate_transition_length(static_cast<ClipBlock *>(c->next()),
half_length);
validate_transition_length(c, half_length);
transition_length = half_length * 2;
if (transition_length > 0) {
adjust_clip_length(static_cast<ClipBlock *>(c->next()),
half_length, false);
adjust_clip_length(c, half_length, true);
}
break;
}
}
if (transition_length > 0) {
if (TransitionBlock *transition =
dynamic_cast<TransitionBlock *>(p)) {
transition->set_length_and_media_out(transition_length);
// Add transition
commands_.append(new NodeAddCommand(c->parent(), transition));
// Insert block
Block *insert_after = (mode == k_in) ? c->previous() : c;
commands_.append(new TrackInsertBlockAfterCommand(
c->track(), transition, insert_after));
// Connect
switch (mode) {
case k_in:
commands_.append(new NodeEdgeAddCommand(
c,
NodeInput(transition, TransitionBlock::k_in_block_input)));
break;
case k_out_dual:
commands_.append(new NodeEdgeAddCommand(
c->next(),
NodeInput(transition, TransitionBlock::k_in_block_input)));
/* fall through */
case k_out:
commands_.append(new NodeEdgeAddCommand(
c, NodeInput(transition,
TransitionBlock::k_out_block_input)));
break;
}
}
}
}
}
void TimelineAddDefaultTransitionCommand::adjust_clip_length(
ClipBlock *c, const Rational &transition_length, bool out)
{
Rational cur_len = lengths_.value(c, c->length());
Rational new_len = cur_len - transition_length;
if (out) {
commands_.append(new BlockResizeCommand(c, new_len));
} else {
commands_.append(new BlockResizeWithMediaInCommand(c, new_len));
}
lengths_.insert(c, new_len);
}
void TimelineAddDefaultTransitionCommand::validate_transition_length(
ClipBlock *c, Rational &transition_length)
{
Rational cur_len = lengths_.value(c, c->length());
Rational half_cur_len = cur_len / 2;
if (transition_length >= half_cur_len) {
transition_length = half_cur_len - timebase_;
}
}
}
-412
View File
@@ -1,412 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOGENERAL_H
#define OAK_TIMELINEUNDOGENERAL_H
#include "config/config.h"
#include "node/block/clip/clip.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "node/output/viewer/viewer.h"
#include "node/project/sequence/sequence.h"
#include "timelineundosplit.h"
namespace olive
{
class BlockResizeCommand : public UndoCommand {
public:
BlockResizeCommand(Block *block, Rational new_length)
: block_(block)
, new_length_(new_length)
{
}
virtual Project *get_relevant_project() const override
{
return block_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
Block *block_;
Rational old_length_;
Rational new_length_;
};
class BlockResizeWithMediaInCommand : public UndoCommand {
public:
BlockResizeWithMediaInCommand(Block *block, Rational new_length)
: block_(block)
, new_length_(new_length)
{
}
virtual Project *get_relevant_project() const
{
return block_->project();
}
protected:
virtual void redo();
virtual void undo();
private:
Block *block_;
Rational old_length_;
Rational new_length_;
};
class BlockSetMediaInCommand : public UndoCommand {
public:
BlockSetMediaInCommand(ClipBlock *block, Rational new_media_in)
: block_(block)
, new_media_in_(new_media_in)
{
}
virtual Project *get_relevant_project() const
{
return block_->project();
}
protected:
virtual void redo();
virtual void undo();
private:
ClipBlock *block_;
Rational old_media_in_;
Rational new_media_in_;
};
class TimelineAddTrackCommand : public UndoCommand {
public:
TimelineAddTrackCommand(TrackList *timeline)
: TimelineAddTrackCommand(timeline,
OAK_CONFIG("AutoMergeTracks").toBool())
{
}
TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks);
static Track *run_immediately(TrackList *timeline)
{
TimelineAddTrackCommand c(timeline);
c.redo();
return c.track();
}
static Track *run_immediately(TrackList *timeline, bool automerge)
{
TimelineAddTrackCommand c(timeline, automerge);
c.redo();
return c.track();
}
virtual ~TimelineAddTrackCommand() override
{
delete position_command_;
}
Track *track() const
{
return track_;
}
virtual Project *get_relevant_project() const override
{
return timeline_->parent()->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
TrackList *timeline_;
Track *track_;
Node *merge_;
NodeInput base_;
NodeInput blend_;
NodeInput direct_;
MultiUndoCommand *position_command_;
QObject memory_manager_;
};
class TimelineRemoveTrackCommand : public UndoCommand {
public:
TimelineRemoveTrackCommand(Track *track)
: track_(track)
, remove_command_(nullptr)
{
}
virtual ~TimelineRemoveTrackCommand()
{
delete remove_command_;
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
Track *track_;
TrackList *list_;
int index_;
UndoCommand *remove_command_;
};
class TransitionRemoveCommand : public UndoCommand {
public:
TransitionRemoveCommand(TransitionBlock *block, bool remove_from_graph)
: block_(block)
, track_(block->track())
, remove_from_graph_(remove_from_graph)
, remove_command_(nullptr)
{
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
TransitionBlock *block_;
Track *track_;
Block *out_block_;
Block *in_block_;
bool remove_from_graph_;
UndoCommand *remove_command_;
};
class TrackReplaceBlockWithGapCommand : public UndoCommand {
public:
TrackReplaceBlockWithGapCommand(Track *track, Block *block,
bool handle_transitions = true)
: track_(track)
, block_(block)
, existing_gap_(nullptr)
, existing_merged_gap_(nullptr)
, our_gap_(nullptr)
, handle_transitions_(handle_transitions)
{
}
virtual Project *get_relevant_project() const override
{
return block_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
void create_remove_transition_command_if_necessary(bool next);
Track *track_;
Block *block_;
GapBlock *existing_gap_;
GapBlock *existing_merged_gap_;
bool existing_gap_precedes_;
GapBlock *our_gap_;
bool handle_transitions_;
QObject memory_manager_;
QVector<TransitionRemoveCommand *> transition_remove_commands_;
};
class BlockEnableDisableCommand : public UndoCommand {
public:
BlockEnableDisableCommand(Block *block, bool enabled)
: block_(block)
, old_enabled_(block_->is_enabled())
, new_enabled_(enabled)
{
}
virtual Project *get_relevant_project() const override
{
return block_->project();
}
protected:
virtual void redo() override
{
block_->set_enabled(new_enabled_);
}
virtual void undo() override
{
block_->set_enabled(old_enabled_);
}
private:
Block *block_;
bool old_enabled_;
bool new_enabled_;
};
class TrackListInsertGaps : public UndoCommand {
public:
TrackListInsertGaps(TrackList *track_list, const Rational &point,
const Rational &length)
: track_list_(track_list)
, point_(point)
, length_(length)
, split_command_(nullptr)
{
}
virtual ~TrackListInsertGaps() override
{
delete split_command_;
}
virtual Project *get_relevant_project() const override
{
return track_list_->parent()->project();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
TrackList *track_list_;
Rational point_;
Rational length_;
QVector<Track *> working_tracks_;
QVector<Block *> gaps_to_extend_;
struct AddGap {
GapBlock *gap;
Block *before;
Track *track;
};
QVector<AddGap> gaps_added_;
BlockSplitPreservingLinksCommand *split_command_;
QObject memory_manager_;
};
class TimelineAddDefaultTransitionCommand : public UndoCommand {
public:
TimelineAddDefaultTransitionCommand(const QVector<ClipBlock *> &clips,
const Rational &timebase)
: clips_(clips)
, timebase_(timebase)
{
}
virtual ~TimelineAddDefaultTransitionCommand() override
{
qDeleteAll(commands_);
}
virtual Project *get_relevant_project() const override
{
return clips_.empty() ? nullptr : clips_.first()->project();
}
protected:
virtual void prepare() override;
virtual void redo() override
{
for (auto it = commands_.cbegin(); it != commands_.cend(); it++) {
(*it)->redo_now();
}
}
virtual void undo() override
{
for (auto it = commands_.crbegin(); it != commands_.crend(); it++) {
(*it)->undo_now();
}
}
private:
enum CreateTransitionMode { k_in, k_out, k_out_dual };
void add_transition(ClipBlock *c, CreateTransitionMode mode);
void adjust_clip_length(ClipBlock *c, const Rational &transition_length,
bool out);
void validate_transition_length(ClipBlock *c, Rational &transition_length);
QVector<ClipBlock *> clips_;
Rational timebase_;
QVector<UndoCommand *> commands_;
QHash<ClipBlock *, Rational> lengths_;
};
}
#endif // OAK_TIMELINEUNDOGENERAL_H
-401
View File
@@ -1,401 +0,0 @@
/***
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 "timelineundopointer.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "node/project.h"
#include "timelineundocommon.h"
namespace olive
{
//
// BlockTrimCommand
//
void BlockTrimCommand::redo()
{
if (doing_nothing_) {
return;
}
// Determine how much time to invalidate
TimeRange invalidate_range;
if (mode_ == Timeline::k_trim_in) {
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_);
block_->set_length_and_media_in(new_length_);
} else {
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_);
block_->set_length_and_media_out(new_length_);
}
if (needs_adjacent_) {
if (we_created_adjacent_) {
// Add adjacent and insert it
adjacent_->setParent(track_->parent());
if (mode_ == Timeline::k_trim_in) {
track_->insert_block_before(adjacent_, block_);
} else {
track_->insert_block_after(adjacent_, block_);
}
} else if (we_removed_adjacent_) {
track_->ripple_remove_block(adjacent_);
// It no longer inputs/outputs anything, remove it
if (remove_block_from_graph_ && node_can_be_removed(adjacent_)) {
if (!deleted_adjacent_command_) {
deleted_adjacent_command_ =
create_and_run_remove_command(adjacent_);
} else {
deleted_adjacent_command_->redo_now();
}
}
} else {
Rational adjacent_length = adjacent_->length() + trim_diff_;
if (mode_ == Timeline::k_trim_in) {
adjacent_->set_length_and_media_out(adjacent_length);
} else {
adjacent_->set_length_and_media_in(adjacent_length);
}
}
}
if (dynamic_cast<TransitionBlock *>(block_)) {
// Whole transition needs to be invalidated
invalidate_range = block_->range();
}
}
void BlockTrimCommand::undo()
{
if (doing_nothing_) {
return;
}
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
if (needs_adjacent_) {
if (we_created_adjacent_) {
// Adjacent is ours, just delete it
track_->ripple_remove_block(adjacent_);
adjacent_->setParent(&memory_manager_);
} else {
if (we_removed_adjacent_) {
if (deleted_adjacent_command_) {
// We deleted adjacent, restore it now
deleted_adjacent_command_->undo_now();
}
if (mode_ == Timeline::k_trim_in) {
track_->insert_block_before(adjacent_, block_);
} else {
track_->insert_block_after(adjacent_, block_);
}
} else {
Rational adjacent_length = adjacent_->length() - trim_diff_;
if (mode_ == Timeline::k_trim_in) {
adjacent_->set_length_and_media_out(adjacent_length);
} else {
adjacent_->set_length_and_media_in(adjacent_length);
}
}
}
}
TimeRange invalidate_range;
if (mode_ == Timeline::k_trim_in) {
block_->set_length_and_media_in(old_length_);
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_);
} else {
block_->set_length_and_media_out(old_length_);
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_);
}
if (dynamic_cast<TransitionBlock *>(block_)) {
// Whole transition needs to be invalidated
invalidate_range = block_->range();
}
}
void BlockTrimCommand::prepare()
{
// Store old length
old_length_ = block_->length();
// Determine if the length isn't changing, in which case we set a flag to do nothing
if ((doing_nothing_ = (old_length_ == new_length_))) {
return;
}
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
trim_diff_ = old_length_ - new_length_;
// Retrieve our adjacent block (or nullptr if none)
if (mode_ == Timeline::k_trim_in) {
adjacent_ = block_->previous();
} else {
adjacent_ = block_->next();
}
// Ignore when trimming the out with no adjacent, because the user must have trimmed the end
// of the last block in the track, so we don't need to do anything elses
needs_adjacent_ = (mode_ == Timeline::k_trim_in || adjacent_);
if (needs_adjacent_) {
// If we're trimming shorter, we need an adjacent, so check if we have a viable one.
we_created_adjacent_ =
(trim_diff_ > 0 &&
(!adjacent_ ||
(!dynamic_cast<GapBlock *>(adjacent_) && !trim_is_a_roll_edit_)));
if (we_created_adjacent_) {
// We shortened but don't have a viable adjacent to lengthen, so we create one
adjacent_ = new GapBlock();
adjacent_->set_length_and_media_out(trim_diff_);
} else {
// Determine if we're removing the adjacent
Rational adjacent_length = adjacent_->length() + trim_diff_;
we_removed_adjacent_ = adjacent_length.isNull();
}
}
}
//
// TrackSlideCommand
//
void TrackSlideCommand::redo()
{
// Make sure all movement blocks' old positions are invalidated
TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out());
// We will always have an in adjacent if there was a valid slide
if (we_created_in_adjacent_) {
// We created in adjacent, so all we have to do is insert it
in_adjacent_->setParent(track_->parent());
track_->insert_block_before(in_adjacent_, blocks_.first());
} else if (-movement_ == in_adjacent_->length()) {
// Movement will remove in adjacent
track_->ripple_remove_block(in_adjacent_);
if (node_can_be_removed(in_adjacent_)) {
if (!in_adjacent_remove_command_) {
in_adjacent_remove_command_ = create_remove_command(in_adjacent_);
}
in_adjacent_remove_command_->redo_now();
}
we_removed_in_adjacent_ = true;
} else {
// Simply resize adjacent
in_adjacent_->set_length_and_media_out(in_adjacent_->length() +
movement_);
}
// We may not have an out adjacent if the slide was at the end of the track
if (out_adjacent_) {
if (we_created_out_adjacent_) {
// We created out adjacent, so we just have to insert it
out_adjacent_->setParent(track_->parent());
track_->insert_block_after(out_adjacent_, blocks_.last());
} else if (movement_ == out_adjacent_->length()) {
// Movement will remove out adjacent
track_->ripple_remove_block(out_adjacent_);
if (node_can_be_removed(out_adjacent_)) {
if (!out_adjacent_remove_command_) {
out_adjacent_remove_command_ =
create_remove_command(out_adjacent_);
}
out_adjacent_remove_command_->redo_now();
}
we_removed_out_adjacent_ = true;
} else {
// Simply resize adjacent
out_adjacent_->set_length_and_media_in(out_adjacent_->length() -
movement_);
}
}
// Make sure all movement blocks' new positions are invalidated
invalidate_range.set_range(
qMin(invalidate_range.in(), blocks_.first()->in()),
qMax(invalidate_range.out(), blocks_.last()->out()));
}
void TrackSlideCommand::undo()
{
// Make sure all movement blocks' old positions are invalidated
TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out());
if (we_created_in_adjacent_) {
// We created this, so we can remove it now
track_->ripple_remove_block(in_adjacent_);
in_adjacent_->setParent(&memory_manager_);
} else if (we_removed_in_adjacent_) {
if (in_adjacent_remove_command_) {
// We removed this, so we can restore it now
in_adjacent_remove_command_->undo_now();
}
track_->insert_block_before(in_adjacent_, blocks_.first());
} else {
// Simply resize adjacent
in_adjacent_->set_length_and_media_out(in_adjacent_->length() -
movement_);
}
if (out_adjacent_) {
if (we_created_out_adjacent_) {
// We created this, so we can remove it now
track_->ripple_remove_block(out_adjacent_);
out_adjacent_->setParent(&memory_manager_);
} else if (we_removed_out_adjacent_) {
if (out_adjacent_remove_command_) {
out_adjacent_remove_command_->undo_now();
}
track_->insert_block_after(out_adjacent_, blocks_.last());
} else {
out_adjacent_->set_length_and_media_in(out_adjacent_->length() +
movement_);
}
}
// Make sure all movement blocks' new positions are invalidated
invalidate_range.set_range(
qMin(invalidate_range.in(), blocks_.first()->in()),
qMax(invalidate_range.out(), blocks_.last()->out()));
}
void TrackSlideCommand::prepare()
{
if (!in_adjacent_) {
in_adjacent_ = new GapBlock();
in_adjacent_->set_length_and_media_out(movement_);
in_adjacent_->setParent(&memory_manager_);
we_created_in_adjacent_ = true;
} else {
we_created_in_adjacent_ = false;
}
if (!out_adjacent_ && blocks_.last()->next()) {
out_adjacent_ = new GapBlock();
out_adjacent_->set_length_and_media_out(-movement_);
out_adjacent_->setParent(&memory_manager_);
we_created_out_adjacent_ = true;
} else {
we_created_out_adjacent_ = false;
}
}
//
// TrackPlaceBlockCommand
//
TrackPlaceBlockCommand::~TrackPlaceBlockCommand()
{
delete ripple_remove_command_;
qDeleteAll(add_track_commands_);
}
void TrackPlaceBlockCommand::redo()
{
// Determine if we need to add tracks
if (track_index_ >= timeline_->get_tracks().size()) {
if (add_track_commands_.isEmpty()) {
// First redo, create tracks now
add_track_commands_.resize(track_index_ -
timeline_->get_tracks().size() + 1);
for (int i = 0; i < add_track_commands_.size(); i++) {
add_track_commands_[i] = new TimelineAddTrackCommand(timeline_);
}
}
for (int i = 0; i < add_track_commands_.size(); i++) {
add_track_commands_.at(i)->redo_now();
}
}
Track *track = timeline_->get_track_at(track_index_);
bool append = (in_ >= track->track_length());
// Check if the placement location is past the end of the timeline
if (append) {
if (in_ > track->track_length()) {
// If so, insert a gap here
if (!gap_) {
gap_ = new GapBlock();
gap_->set_length_and_media_out(in_ - track->track_length());
}
gap_->setParent(track->parent());
track->append_block(gap_);
}
track->append_block(insert_);
} else {
// Place the Block at this point
if (!ripple_remove_command_) {
ripple_remove_command_ = new TrackRippleRemoveAreaCommand(
track, TimeRange(in_, in_ + insert_->length()));
ripple_remove_command_->set_allow_splitting_gaps(true);
}
ripple_remove_command_->redo_now();
track->insert_block_after(insert_,
ripple_remove_command_->get_insertion_index());
}
}
void TrackPlaceBlockCommand::undo()
{
Track *t = timeline_->get_track_at(track_index_);
TimeRange insert_range(insert_->in(), insert_->out());
// Firstly, remove our insert
t->ripple_remove_block(insert_);
if (ripple_remove_command_) {
// If we ripple removed, just undo that
ripple_remove_command_->undo_now();
} else if (gap_) {
t->ripple_remove_block(gap_);
gap_->setParent(&memory_manager_);
}
// Remove tracks if we added them
for (int i = add_track_commands_.size() - 1; i >= 0; i--) {
add_track_commands_.at(i)->undo_now();
}
}
}
-214
View File
@@ -1,214 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOPOINTER_H
#define OAK_TIMELINEUNDOPOINTER_H
#include "node/block/gap/gap.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "node/project/sequence/sequence.h"
#include "timelineundogeneral.h"
#include "timelineundoripple.h"
namespace olive
{
/**
* @brief Performs a trim in the timeline that only affects the block and the block adjacent
*
* Changes the length of one block while also changing the length of the block directly adjacent
* to compensate so that the rest of the track is unaffected.
*
* By default, this will only affect the length of gaps. If the adjacent needs to increase its
* length and is not a gap, a gap will be created and inserted to fill that time. This command can
* be set to always trim even if the adjacent clip isn't a gap with SetTrimIsARollEdit()
*/
class BlockTrimCommand : public UndoCommand {
public:
BlockTrimCommand(Track *track, Block *block, Rational new_length,
Timeline::MovementMode mode)
: track_(track)
, block_(block)
, new_length_(new_length)
, mode_(mode)
, deleted_adjacent_command_(nullptr)
, trim_is_a_roll_edit_(false)
, remove_block_from_graph_(true)
{
}
virtual ~BlockTrimCommand() override
{
delete deleted_adjacent_command_;
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
/**
* @brief Set this if the trim should always affect the adjacent clip and not create a gap
*/
void set_trim_is_a_roll_edit(bool e)
{
trim_is_a_roll_edit_ = e;
}
/**
* @brief Set whether adjacent blocks set to zero length should be removed from the whole graph
*
* If an adjacent block's length is set to 0, it's automatically removed from the track. By
* default it also gets removed from the whole graph. Set this to FALSE to disable that
* functionality.
*/
void set_remove_zero_length_from_graph(bool e)
{
remove_block_from_graph_ = e;
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
bool doing_nothing_;
Rational trim_diff_;
Track *track_;
Block *block_;
Rational old_length_;
Rational new_length_;
Timeline::MovementMode mode_;
Block *adjacent_;
bool needs_adjacent_;
bool we_created_adjacent_;
bool we_removed_adjacent_;
UndoCommand *deleted_adjacent_command_;
bool trim_is_a_roll_edit_;
bool remove_block_from_graph_;
QObject memory_manager_;
};
class TrackSlideCommand : public UndoCommand {
public:
TrackSlideCommand(Track *track, const QList<Block *> &moving_blocks,
Block *in_adjacent, Block *out_adjacent,
const Rational &movement)
: track_(track)
, blocks_(moving_blocks)
, movement_(movement)
, we_removed_in_adjacent_(false)
, in_adjacent_(in_adjacent)
, in_adjacent_remove_command_(nullptr)
, we_removed_out_adjacent_(false)
, out_adjacent_(out_adjacent)
, out_adjacent_remove_command_(nullptr)
{
Q_ASSERT(!movement_.isNull());
}
virtual ~TrackSlideCommand() override
{
delete in_adjacent_remove_command_;
delete out_adjacent_remove_command_;
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
Track *track_;
QList<Block *> blocks_;
Rational movement_;
bool we_created_in_adjacent_;
bool we_removed_in_adjacent_;
Block *in_adjacent_;
UndoCommand *in_adjacent_remove_command_;
bool we_created_out_adjacent_;
bool we_removed_out_adjacent_;
Block *out_adjacent_;
UndoCommand *out_adjacent_remove_command_;
QObject memory_manager_;
};
/**
* @brief Destructively places `block` at the in point `start`
*
* The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are
* either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of
* the Sequence, a GapBlock is inserted to compensate.
*/
class TrackPlaceBlockCommand : public UndoCommand {
public:
TrackPlaceBlockCommand(TrackList *timeline, int track, Block *block,
Rational in)
: timeline_(timeline)
, track_index_(track)
, in_(in)
, gap_(nullptr)
, insert_(block)
, ripple_remove_command_(nullptr)
{
}
virtual ~TrackPlaceBlockCommand() override;
virtual Project *get_relevant_project() const override
{
return timeline_->parent()->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
TrackList *timeline_;
int track_index_;
Rational in_;
GapBlock *gap_;
Block *insert_;
QVector<TimelineAddTrackCommand *> add_track_commands_;
QObject memory_manager_;
TrackRippleRemoveAreaCommand *ripple_remove_command_;
};
}
#endif // OAK_TIMELINEUNDOPOINTER_H
-530
View File
@@ -1,530 +0,0 @@
/***
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 "timelineundoripple.h"
#include "timelineundocommon.h"
namespace olive
{
//
// TrackRippleRemoveAreaCommand
//
TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(
Track *track, const TimeRange &range)
: track_(track)
, range_(range)
, allow_splitting_gaps_(false)
, splice_split_command_(nullptr)
{
trim_out_.block = nullptr;
trim_in_.block = nullptr;
}
TrackRippleRemoveAreaCommand::~TrackRippleRemoveAreaCommand()
{
delete splice_split_command_;
qDeleteAll(remove_block_commands_);
}
void TrackRippleRemoveAreaCommand::prepare()
{
// Determine precisely what will be happening to these tracks
Block *first_block = track_->nearest_block_before_or_at(range_.in());
if (!first_block) {
// No blocks at this time, nothing to be done on this track
return;
}
// Determine if this first block is getting trimmed or removed
bool first_block_is_out_trimmed = first_block->in() < range_.in();
bool first_block_is_in_trimmed = first_block->out() > range_.out();
// Set's the block that any insert command should insert AFTER. If the first block is not
// getting out-trimmed, that means first block is either getting removed or in-trimmed, which
// means any insert should happen before it
insert_previous_ = first_block_is_out_trimmed ? first_block :
first_block->previous();
// If it's getting trimmed, determine if it's actually getting spliced
if (first_block_is_out_trimmed && first_block_is_in_trimmed) {
if (!allow_splitting_gaps_ && dynamic_cast<GapBlock *>(first_block)) {
// As a rule, we don't split gaps, so we just treat it as a trim of the range requested
trim_out_ = { first_block, first_block->length(),
first_block->length() - range_.length() };
} else {
// This block is getting spliced, so we'll handle that later
splice_split_command_ =
new BlockSplitCommand(first_block, range_.in());
}
} else {
// It's just getting trimmed or removed, so we'll append that operation
if (first_block_is_out_trimmed) {
trim_out_ = { first_block, first_block->length(),
first_block->length() -
(first_block->out() - range_.in()) };
} else if (first_block_is_in_trimmed) {
// Block is getting in trimmed
trim_in_ = { first_block, first_block->length(),
first_block->length() -
(range_.out() - first_block->in()) };
} else {
// We know for sure this block is within the range so it will be removed
removals_.append(
RemoveOperation({ first_block, first_block->previous() }));
}
// If the first block is getting in trimmed, we're already at the end of our range
if (!first_block_is_in_trimmed) {
// Loop through the rest of the blocks and determine what to do with those
for (Block *next = first_block->next(); next; next = next->next()) {
bool trimming = (next->out() > range_.out());
if (trimming) {
trim_in_ = { next, next->length(),
next->length() - (range_.out() - next->in()) };
break;
} else {
removals_.append(
RemoveOperation({ next, next->previous() }));
if (next->out() == range_.out()) {
break;
}
}
}
}
}
}
void TrackRippleRemoveAreaCommand::redo()
{
if (splice_split_command_) {
// We're just splicing
splice_split_command_->redo_now();
// Trim the in of the split
Block *split = splice_split_command_->new_block();
split->set_length_and_media_in(split->length() -
(range_.out() - split->in()));
} else {
if (trim_out_.block) {
trim_out_.block->set_length_and_media_out(trim_out_.new_length);
}
if (trim_in_.block) {
trim_in_.block->set_length_and_media_in(trim_in_.new_length);
}
// Perform removals
if (!removals_.isEmpty()) {
foreach (auto op, removals_) {
// Ripple remove them all first
track_->ripple_remove_block(op.block);
}
// Create undo commands for node removals where possible
if (remove_block_commands_.isEmpty()) {
foreach (auto op, removals_) {
if (node_can_be_removed(op.block)) {
remove_block_commands_.append(
create_remove_command(op.block));
}
}
}
foreach (UndoCommand *c, remove_block_commands_) {
c->redo_now();
}
}
}
}
void TrackRippleRemoveAreaCommand::undo()
{
if (splice_split_command_) {
splice_split_command_->undo_now();
} else {
if (trim_out_.block) {
trim_out_.block->set_length_and_media_out(trim_out_.old_length);
}
if (trim_in_.block) {
trim_in_.block->set_length_and_media_in(trim_in_.old_length);
}
// Un-remove any blocks
for (int i = remove_block_commands_.size() - 1; i >= 0; i--) {
remove_block_commands_.at(i)->undo_now();
}
foreach (auto op, removals_) {
track_->insert_block_after(op.block, op.before);
}
}
}
//
// TrackListRippleRemoveAreaCommand
//
void TrackListRippleRemoveAreaCommand::prepare()
{
foreach (Track *track, list_->get_tracks()) {
if (track->is_locked()) {
continue;
}
TrackRippleRemoveAreaCommand *c =
new TrackRippleRemoveAreaCommand(track, range_);
commands_.append(c);
working_tracks_.append(track);
}
}
void TrackListRippleRemoveAreaCommand::redo()
{
foreach (TrackRippleRemoveAreaCommand *c, commands_) {
c->redo_now();
}
}
void TrackListRippleRemoveAreaCommand::undo()
{
foreach (TrackRippleRemoveAreaCommand *c, commands_) {
c->undo_now();
}
}
//
// TimelineRippleRemoveAreaCommand
//
TimelineRippleRemoveAreaCommand::TimelineRippleRemoveAreaCommand(
Sequence *timeline, Rational in, Rational out)
: timeline_(timeline)
{
for (int i = 0; i < Track::k_count; i++) {
add_child(new TrackListRippleRemoveAreaCommand(
timeline->track_list(static_cast<Track::Type>(i)), in, out));
}
}
//
// TrackListRippleToolCommand
//
TrackListRippleToolCommand::TrackListRippleToolCommand(
TrackList *track_list, const QHash<Track *, RippleInfo> &info,
const Rational &ripple_movement,
const Timeline::MovementMode &movement_mode)
: track_list_(track_list)
, info_(info)
, ripple_movement_(ripple_movement)
, movement_mode_(movement_mode)
{
}
void TrackListRippleToolCommand::ripple(bool redo)
{
if (info_.isEmpty()) {
return;
}
// The following variables are used to determine how much of the cache to invalidate
// If we can shift, we will shift from the latest out before the ripple to the latest out after,
// since those sections will be unchanged by this ripple
Rational pre_latest_out = RATIONAL_MIN;
Rational post_latest_out = RATIONAL_MIN;
// Make timeline changes
for (auto it = info_.cbegin(); it != info_.cend(); it++) {
Track *track = it.key();
const RippleInfo &info = it.value();
WorkingData working_data = working_data_.value(track);
Block *b = info.block;
// Generate block length
Rational new_block_length;
Rational operation_movement = ripple_movement_;
if (movement_mode_ == Timeline::k_trim_in) {
operation_movement = -operation_movement;
}
if (!redo) {
operation_movement = -operation_movement;
}
if (b) {
new_block_length = b->length() + operation_movement;
}
Rational pre_shift;
Rational post_shift;
if (info.append_gap) {
// Rather than rippling the referenced block, we'll insert a gap and ripple with that
GapBlock *gap = working_data.created_gap;
if (redo) {
if (!gap) {
gap = new GapBlock();
gap->set_length_and_media_out(qAbs(ripple_movement_));
working_data.created_gap = gap;
}
gap->setParent(track->parent());
track->insert_block_before(gap, b);
// As an insertion, we will shift from the gap's in to the gap's out
pre_shift = gap->in();
post_shift = gap->out();
working_data.earliest_point_of_change = gap->in();
} else {
// As a removal, we will shift from the gap's out to the gap's in
pre_shift = gap->out();
post_shift = gap->in();
track->ripple_remove_block(gap);
gap->setParent(&memory_manager_);
}
} else if ((redo && new_block_length.isNull()) ||
(!redo && !b->track())) {
// The ripple is the length of this block. We assume that for this to happen, it must have
// been a gap that we will now remove.
if (redo) {
// The earliest point changes will happen is at the start of this block
working_data.earliest_point_of_change = b->in();
// As a removal, we will be shifting from the out point to the in point
pre_shift = b->out();
post_shift = b->in();
// Remove gap from track and from graph
working_data.removed_gap_after = b->previous();
track->ripple_remove_block(b);
b->setParent(&memory_manager_);
} else {
// Restore gap to graph and track
b->setParent(track->parent());
track->insert_block_after(b, working_data.removed_gap_after);
// The earliest point changes will happen is at the start of this block
working_data.earliest_point_of_change = b->in();
// As an insert, we will be shifting from the block's in point to its out point
pre_shift = b->in();
post_shift = b->out();
}
} else {
// Store old length
working_data.old_length = b->length();
if (movement_mode_ == Timeline::k_trim_in) {
// The earliest point changes will occur is in point of this bloc
working_data.earliest_point_of_change = b->in();
// Undo the trim in inversion we do above, this will still be inverted accurately for
// undoing where appropriate
Rational inverted = -operation_movement;
if (inverted > 0) {
pre_shift = b->in() + inverted;
post_shift = b->in();
} else {
pre_shift = b->in();
post_shift = b->in() - inverted;
}
// Update length
b->set_length_and_media_in(new_block_length);
} else {
// The earliest point changes will occur is the out point if trimming out or the in point
// if trimming in
working_data.earliest_point_of_change = b->out();
// The latest out before the ripple is this block's current out point
pre_shift = b->out();
// Update length
b->set_length_and_media_out(new_block_length);
// The latest out after the ripple is this block's out point after the length change
post_shift = b->out();
}
}
working_data_.insert(it.key(), working_data);
pre_latest_out = qMax(pre_latest_out, pre_shift);
post_latest_out = qMax(post_latest_out, post_shift);
}
}
//
// TimelineRippleDeleteGapsAtRegionsCommand
//
void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
{
int max_gaps = 0;
QHash<Track *, QVector<RemovalRequest>> requested_gaps;
// Convert regions to gaps
for (const QPair<Track *, TimeRange> &region : qAsConst(regions_)) {
Track *track = region.first;
const TimeRange &range = region.second;
GapBlock *gap =
dynamic_cast<GapBlock *>(track->nearest_block_before_or_at(range.in()));
if (gap) {
QVector<RemovalRequest> &gaps_on_track = requested_gaps[track];
RemovalRequest this_req = { gap, range };
// Insertion sort
bool inserted = false;
for (int i = 0; i < gaps_on_track.size(); i++) {
if (gaps_on_track.at(i).range.in() < range.in()) {
gaps_on_track.insert(i, this_req);
inserted = true;
break;
}
}
if (!inserted) {
gaps_on_track.append(this_req);
}
max_gaps = qMax(max_gaps, gaps_on_track.size());
} else {
qWarning() << "Failed to find corresponding gap to region";
}
}
// For each gap on each track, find a corresponding gap on every other track (which may include
// a requested gap) to ripple in order to keep everything synchronized
QHash<GapBlock *, Rational> gap_lengths;
for (int gap_index = 0; gap_index < max_gaps; gap_index++) {
Rational earliest_point = RATIONAL_MAX;
Rational ripple_length = RATIONAL_MAX;
Rational latest_point = RATIONAL_MIN;
foreach (const QVector<RemovalRequest> &gaps_on_track, requested_gaps) {
if (gap_index < gaps_on_track.size()) {
const RemovalRequest &gap = gaps_on_track.at(gap_index);
earliest_point = qMin(earliest_point, gap.range.in());
ripple_length = qMin(ripple_length, gap.range.length());
latest_point = qMax(latest_point, gap.range.out());
}
}
// Determine which gaps will be involved in this operation
QVector<GapBlock *> gaps;
foreach (Track *track, timeline_->get_tracks()) {
if (track->is_locked()) {
continue;
}
const QVector<RemovalRequest> &requested_gaps_on_track =
requested_gaps.value(track);
GapBlock *gap = nullptr;
if (gap_index < requested_gaps_on_track.size()) {
// A requested gap was at this index, use it
gap = requested_gaps_on_track.at(gap_index).gap;
} else {
// No requested gap was at this index, find one
Block *block = track->nearest_block_after_or_at(earliest_point);
if (block) {
// Found a block, test if it's a gap
gap = dynamic_cast<GapBlock *>(block);
if (!gap) {
if (block->in() == earliest_point) {
if (block->next()) {
gap = dynamic_cast<GapBlock *>(block->next());
if (!gap) {
ripple_length = 0;
}
}
} else {
gap = dynamic_cast<GapBlock *>(block->previous());
if (!gap) {
ripple_length = 0;
}
}
}
} else {
// Assume track finishes here and track won't be affected by this operation
}
}
if (gap) {
gaps.append(gap);
if (!gap_lengths.contains(gap)) {
gap_lengths.insert(gap, gap->length());
}
ripple_length = qMin(ripple_length, gap_lengths.value(gap));
}
if (ripple_length == 0) {
break;
}
}
if (ripple_length > 0) {
foreach (GapBlock *gap, gaps) {
if (gap_lengths.value(gap) == ripple_length) {
commands_.append(
new TrackRippleRemoveBlockCommand(gap->track(), gap));
} else {
gap_lengths[gap] -= ripple_length;
commands_.append(
new BlockResizeCommand(gap, gap_lengths.value(gap)));
}
}
}
}
}
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
{
for (auto it = commands_.cbegin(); it != commands_.cend(); it++) {
(*it)->redo_now();
}
}
void TimelineRippleDeleteGapsAtRegionsCommand::undo()
{
for (auto it = commands_.crbegin(); it != commands_.crend(); it++) {
(*it)->undo_now();
}
}
}
-253
View File
@@ -1,253 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDORIPPLE_H
#define OAK_TIMELINEUNDORIPPLE_H
#include "node/block/gap/gap.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "node/project/sequence/sequence.h"
#include "timelineundogeneral.h"
#include "timelineundosplit.h"
#include "timelineundotrack.h"
namespace olive
{
/**
* @brief Clears the area between in and out
*
* The area between `in` and `out` is guaranteed to be freed. BLocks are trimmed and removed to free this space.
* By default, nothing takes this area meaning all subsequent clips are pushed backward, however you can specify
* a block to insert at the `in` point. No checking is done to ensure `insert` is the same length as `in` to `out`.
*/
class TrackRippleRemoveAreaCommand : public UndoCommand {
public:
TrackRippleRemoveAreaCommand(Track *track, const TimeRange &range);
virtual ~TrackRippleRemoveAreaCommand() override;
virtual Project *get_relevant_project() const override
{
return track_->project();
}
/**
* @brief Block to insert after if you want to insert something between this ripple
*/
Block *get_insertion_index() const
{
return insert_previous_;
}
Block *get_spliced_block() const
{
if (splice_split_command_) {
return splice_split_command_->new_block();
}
return nullptr;
}
void set_allow_splitting_gaps(bool e)
{
allow_splitting_gaps_ = e;
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
struct TrimOperation {
Block *block;
Rational old_length;
Rational new_length;
};
struct RemoveOperation {
Block *block;
Block *before;
};
Track *track_;
TimeRange range_;
TrimOperation trim_out_;
QVector<RemoveOperation> removals_;
TrimOperation trim_in_;
Block *insert_previous_;
bool allow_splitting_gaps_;
BlockSplitCommand *splice_split_command_;
QVector<UndoCommand *> remove_block_commands_;
};
class TrackListRippleRemoveAreaCommand : public UndoCommand {
public:
TrackListRippleRemoveAreaCommand(TrackList *list, Rational in, Rational out)
: list_(list)
, range_(in, out)
{
}
virtual ~TrackListRippleRemoveAreaCommand() override
{
qDeleteAll(commands_);
}
virtual Project *get_relevant_project() const override
{
return list_->parent()->project();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
TrackList *list_;
QList<Track *> working_tracks_;
TimeRange range_;
QVector<TrackRippleRemoveAreaCommand *> commands_;
};
class TimelineRippleRemoveAreaCommand : public MultiUndoCommand {
public:
TimelineRippleRemoveAreaCommand(Sequence *timeline, Rational in,
Rational out);
virtual Project *get_relevant_project() const override
{
return timeline_->project();
}
private:
Sequence *timeline_;
};
class TrackListRippleToolCommand : public UndoCommand {
public:
struct RippleInfo {
Block *block;
bool append_gap;
};
TrackListRippleToolCommand(TrackList *track_list,
const QHash<Track *, RippleInfo> &info,
const Rational &ripple_movement,
const Timeline::MovementMode &movement_mode);
virtual Project *get_relevant_project() const override
{
return track_list_->parent()->project();
}
protected:
virtual void redo() override
{
ripple(true);
}
virtual void undo() override
{
ripple(false);
}
private:
void ripple(bool redo);
TrackList *track_list_;
QHash<Track *, RippleInfo> info_;
Rational ripple_movement_;
Timeline::MovementMode movement_mode_;
struct WorkingData {
GapBlock *created_gap = nullptr;
Block *removed_gap_after;
Rational old_length;
Rational earliest_point_of_change;
};
QHash<Track *, WorkingData> working_data_;
QObject memory_manager_;
};
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
public:
using RangeList = QVector<QPair<Track *, TimeRange>>;
TimelineRippleDeleteGapsAtRegionsCommand(Sequence *vo,
const RangeList &regions)
: timeline_(vo)
, regions_(regions)
{
}
virtual ~TimelineRippleDeleteGapsAtRegionsCommand() override
{
qDeleteAll(commands_);
}
virtual Project *get_relevant_project() const override
{
return timeline_->project();
}
bool has_commands() const
{
return !commands_.isEmpty();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
Sequence *timeline_;
RangeList regions_;
QVector<UndoCommand *> commands_;
struct RemovalRequest {
GapBlock *gap;
TimeRange range;
};
};
}
#endif // OAK_TIMELINEUNDORIPPLE_H
-188
View File
@@ -1,188 +0,0 @@
/***
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 "timelineundosplit.h"
#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
#include "node/nodeundo.h"
namespace olive
{
//
// BlockSplitCommand
//
void BlockSplitCommand::prepare()
{
reconnect_tree_command_ = new MultiUndoCommand();
new_block_ = static_cast<Block *>(
Node::copy_node_in_graph(block_, reconnect_tree_command_));
}
void BlockSplitCommand::redo()
{
old_length_ = block_->length();
Q_ASSERT(point_ > block_->in() && point_ < block_->out());
reconnect_tree_command_->redo_now();
// Determine our new lengths
Rational new_length = point_ - block_->in();
Rational new_part_length = block_->out() - point_;
// Begin an operation
Track *track = block_->track();
// Set lengths
block_->set_length_and_media_out(new_length);
new_block()->set_length_and_media_in(new_part_length);
// Insert new block
track->insert_block_after(new_block(), block_);
if (ClipBlock *new_clip = dynamic_cast<ClipBlock *>(new_block_)) {
ClipBlock *old_clip = static_cast<ClipBlock *>(block_);
new_clip->add_cache_passthrough_from(old_clip);
}
// If the block had an out transition, we move it to the new block
moved_transition_ = NodeInput();
TransitionBlock *potential_transition =
dynamic_cast<TransitionBlock *>(new_block()->next());
if (potential_transition) {
for (const Node::OutputConnection &output :
block_->output_connections()) {
if (output.second.node() == potential_transition) {
moved_transition_ = NodeInput(potential_transition,
TransitionBlock::k_out_block_input);
Node::disconnect_edge(block_, moved_transition_);
Node::connect_edge(new_block(), moved_transition_);
break;
}
}
}
}
void BlockSplitCommand::undo()
{
Track *track = block_->track();
if (moved_transition_.is_valid()) {
Node::disconnect_edge(new_block(), moved_transition_);
Node::connect_edge(block_, moved_transition_);
}
block_->set_length_and_media_out(old_length_);
track->ripple_remove_block(new_block());
// If we ran a reconnect command, disconnect now
reconnect_tree_command_->undo_now();
}
//
// BlockSplitPreservingLinksCommand
//
Block *BlockSplitPreservingLinksCommand::get_split(Block *original,
int time_index) const
{
if (time_index >= 0 && time_index < times_.size()) {
int original_index = blocks_.indexOf(original);
if (original_index != -1) {
return splits_.at(time_index).at(original_index);
}
}
return nullptr;
}
void BlockSplitPreservingLinksCommand::prepare()
{
splits_.resize(times_.size());
for (int i = 0; i < times_.size(); i++) {
const Rational &time = times_.at(i);
// FIXME: I realize this isn't going to work if the times aren't ordered. I'm lazy so rather
// than writing in a sorting algorithm here, I'll just put an assert as a reminder
// if this ever becomes an issue.
Q_ASSERT(i == 0 || time > times_.at(i - 1));
QVector<Block *> splits(blocks_.size());
for (int j = 0; j < blocks_.size(); j++) {
Block *b = blocks_.at(j);
if (b->in() < time && b->out() > time) {
BlockSplitCommand *split_command =
new BlockSplitCommand(b, time);
split_command->redo_now();
splits.replace(j, split_command->new_block());
commands_.append(split_command);
} else {
splits.replace(j, nullptr);
}
}
splits_.replace(i, splits);
}
// Now that we've determined all the splits, we can relink everything
for (int i = 0; i < blocks_.size(); i++) {
Block *a = blocks_.at(i);
for (int j = 0; j < blocks_.size(); j++) {
if (i == j) {
continue;
}
Block *b = blocks_.at(j);
if (Block::are_linked(a, b)) {
// These blocks are linked, ensure all the splits are linked too
foreach (const QVector<Block *> &split_list, splits_) {
NodeLinkCommand *blc = new NodeLinkCommand(
split_list.at(i), split_list.at(j), true);
blc->redo_now();
commands_.append(blc);
}
}
}
}
}
//
// TrackSplitAtTimeCommand
//
void TrackSplitAtTimeCommand::prepare()
{
// Find Block that contains this time
Block *b = track_->block_containing_time(point_);
if (b) {
command_ = new BlockSplitCommand(b, point_);
}
}
}
-171
View File
@@ -1,171 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOSPLIT_H
#define OAK_TIMELINEUNDOSPLIT_H
#include "node/output/track/track.h"
namespace olive
{
class BlockSplitCommand : public UndoCommand {
public:
BlockSplitCommand(Block *block, Rational point)
: block_(block)
, new_block_(nullptr)
, point_(point)
, reconnect_tree_command_(nullptr)
{
}
virtual ~BlockSplitCommand() override
{
delete reconnect_tree_command_;
}
virtual Project *get_relevant_project() const override
{
return block_->project();
}
/**
* @brief Access the second block created as a result. Only valid after redo().
*/
Block *new_block()
{
return new_block_;
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override;
private:
Block *block_;
Block *new_block_;
Rational old_length_;
Rational point_;
MultiUndoCommand *reconnect_tree_command_;
NodeInput moved_transition_;
};
class BlockSplitPreservingLinksCommand : public UndoCommand {
public:
BlockSplitPreservingLinksCommand(const QVector<Block *> &blocks,
const QList<Rational> &times)
: blocks_(blocks)
, times_(times)
{
}
virtual ~BlockSplitPreservingLinksCommand() override
{
qDeleteAll(commands_);
}
virtual Project *get_relevant_project() const override
{
return blocks_.first()->project();
}
Block *get_split(Block *original, int time_index) const;
protected:
virtual void prepare() override;
virtual void redo() override
{
for (int i = 0; i < commands_.size(); i++) {
commands_.at(i)->redo_now();
}
}
virtual void undo() override
{
for (int i = commands_.size() - 1; i >= 0; i--) {
commands_.at(i)->undo_now();
}
}
private:
QVector<Block *> blocks_;
QList<Rational> times_;
QVector<UndoCommand *> commands_;
QVector<QVector<Block *>> splits_;
};
class TrackSplitAtTimeCommand : public UndoCommand {
public:
TrackSplitAtTimeCommand(Track *track, Rational point)
: track_(track)
, point_(point)
, command_(nullptr)
{
}
virtual ~TrackSplitAtTimeCommand() override
{
delete command_;
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void prepare() override;
virtual void redo() override
{
if (command_) {
command_->redo_now();
}
}
virtual void undo() override
{
if (command_) {
command_->undo_now();
}
}
private:
Track *track_;
Rational point_;
UndoCommand *command_;
};
}
#endif // OAK_TIMELINEUNDOSPLIT_H
-27
View File
@@ -1,27 +0,0 @@
/***
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 "timelineundotrack.h"
namespace olive
{
}
-163
View File
@@ -1,163 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOTRACK_H
#define OAK_TIMELINEUNDOTRACK_H
#include "node/output/track/track.h"
namespace olive
{
class TrackRippleRemoveBlockCommand : public UndoCommand {
public:
TrackRippleRemoveBlockCommand(Track *track, Block *block)
: track_(track)
, block_(block)
{
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void redo() override
{
before_ = block_->previous();
track_->ripple_remove_block(block_);
}
virtual void undo() override
{
track_->insert_block_after(block_, before_);
}
private:
Track *track_;
Block *block_;
Block *before_;
};
class TrackPrependBlockCommand : public UndoCommand {
public:
TrackPrependBlockCommand(Track *track, Block *block)
: track_(track)
, block_(block)
{
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void redo() override
{
track_->prepend_block(block_);
}
virtual void undo() override
{
track_->ripple_remove_block(block_);
}
private:
Track *track_;
Block *block_;
};
class TrackInsertBlockAfterCommand : public UndoCommand {
public:
TrackInsertBlockAfterCommand(Track *track, Block *block, Block *before)
: track_(track)
, block_(block)
, before_(before)
{
}
virtual Project *get_relevant_project() const override
{
return block_->project();
}
protected:
virtual void redo() override
{
track_->insert_block_after(block_, before_);
}
virtual void undo() override
{
track_->ripple_remove_block(block_);
}
private:
Track *track_;
Block *block_;
Block *before_;
};
/**
* @brief Replaces Block `old` with Block `replace`
*
* Both blocks must have equal lengths.
*/
class TrackReplaceBlockCommand : public UndoCommand {
public:
TrackReplaceBlockCommand(Track *track, Block *old, Block *replace)
: track_(track)
, old_(old)
, replace_(replace)
{
}
virtual Project *get_relevant_project() const override
{
return track_->project();
}
protected:
virtual void redo() override
{
track_->replace_block(old_, replace_);
}
virtual void undo() override
{
track_->replace_block(replace_, old_);
}
private:
Track *track_;
Block *old_;
Block *replace_;
};
}
#endif // OAK_TIMELINEUNDOTRACK_H
-27
View File
@@ -1,27 +0,0 @@
/***
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 "timelineundoworkarea.h"
namespace olive
{
}
-108
View File
@@ -1,108 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEUNDOWORKAREA_H
#define OAK_TIMELINEUNDOWORKAREA_H
#include "node/project.h"
namespace olive
{
class WorkareaSetEnabledCommand : public UndoCommand {
public:
WorkareaSetEnabledCommand(Project *project, TimelineWorkArea *points,
bool enabled)
: project_(project)
, points_(points)
, old_enabled_(points_->enabled())
, new_enabled_(enabled)
{
}
virtual Project *get_relevant_project() const override
{
return project_;
}
protected:
virtual void redo() override
{
points_->set_enabled(new_enabled_);
}
virtual void undo() override
{
points_->set_enabled(old_enabled_);
}
private:
Project *project_;
TimelineWorkArea *points_;
bool old_enabled_;
bool new_enabled_;
};
class WorkareaSetRangeCommand : public UndoCommand {
public:
WorkareaSetRangeCommand(TimelineWorkArea *workarea, const TimeRange &range,
const TimeRange &old_range)
: workarea_(workarea)
, old_range_(old_range)
, new_range_(range)
{
}
WorkareaSetRangeCommand(TimelineWorkArea *workarea, const TimeRange &range)
: WorkareaSetRangeCommand(workarea, range, workarea->range())
{
}
virtual Project *get_relevant_project() const override
{
return Project::get_project_from_object(workarea_);
}
protected:
virtual void redo() override
{
workarea_->set_range(new_range_);
}
virtual void undo() override
{
workarea_->set_range(old_range_);
}
private:
TimelineWorkArea *workarea_;
TimeRange old_range_;
TimeRange new_range_;
};
}
#endif // OAK_TIMELINEUNDOWORKAREA_H
-124
View File
@@ -1,124 +0,0 @@
/***
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 "timelineworkarea.h"
#include "common/xmlutils.h"
namespace olive
{
const Rational TimelineWorkArea::k_reset_in = 0;
const Rational TimelineWorkArea::k_reset_out = RATIONAL_MAX;
TimelineWorkArea::TimelineWorkArea(QObject *parent)
: QObject(parent)
, workarea_enabled_(false)
{
}
bool TimelineWorkArea::enabled() const
{
return workarea_enabled_;
}
void TimelineWorkArea::set_enabled(bool e)
{
workarea_enabled_ = e;
emit enabled_changed(workarea_enabled_);
}
const TimeRange &TimelineWorkArea::range() const
{
return workarea_range_;
}
void TimelineWorkArea::set_range(const TimeRange &range)
{
workarea_range_ = range;
emit range_changed(workarea_range_);
}
bool TimelineWorkArea::load(QXmlStreamReader *reader)
{
Rational range_in = this->in();
Rational range_out = this->out();
uint version = 0;
XMLAttributeLoop(reader, attr)
{
if (attr.name() == QStringLiteral("version")) {
version = attr.value().toUInt();
}
}
Q_UNUSED(version)
while (xml_read_next_start_element(reader)) {
if (reader->name() == QStringLiteral("enabled")) {
this->set_enabled(reader->readElementText() != QStringLiteral("0"));
} else if (reader->name() == QStringLiteral("in")) {
range_in =
Rational::from_string(reader->readElementText().toStdString());
} else if (reader->name() == QStringLiteral("out")) {
range_out =
Rational::from_string(reader->readElementText().toStdString());
} else {
reader->skipCurrentElement();
}
}
TimeRange loaded_workarea(range_in, range_out);
if (loaded_workarea != this->range()) {
this->set_range(loaded_workarea);
}
return true;
}
void TimelineWorkArea::save(QXmlStreamWriter *writer) const
{
writer->writeAttribute(QStringLiteral("version"), QString::number(1));
writer->writeTextElement(QStringLiteral("enabled"),
QString::number(this->enabled()));
writer->writeTextElement(QStringLiteral("in"),
QString::fromStdString(this->in().to_string()));
writer->writeTextElement(QStringLiteral("out"),
QString::fromStdString(this->out().to_string()));
}
Rational TimelineWorkArea::in() const
{
return workarea_range_.in();
}
Rational TimelineWorkArea::out() const
{
return workarea_range_.out();
}
Rational TimelineWorkArea::length() const
{
return workarea_range_.length();
}
}
-68
View File
@@ -1,68 +0,0 @@
/***
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/>.
***/
#ifndef OAK_TIMELINEWORKAREA_H
#define OAK_TIMELINEWORKAREA_H
#include <olive/core/core.h>
#include <QObject>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
namespace olive
{
using namespace core;
class TimelineWorkArea : public QObject {
Q_OBJECT
public:
TimelineWorkArea(QObject *parent = nullptr);
bool enabled() const;
void set_enabled(bool e);
Rational in() const;
Rational out() const;
Rational length() const;
const TimeRange &range() const;
void set_range(const TimeRange &range);
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
static const Rational k_reset_in;
static const Rational k_reset_out;
signals:
void enabled_changed(bool e);
void range_changed(const TimeRange &r);
private:
bool workarea_enabled_;
TimeRange workarea_range_;
};
}
#endif // OAK_TIMELINEWORKAREA_H