Files
oak-editor/app/node/node.cpp
T
itsmattkc d7c5ac4b44 implement entire project in nodes
Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly.
2021-02-15 21:31:57 +11:00

2154 lines
60 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
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 "node.h"
#include <QApplication>
#include <QGuiApplication>
#include <QDebug>
#include <QFile>
#include "common/bezier.h"
#include "common/lerp.h"
#include "common/timecodefunctions.h"
#include "common/xmlutils.h"
#include "core.h"
#include "config/config.h"
#include "project/project.h"
#include "project/item/footage/footage.h"
#include "ui/colorcoding.h"
#include "ui/icons/icons.h"
#include "widget/nodeview/nodeviewundo.h"
namespace olive {
#define super QObject
const QString Node::kDefaultOutput = QStringLiteral("output");
Node::Node(bool create_default_output) :
can_be_deleted_(true),
override_color_(-1)
{
if (create_default_output) {
AddOutput();
}
}
Node::~Node()
{
// Disconnect all edges
DisconnectAll();
// Remove self from anything while we're still a node rather than a base QObject
setParent(nullptr);
// Remove all immediates
foreach (NodeInputImmediate* i, standard_immediates_) {
delete i;
}
for (auto it=array_immediates_.cbegin(); it!=array_immediates_.cend(); it++) {
foreach (NodeInputImmediate* i, it.value()) {
delete i;
}
}
}
NodeGraph *Node::parent() const
{
return static_cast<NodeGraph*>(QObject::parent());
}
void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled)
{
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("input")) {
LoadInput(reader, xml_node_data, cancelled);
} else if (reader->name() == QStringLiteral("ptr")) {
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
} else if (reader->name() == QStringLiteral("pos")) {
QPointF p;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("x")) {
p.setX(reader->readElementText().toDouble());
} else if (reader->name() == QStringLiteral("y")) {
p.setY(reader->readElementText().toDouble());
} else {
reader->skipCurrentElement();
}
}
SetPosition(p);
} else if (reader->name() == QStringLiteral("label")) {
SetLabel(reader->readElementText());
} else if (reader->name() == QStringLiteral("color")) {
override_color_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("links")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("link")) {
xml_node_data.block_links.append({this, reader->readElementText().toULongLong()});
} else {
reader->skipCurrentElement();
}
}
} else if (reader->name() == QStringLiteral("custom")) {
LoadInternal(reader, xml_node_data, version, cancelled);
} else if (reader->name() == QStringLiteral("connections")) {
// Load connections
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("connection")) {
QString param_id;
int ele = -1;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("element")) {
ele = attr.value().toInt();
} else if (attr.name() == QStringLiteral("input")) {
param_id = attr.value().toString();
}
}
QString output_node_id;
QString output_param_id;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
output_node_id = reader->readElementText();
} else if (reader->name() == QStringLiteral("output")) {
output_param_id = reader->readElementText();
} else {
reader->skipCurrentElement();
}
}
xml_node_data.desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong(), output_param_id});
} else {
reader->skipCurrentElement();
}
}
} else {
reader->skipCurrentElement();
}
}
}
void Node::Save(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeStartElement(QStringLiteral("pos"));
writer->writeTextElement(QStringLiteral("x"), QString::number(GetPosition().x()));
writer->writeTextElement(QStringLiteral("y"), QString::number(GetPosition().y()));
writer->writeEndElement(); // pos
writer->writeTextElement(QStringLiteral("label"), GetLabel());
writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_));
foreach (const QString& input, input_ids_) {
writer->writeStartElement(QStringLiteral("input"));
SaveInput(writer, input);
writer->writeEndElement(); // input
}
writer->writeStartElement(QStringLiteral("links"));
foreach (Node* link, links_) {
writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast<quintptr>(link)));
}
writer->writeEndElement(); // links
writer->writeStartElement(QStringLiteral("connections"));
for (auto it=input_connections().cbegin(); it!=input_connections().cend(); it++) {
writer->writeStartElement(QStringLiteral("connection"));
writer->writeAttribute(QStringLiteral("input"), it->first.input());
writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element()));
writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast<quintptr>(it->second.node())));
writer->writeTextElement(QStringLiteral("output"), it->second.output());
writer->writeEndElement(); // connection
}
writer->writeEndElement(); // connections
writer->writeStartElement(QStringLiteral("custom"));
SaveInternal(writer);
writer->writeEndElement(); // custom
}
Project* Node::project() const
{
return dynamic_cast<Project*>(parent());
}
QString Node::ShortName() const
{
return Name();
}
QString Node::Description() const
{
// Return an empty string by default
return QString();
}
void Node::Retranslate()
{
}
QIcon Node::icon() const
{
// Just a meaningless default icon to be used where necessary
return icon::New;
}
Color Node::color() const
{
int c;
if (override_color_ >= 0) {
c = override_color_;
} else {
c = Config::Current()[QStringLiteral("CatColor%1").arg(this->Category().first())].toInt();
}
return ColorCoding::GetColor(c);
}
QLinearGradient Node::gradient_color(qreal top, qreal bottom) const
{
QLinearGradient grad;
grad.setStart(0, top);
grad.setFinalStop(0, bottom);
QColor c = color().toQColor();
grad.setColorAt(0.0, c.lighter());
grad.setColorAt(1.0, c);
return grad;
}
QBrush Node::brush(qreal top, qreal bottom) const
{
if (Config::Current()[QStringLiteral("UseGradients")].toBool()) {
return gradient_color(top, bottom);
} else {
return color().toQColor();
}
}
void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input)
{
// Ensure graph is the same
Q_ASSERT(input.node()->parent() == output.node()->parent());
// Ensure a connection isn't getting overwritten
Q_ASSERT(input.node()->input_connections().find(input) == input.node()->input_connections().end());
// Insert connection on both sides
input.node()->input_connections_[input] = output;
output.node()->output_connections_.push_back(std::pair<NodeOutput, NodeInput>({output, input}));
// Call internal events
input.node()->InputConnectedEvent(input.input(), input.element(), output);
output.node()->OutputConnectedEvent(output.output(), input);
// Emit signals
emit input.node()->InputConnected(output, input);
emit output.node()->OutputConnected(output, input);
// Invalidate all if this node isn't ignoring this input
if (!input.node()->ignore_connections_.contains(input.input())) {
input.node()->InvalidateAll(input.input(), input.element());
}
}
void Node::DisconnectEdge(const NodeOutput &output, const NodeInput &input)
{
// Ensure graph is the same
Q_ASSERT(input.node()->parent() == output.node()->parent());
// Ensure connection exists
Q_ASSERT(input.node()->input_connections().at(input) == output);
// Remove connection from both sides
InputConnections& inputs = input.node()->input_connections_;
inputs.erase(inputs.find(input));
OutputConnections& outputs = output.node()->output_connections_;
outputs.erase(std::find(outputs.begin(), outputs.end(), std::pair<NodeOutput, NodeInput>({output, input})));
// Call internal events
input.node()->InputDisconnectedEvent(input.input(), input.element(), output);
output.node()->OutputDisconnectedEvent(output.output(), input);
emit input.node()->InputDisconnected(output, input);
emit output.node()->OutputDisconnected(output, input);
if (!input.node()->ignore_connections_.contains(input.input())) {
input.node()->InvalidateAll(input.input(), input.element());
}
}
QString Node::GetInputName(const QString &id) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->human_name;
} else {
ReportInvalidInput("get name of", id);
return QString();
}
}
void Node::LoadInput(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
{
QString param_id;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
param_id = attr.value().toString();
break;
}
}
if (param_id.isEmpty()) {
qWarning() << "Failed to load parameter with missing ID";
return;
}
if (!HasInputWithID(param_id)) {
qWarning() << "Failed to load parameter that didn't exist";
return;
}
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("primary")) {
// Load primary immediate
LoadImmediate(reader, param_id, -1, xml_node_data, cancelled);
} else if (reader->name() == QStringLiteral("subelements")) {
// Load subelements
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("count")) {
InputArrayResize(param_id, attr.value().toInt());
}
}
int element_counter = 0;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("element")) {
LoadImmediate(reader, param_id, element_counter, xml_node_data, cancelled);
element_counter++;
} else {
reader->skipCurrentElement();
}
}
} else {
reader->skipCurrentElement();
}
}
}
void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const
{
writer->writeAttribute(QStringLiteral("id"), id);
writer->writeStartElement(QStringLiteral("primary"));
SaveImmediate(writer, id, -1);
writer->writeEndElement(); // primary
writer->writeStartElement(QStringLiteral("subelements"));
int arr_sz = InputArraySize(id);
writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz));
for (int i=0; i<arr_sz; i++) {
writer->writeStartElement(QStringLiteral("element"));
SaveImmediate(writer, id, i);
writer->writeEndElement(); // element
}
writer->writeEndElement(); // subelements
}
bool Node::IsInputConnectable(const QString &input) const
{
return !(GetInputFlags(input) & kInputFlagNotConnectable);
}
bool Node::IsInputKeyframable(const QString &input) const
{
return !(GetInputFlags(input) & kInputFlagNotKeyframable);
}
bool Node::IsInputKeyframing(const QString &input, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->is_keyframing();
} else {
ReportInvalidInput("get keyframing state of", input);
return false;
}
}
void Node::SetInputIsKeyframing(const QString &input, bool e, int element)
{
if (!IsInputKeyframable(input)) {
qDebug() << "Ignored set keyframing of" << input << "because this input is not keyframable";
return;
}
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
imm->set_is_keyframing(e);
emit KeyframeEnableChanged(NodeInput(this, input, element), e);
} else {
ReportInvalidInput("set keyframing state of", input);
}
}
bool Node::IsInputConnected(const QString &input, int element) const
{
return GetConnectedOutput(input, element).IsValid();
}
NodeOutput Node::GetConnectedOutput(const QString &input, int element) const
{
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
if (it->first.input() == input && it->first.element() == element) {
return it->second;
}
}
return NodeOutput();
}
bool Node::IsUsingStandardValue(const QString &input, int track, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->is_using_standard_value(track);
} else {
ReportInvalidInput("determine whether using standard value in", input);
return true;
}
}
NodeValue::Type Node::GetInputDataType(const QString &id) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->type;
} else {
ReportInvalidInput("get data type of", id);
return NodeValue::kNone;
}
}
void Node::SetInputDataType(const QString &id, const NodeValue::Type &type)
{
Input* i = GetInternalInputData(id);
if (i) {
i->type = type;
emit InputDataTypeChanged(id, type);
} else {
ReportInvalidInput("set data type of", id);
}
}
bool Node::HasInputProperty(const QString &id, const QString &name) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->properties.contains(name);
} else {
ReportInvalidInput("get property of", id);
return false;
}
}
QHash<QString, QVariant> Node::GetInputProperties(const QString &id) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->properties;
} else {
ReportInvalidInput("get property table of", id);
return QHash<QString, QVariant>();
}
}
QVariant Node::GetInputProperty(const QString &id, const QString &name) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->properties.value(name);
} else {
ReportInvalidInput("get property of", id);
return QVariant();
}
}
void Node::SetInputProperty(const QString &id, const QString &name, const QVariant &value)
{
Input* i = GetInternalInputData(id);
if (i) {
i->properties.insert(name, value);
emit InputPropertyChanged(id, name, value);
} else {
ReportInvalidInput("set property of", id);
}
}
SplitValue Node::GetSplitValueAtTime(const QString &input, const rational &time, int element) const
{
SplitValue vals;
int nb_tracks = GetNumberOfKeyframeTracks(input);
for (int i=0;i<nb_tracks;i++) {
if (IsUsingStandardValue(input, i, element)) {
vals.append(GetSplitStandardValueOnTrack(input, i, element));
} else {
vals.append(GetSplitValueAtTimeOnTrack(input, time, i, element));
}
}
return vals;
}
QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
if (!IsUsingStandardValue(input, track, element)) {
const NodeKeyframeTrack& key_track = GetKeyframeTracks(input, element).at(track);
if (key_track.first()->time() >= time) {
// This time precedes any keyframe, so we just return the first value
return key_track.first()->value();
}
if (key_track.last()->time() <= time) {
// This time is after any keyframes so we return the last value
return key_track.last()->value();
}
// If we're here, the time must be somewhere in between the keyframes
for (int i=0;i<key_track.size()-1;i++) {
NodeKeyframe* before = key_track.at(i);
NodeKeyframe* after = key_track.at(i+1);
if (before->time() == time
|| !NodeValue::type_can_be_interpolated(GetInputDataType(input))
|| (before->time() < time && before->type() == NodeKeyframe::kHold)) {
// Time == keyframe time, so value is precise
return before->value();
} else if (after->time() == time) {
// Time == keyframe time, so value is precise
return after->value();
} else if (before->time() < time && after->time() > time) {
// We must interpolate between these keyframes
if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
// Perform a cubic bezier with two control points
double t = Bezier::CubicXtoT(time.toDouble(),
before->time().toDouble(),
before->time().toDouble() + before->valid_bezier_control_out().x(),
after->time().toDouble() + after->valid_bezier_control_in().x(),
after->time().toDouble());
double y = Bezier::CubicTtoY(before->value().toDouble(),
before->value().toDouble() + before->valid_bezier_control_out().y(),
after->value().toDouble() + after->valid_bezier_control_in().y(),
after->value().toDouble(),
t);
return y;
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Perform a quadratic bezier with only one control point
QPointF control_point;
double control_point_time;
double control_point_value;
if (before->type() == NodeKeyframe::kBezier) {
control_point = before->valid_bezier_control_out();
control_point_time = before->time().toDouble() + control_point.x();
control_point_value = before->value().toDouble() + control_point.y();
} else {
control_point = after->valid_bezier_control_in();
control_point_time = after->time().toDouble() + control_point.x();
control_point_value = after->value().toDouble() + control_point.y();
}
// Generate T from time values - used to determine bezier progress
double t = Bezier::QuadraticXtoT(time.toDouble(), before->time().toDouble(), control_point_time, after->time().toDouble());
// Generate value using T
double y = Bezier::QuadraticTtoY(before->value().toDouble(), control_point_value, after->value().toDouble(), t);
return y;
} else {
// To have arrived here, the keyframes must both be linear
qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble());
return lerp(before->value().toDouble(), after->value().toDouble(), period_progress);
}
}
}
}
return GetSplitStandardValueOnTrack(input, track, element);
}
QVariant Node::GetDefaultValue(const QString &input) const
{
NodeValue::Type type = GetInputDataType(input);
return NodeValue::combine_track_values_into_normal_value(type, GetSplitDefaultValue(input));
}
SplitValue Node::GetSplitDefaultValue(const QString &input) const
{
const Input* i = GetInternalInputData(input);
if (i) {
return i->default_value;
} else {
ReportInvalidInput("retrieve default value of", input);
return SplitValue();
}
}
QVariant Node::GetSplitDefaultValueOnTrack(const QString &input, int track) const
{
return GetSplitDefaultValue(input).at(track);
}
const QVector<NodeKeyframeTrack> &Node::GetKeyframeTracks(const QString &input, int element) const
{
return GetImmediate(input, element)->keyframe_tracks();
}
QVector<NodeKeyframe *> Node::GetKeyframesAtTime(const QString &input, const rational &time, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->get_keyframe_at_time(time);
} else {
ReportInvalidInput("get keyframes at time from", input);
return QVector<NodeKeyframe*>();
}
}
NodeKeyframe *Node::GetKeyframeAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->get_keyframe_at_time_on_track(time, track);
} else {
ReportInvalidInput("get keyframe at time on track from", input);
return nullptr;
}
}
NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->get_best_keyframe_type_for_time(time, track);
} else {
ReportInvalidInput("get closest keyframe before a time from", input);
return NodeKeyframe::kDefaultType;
}
}
int Node::GetNumberOfKeyframeTracks(const QString &id) const
{
return NodeValue::get_number_of_keyframe_tracks(GetInputDataType(id));
}
NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->get_earliest_keyframe();
} else {
ReportInvalidInput("get earliest keyframe from", id);
return nullptr;
}
}
NodeKeyframe *Node::GetLatestKeyframe(const QString &id, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->get_latest_keyframe();
} else {
ReportInvalidInput("get latest keyframe from", id);
return nullptr;
}
}
NodeKeyframe *Node::GetClosestKeyframeBeforeTime(const QString &id, const rational &time, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->get_closest_keyframe_before_time(time);
} else {
ReportInvalidInput("get closest keyframe before a time from", id);
return nullptr;
}
}
NodeKeyframe *Node::GetClosestKeyframeAfterTime(const QString &id, const rational &time, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->get_closest_keyframe_after_time(time);
} else {
ReportInvalidInput("get closest keyframe after a time from", id);
return nullptr;
}
}
bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->has_keyframe_at_time(time);
} else {
ReportInvalidInput("determine if it has a keyframe at a time from", id);
return false;
}
}
QStringList Node::GetComboBoxStrings(const QString &id) const
{
return GetInputProperty(id, QStringLiteral("combo_str")).toStringList();
}
QVariant Node::GetStandardValue(const QString &id, int element) const
{
NodeValue::Type type = GetInputDataType(id);
return NodeValue::combine_track_values_into_normal_value(type, GetSplitStandardValue(id, element));
}
SplitValue Node::GetSplitStandardValue(const QString &id, int element) const
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
return imm->get_split_standard_value();
} else {
ReportInvalidInput("get standard value of", id);
return SplitValue();
}
}
QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int element) const
{
NodeInputImmediate* imm = GetImmediate(input, element);
if (imm) {
return imm->get_split_standard_value_on_track(track);
} else {
ReportInvalidInput("get standard value of", input);
return QVariant();
}
}
void Node::SetStandardValue(const QString &id, const QVariant &value, int element)
{
NodeValue::Type type = GetInputDataType(id);
SetSplitStandardValue(id, NodeValue::split_normal_value_into_track_values(type, value), element);
}
void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int element)
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
imm->set_split_standard_value(value);
for (int i=0; i<value.size(); i++) {
if (IsUsingStandardValue(id, i, element)) {
// If this standard value is being used, we need to send a value changed signal
ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
break;
}
}
} else {
ReportInvalidInput("set standard value of", id);
}
}
void Node::SetSplitStandardValueOnTrack(const QString &id, int track, const QVariant &value, int element)
{
NodeInputImmediate* imm = GetImmediate(id, element);
if (imm) {
imm->set_standard_value_on_track(value, track);
if (IsUsingStandardValue(id, track, element)) {
// If this standard value is being used, we need to send a value changed signal
ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
}
} else {
ReportInvalidInput("set standard value of", id);
}
}
bool Node::InputIsArray(const QString &id) const
{
return GetInputFlags(id) & kInputFlagArray;
}
void Node::InputArrayInsert(const QString &id, int index, bool undoable)
{
if (undoable) {
Core::instance()->undo_stack()->push(new ArrayInsertCommand(this, id, index));
} else {
// Add new input
ArrayResizeInternal(id, InputArraySize(id) + 1);
// Move connections down
InputConnections copied_edges = input_connections();
for (auto it=copied_edges.crbegin(); it!=copied_edges.crend(); it++) {
if (it->first.element() >= index) {
// Disconnect this and reconnect it one element down
NodeInput new_edge = it->first;
new_edge.set_element(new_edge.element() + 1);
DisconnectEdge(it->second, it->first);
ConnectEdge(it->second, new_edge);
}
}
// Shift values and keyframes up one element
for (int i=InputArraySize(id)-1; i>index; i--) {
CopyValuesOfElement(this, this, id, i-1, i);
}
// Reset value of element we just "inserted"
ClearElement(id, index);
}
}
void Node::InputArrayResize(const QString &id, int size, bool undoable)
{
if (InputArraySize(id) == size) {
return;
}
ArrayResizeCommand* c = new ArrayResizeCommand(this, id, size);
if (undoable) {
Core::instance()->undo_stack()->push(c);
} else {
c->redo();
delete c;
}
}
void Node::InputArrayRemove(const QString &id, int index, bool undoable)
{
if (undoable) {
Core::instance()->undo_stack()->push(new ArrayRemoveCommand(this, id, index));
} else {
// Remove input
ArrayResizeInternal(id, InputArraySize(id) - 1);
// Move connections up
InputConnections copied_edges = input_connections();
for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) {
if (it->first.element() >= index) {
// Disconnect this and reconnect it one element up if it's not the element being removed
DisconnectEdge(it->second, it->first);
if (it->first.element() > index) {
NodeInput new_edge = it->first;
new_edge.set_element(new_edge.element() - 1);
ConnectEdge(it->second, new_edge);
}
}
}
// Shift values and keyframes down one element
int arr_sz = InputArraySize(id);
for (int i=index; i<arr_sz; i++) {
// Copying ArraySize()+1 is actually legal because immediates are never deleted
CopyValuesOfElement(this, this, id, i+1, i);
}
// Reset value of last element
ClearElement(id, arr_sz);
}
}
int Node::InputArraySize(const QString &id) const
{
const Input* i = GetInternalInputData(id);
if (i) {
return i->array_size;
} else {
ReportInvalidInput("retrieve array size of", id);
return 0;
}
}
const NodeKeyframeTrack &Node::GetTrackFromKeyframe(NodeKeyframe *key) const
{
return GetImmediate(key->input(), key->element())->keyframe_tracks().at(key->track());
}
NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const
{
if (element == -1) {
return standard_immediates_.value(input, nullptr);
} else if (array_immediates_.contains(input)) {
const QVector<NodeInputImmediate*>& imm_arr = array_immediates_.value(input);
if (element >= 0 && element < imm_arr.size()) {
return imm_arr.at(element);
}
}
return nullptr;
}
Node::InputFlags Node::GetInputFlags(const QString &input) const
{
const Input* i = GetInternalInputData(input);
if (i) {
return i->flags;
} else {
ReportInvalidInput("retrieve flags of", input);
return InputFlags(kInputFlagNormal);
}
}
NodeValueTable Node::Value(const QString& output, NodeValueDatabase &value) const
{
Q_UNUSED(output)
return value.Merge();
}
void Node::InvalidateCache(const TimeRange &range, const QString &from, int element)
{
Q_UNUSED(from)
Q_UNUSED(element)
SendInvalidateCache(range);
}
void Node::BeginOperation()
{
// Ripple through graph
for (const std::pair<NodeOutput, NodeInput>& output : output_connections_) {
output.second.node()->BeginOperation();
}
}
void Node::EndOperation()
{
// Ripple through graph
for (const std::pair<NodeOutput, NodeInput>& output : output_connections_) {
output.second.node()->EndOperation();
}
}
TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time) const
{
// Default behavior is no time adjustment at all
return input_time;
}
TimeRange Node::OutputTimeAdjustment(const QString &, int, const TimeRange &input_time) const
{
// Default behavior is no time adjustment at all
return input_time;
}
QVector<Node *> Node::CopyDependencyGraph(const QVector<Node *> &nodes, MultiUndoCommand *command)
{
int nb_nodes = nodes.size();
QVector<Node*> copies(nb_nodes);
for (int i=0; i<nb_nodes; i++) {
// Create another of the same node
Node* c = nodes.at(i)->copy();;
// Copy the values, but NOT the connections, since we'll be connecting to our own clones later
Node::CopyInputs(nodes.at(i), c, false);
// Add to graph
NodeGraph* graph = static_cast<NodeGraph*>(nodes.at(i)->parent());
if (command) {
command->add_child(new NodeAddCommand(graph, c));
} else {
c->setParent(graph);
}
// Store in array at the same index as source
copies[i] = c;
}
CopyDependencyGraph(nodes, copies, command);
return copies;
}
void Node::CopyDependencyGraph(const QVector<Node *> &src, const QVector<Node *> &dst, MultiUndoCommand *command)
{
for (int i=0; i<src.size(); i++) {
Node* src_node = src.at(i);
Node* dst_node = dst.at(i);
for (auto it=src_node->input_connections_.cbegin(); it!=src_node->input_connections_.cend(); it++) {
// Determine if the connected node is in our src list
int connection_index = src.indexOf(it->second.node());
if (connection_index > -1) {
// Find the equivalent node in the dst list
Node* dst_connection = dst.at(connection_index);
NodeOutput copied_output = NodeOutput(dst_connection, it->second.output());
NodeInput copied_input = NodeInput(dst_node, it->first.input(), it->first.element());
if (command) {
command->add_child(new NodeEdgeAddCommand(copied_output, copied_input));
} else {
ConnectEdge(copied_output, copied_input);
}
}
}
}
}
void Node::SendInvalidateCache(const TimeRange &range)
{
for (const OutputConnection& conn : output_connections_) {
// Send clear cache signal to the Node
const NodeInput& in = conn.second;
in.node()->InvalidateCache(range, in.input(), in.element());
}
}
void Node::InvalidateAll(const QString &input, int element)
{
InvalidateCache(TimeRange(RATIONAL_MIN, RATIONAL_MAX), input, element);
}
bool Node::Link(Node *a, Node *b)
{
if (a == b || !a || !b) {
return false;
}
if (AreLinked(a, b)) {
return false;
}
a->links_.append(b);
b->links_.append(a);
a->LinkChangeEvent();
b->LinkChangeEvent();
emit a->LinksChanged();
emit b->LinksChanged();
return true;
}
bool Node::Unlink(Node *a, Node *b)
{
if (!AreLinked(a, b)) {
return false;
}
a->links_.removeOne(b);
b->links_.removeOne(a);
a->LinkChangeEvent();
b->LinkChangeEvent();
emit a->LinksChanged();
emit b->LinksChanged();
return true;
}
bool Node::AreLinked(Node *a, Node *b)
{
return a->links_.contains(b);
}
void Node::AddInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags)
{
if (id.isEmpty()) {
qWarning() << "Rejected adding input with an empty ID on node" << this->id();
return;
}
if (HasParamWithID(id)) {
qWarning() << "Failed to add input to node" << this->id() << "- param with ID" << id << "already exists";
return;
}
Node::Input i;
i.type = type;
i.default_value = NodeValue::split_normal_value_into_track_values(type, default_value);
i.flags = flags;
i.array_size = 0;
input_ids_.append(id);
input_data_.append(i);
if (!standard_immediates_.value(id, nullptr)) {
standard_immediates_.insert(id, CreateImmediate(id));
}
emit InputAdded(id);
}
void Node::RemoveInput(const QString &id)
{
int index = input_ids_.indexOf(id);
if (index == -1) {
ReportInvalidInput("remove", id);
return;
}
input_ids_.removeAt(index);
input_data_.removeAt(index);
emit InputRemoved(id);
}
void Node::AddOutput(const QString &id)
{
if (id.isEmpty()) {
qWarning() << "Rejected adding output with an empty ID on node" << this->id();
return;
}
if (HasParamWithID(id)) {
qWarning() << "Failed to add output to node" << this->id() << "- param with ID" << id << "already exists";
return;
}
outputs_.append(id);
emit OutputAdded(id);
}
void Node::RemoveOutput(const QString &id)
{
if (outputs_.removeOne(id)) {
emit OutputRemoved(id);
} else {
ReportInvalidInput("remove", id);
}
}
void Node::ReportInvalidInput(const char *attempted_action, const QString& id) const
{
qWarning() << "Failed to" << attempted_action << "parameter" << id
<< "in node" << this->id() << "- input doesn't exist";
}
NodeInputImmediate *Node::CreateImmediate(const QString &input)
{
const Input* i = GetInternalInputData(input);
if (i) {
return new NodeInputImmediate(i->type, i->default_value);
} else {
ReportInvalidInput("create immediate", input);
return nullptr;
}
}
void Node::ArrayResizeInternal(const QString &id, int size)
{
Input* imm = GetInternalInputData(id);
if (!imm) {
ReportInvalidInput("set array size", id);
return;
}
if (imm->array_size != size) {
// Update array size
if (imm->array_size < size) {
// Size is larger, create any immediates that don't exist
QVector<NodeInputImmediate*>& subinputs = array_immediates_[id];
for (int i=subinputs.size(); i<size; i++) {
subinputs.append(CreateImmediate(id));
}
// Note that we do not delete any immediates when decreasing size since the user might still
// want that data. Therefore it's important to note that array_size_ does NOT necessarily
// equal subinputs_.size()
}
imm->array_size = size;
emit InputArraySizeChanged(id, size);
ParameterValueChanged(id, -1, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
}
}
int Node::GetInternalInputArraySize(const QString &input)
{
return array_immediates_.value(input).size();
}
void Node::SetInputName(const QString &id, const QString &name)
{
Input* i = GetInternalInputData(id);
if (i) {
i->human_name = name;
emit InputNameChanged(id, name);
} else {
ReportInvalidInput("set name of", id);
}
}
void Node::IgnoreInvalidationsFrom(const QString& input_id)
{
ignore_connections_.append(input_id);
}
void Node::IgnoreHashingFrom(const QString &input_id)
{
ignore_when_hashing_.append(input_id);
}
void Node::LoadInternal(QXmlStreamReader *reader, XMLNodeData &, uint, const QAtomicInt*)
{
reader->skipCurrentElement();
}
void Node::SaveInternal(QXmlStreamWriter *) const
{
}
bool Node::HasGizmos() const
{
return false;
}
void Node::DrawGizmos(NodeValueDatabase &, QPainter *)
{
}
bool Node::GizmoPress(NodeValueDatabase &, const QPointF &)
{
return false;
}
void Node::GizmoMove(const QPointF &, const rational&)
{
}
void Node::GizmoRelease()
{
}
const QString &Node::GetLabel() const
{
return label_;
}
void Node::SetLabel(const QString &s)
{
if (label_ != s) {
label_ = s;
emit LabelChanged(label_);
}
}
void Node::Hash(const QString &output, QCryptographicHash &hash, const rational& time) const
{
Q_UNUSED(output)
// Add this Node's ID and output being used
hash.addData(id().toUtf8());
hash.addData(output.toUtf8());
foreach (const QString& input, input_ids_) {
// For each input, try to hash its value
if (ignore_when_hashing_.contains(input)) {
continue;
}
int arr_sz = InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
HashInputElement(hash, input, i, time);
}
}
}
void Node::CopyInputs(Node *source, Node *destination, bool include_connections)
{
Q_ASSERT(source->id() == destination->id());
foreach (const QString& input, source->inputs()) {
CopyInput(source, destination, input, include_connections, true);
}
destination->SetPosition(source->GetPosition());
destination->SetLabel(source->GetLabel());
}
void Node::CopyInput(Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays)
{
Q_ASSERT(src->id() == dst->id());
CopyValuesOfElement(src, dst, input, -1);
// Copy array size
if (src->InputIsArray(input) && traverse_arrays) {
int src_array_sz = src->InputArraySize(input);
for (int i=0; i<src_array_sz; i++) {
CopyValuesOfElement(src, dst, input, i);
}
}
// Copy connections
if (include_connections) {
if (traverse_arrays) {
// Copy all connections
for (auto it=src->input_connections().cbegin(); it!=src->input_connections().cend(); it++) {
ConnectEdge(it->second, NodeInput(dst, input, it->first.element()));
}
} else {
// Just copy the primary connection (at -1)
if (src->IsInputConnected(input)) {
ConnectEdge(src->GetConnectedOutput(input), NodeInput(dst, input));
}
}
}
}
void Node::CopyValuesOfElement(Node *src, Node *dst, const QString &input, int src_element, int dst_element)
{
if (dst_element >= dst->GetInternalInputArraySize(input)) {
qDebug() << "Ignored destination element that was out of array bounds";
return;
}
// Copy standard value
dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element);
// Copy keyframes
dst->GetImmediate(input, dst_element)->delete_all_keyframes();
foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) {
foreach (NodeKeyframe* key, track) {
key->copy(dst_element, dst);
}
}
// Copy keyframing state
if (src->IsInputKeyframable(input)) {
dst->SetInputIsKeyframing(input, src->IsInputKeyframing(input, src_element), dst_element);
}
// If this is the root of an array, copy the array size
if (src_element == -1 && dst_element == -1) {
dst->InputArrayResize(input, dst->InputArraySize(input));
}
}
bool Node::CanBeDeleted() const
{
return can_be_deleted_;
}
void Node::SetCanBeDeleted(bool s)
{
can_be_deleted_ = s;
}
void GetDependenciesRecursively(QVector<Node*>& list, const Node* node, bool traverse, bool exclusive_only)
{
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
Node* connected_node = it->second.node();
if (connected_node->outputs().size() == 1 || !exclusive_only) {
if (!list.contains(connected_node)) {
list.append(connected_node);
if (traverse) {
GetDependenciesRecursively(list, connected_node, traverse, exclusive_only);
}
}
}
}
}
/**
* @brief Recursively collects dependencies of Node `n` and appends them to QList `list`
*
* @param traverse
*
* TRUE to recursively traverse each node for a complete dependency graph. FALSE to return only the immediate
* dependencies.
*/
QVector<Node *> Node::GetDependenciesInternal(bool traverse, bool exclusive_only) const
{
QVector<Node*> list;
GetDependenciesRecursively(list, this, traverse, exclusive_only);
return list;
}
void Node::HashInputElement(QCryptographicHash &hash, const QString& input, int element, const rational &time) const
{
// Get time adjustment
// For a single frame, we only care about one of the times
rational input_time = InputTimeAdjustment(input, element, TimeRange(time, time)).in();
if (IsInputConnected(input, element)) {
// Traverse down this edge
NodeOutput output = GetConnectedOutput(input, element);
output.node()->Hash(output.output(), hash, input_time);
} else {
// Grab the value at this time
QVariant value = GetValueAtTime(input, input_time, element);
hash.addData(NodeValue::ValueToBytes(GetInputDataType(input), value));
}
}
QVector<Node *> Node::GetDependencies() const
{
return GetDependenciesInternal(true, false);
}
QVector<Node *> Node::GetExclusiveDependencies() const
{
return GetDependenciesInternal(true, true);
}
QVector<Node *> Node::GetImmediateDependencies() const
{
return GetDependenciesInternal(false, false);
}
ShaderCode Node::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
return ShaderCode(QString(), QString());
}
void Node::ProcessSamples(NodeValueDatabase &, const SampleBufferPtr, SampleBufferPtr, int) const
{
}
void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const
{
Q_UNUSED(frame)
Q_UNUSED(job)
}
bool Node::OutputsTo(Node *n, bool recursively) const
{
for (const OutputConnection& conn : output_connections_) {
Node* connected = conn.second.node();
if (connected == n) {
return true;
} else if (recursively && connected->OutputsTo(n, recursively)) {
return true;
}
}
return false;
}
bool Node::OutputsTo(const QString &id, bool recursively) const
{
for (const OutputConnection& conn : output_connections_) {
Node* connected = conn.second.node();
if (connected->id() == id) {
return true;
} else if (recursively && connected->OutputsTo(id, recursively)) {
return true;
}
}
return false;
}
bool Node::OutputsTo(const NodeInput &input, bool recursively) const
{
for (const OutputConnection& conn : output_connections_) {
const NodeInput& connected = conn.second;
if (connected == input) {
return true;
} else if (recursively && connected.node()->OutputsTo(input, recursively)) {
return true;
}
}
return false;
}
bool Node::InputsFrom(Node *n, bool recursively) const
{
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
const NodeOutput& connected = it->second;
if (connected.node() == n) {
return true;
} else if (recursively && connected.node()->InputsFrom(n, recursively)) {
return true;
}
}
return false;
}
bool Node::InputsFrom(const QString &id, bool recursively) const
{
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
const NodeOutput& connected = it->second;
if (connected.node()->id() == id) {
return true;
} else if (recursively && connected.node()->InputsFrom(id, recursively)) {
return true;
}
}
return false;
}
int Node::GetRoutesTo(Node *n) const
{
bool outputs_directly = false;
int routes = 0;
foreach (const OutputConnection& conn, output_connections_) {
Node* connected_node = conn.second.node();
if (connected_node == n) {
outputs_directly = true;
} else {
routes += connected_node->GetRoutesTo(n);
}
}
if (outputs_directly) {
routes++;
}
return routes;
}
void Node::DisconnectAll()
{
// Disconnect inputs (copy map since internal map will change as we disconnect)
InputConnections copy = input_connections_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
DisconnectEdge(it->second, it->first);
}
while (!output_connections_.empty()) {
OutputConnection conn = output_connections_.back();
DisconnectEdge(conn.first, conn.second);
}
}
QString Node::GetCategoryName(const CategoryID &c)
{
switch (c) {
case kCategoryInput:
return tr("Input");
case kCategoryOutput:
return tr("Output");
case kCategoryGeneral:
return tr("General");
case kCategoryDistort:
return tr("Distort");
case kCategoryMath:
return tr("Math");
case kCategoryColor:
return tr("Color");
case kCategoryFilter:
return tr("Filter");
case kCategoryTimeline:
return tr("Timeline");
case kCategoryGenerator:
return tr("Generator");
case kCategoryChannels:
return tr("Channel");
case kCategoryTransition:
return tr("Transition");
case kCategoryProject:
return tr("Project");
case kCategoryUnknown:
case kCategoryCount:
break;
}
return tr("Uncategorized");
}
QVector<TimeRange> Node::TransformTimeTo(const TimeRange &time, Node *target, bool input_dir)
{
QVector<TimeRange> paths_found;
if (input_dir) {
// If this input is connected, traverse it to see if we stumble across the specified `node`
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
TimeRange input_adjustment = InputTimeAdjustment(it->first.input(), it->first.element(), time);
Node* connected = it->second.node();
if (connected == target) {
// We found the target, no need to keep traversing
if (!paths_found.contains(input_adjustment)) {
paths_found.append(input_adjustment);
}
} else {
// We did NOT find the target, traverse this
paths_found.append(connected->TransformTimeTo(input_adjustment, target, input_dir));
}
}
} else {
// If this input is connected, traverse it to see if we stumble across the specified `node`
foreach (const OutputConnection& conn, output_connections_) {
Node* connected_node = conn.second.node();
TimeRange output_adjustment = connected_node->OutputTimeAdjustment(conn.second.input(), conn.second.element(), time);
if (connected_node == target) {
paths_found.append(output_adjustment);
} else {
paths_found.append(connected_node->TransformTimeTo(output_adjustment, target, input_dir));
}
}
}
return paths_found;
}
QVariant Node::PtrToValue(void *ptr)
{
return reinterpret_cast<quintptr>(ptr);
}
const QPointF &Node::GetPosition() const
{
return position_;
}
void Node::SetPosition(const QPointF &pos)
{
position_ = pos;
emit PositionChanged(position_);
}
void Node::ParameterValueChanged(const QString& input, int element, const TimeRange& range)
{
InputValueChangedEvent(input, element);
emit ValueChanged(NodeInput(this, input, element), range);
if (ignore_connections_.contains(input)) {
return;
}
InvalidateCache(range, input, element);
}
void Node::LoadImmediate(QXmlStreamReader *reader, const QString& input, int element, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
{
NodeValue::Type data_type = GetInputDataType(input);
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("standard")) {
// Load standard value
int val_index = 0;
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("track")) {
QString value_text = reader->readElementText();
QVariant value_on_track;
if (!value_text.isEmpty()) {
value_on_track = NodeValue::StringToValue(data_type, value_text, element);
}
SetSplitStandardValueOnTrack(input, val_index, value_on_track, element);
val_index++;
} else {
reader->skipCurrentElement();
}
}
} else if (reader->name() == QStringLiteral("keyframing") && IsInputKeyframable(input)) {
SetInputIsKeyframing(input, reader->readElementText().toInt(), element);
} else if (reader->name() == QStringLiteral("keyframes")) {
int track = 0;
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("track")) {
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("key")) {
QString key_input;
rational key_time;
NodeKeyframe::Type key_type;
QVariant key_value;
QPointF key_in_handle;
QPointF key_out_handle;
XMLAttributeLoop(reader, attr) {
if (cancelled && *cancelled) {
return;
}
if (attr.name() == QStringLiteral("input")) {
key_input = attr.value().toString();
} else if (attr.name() == QStringLiteral("time")) {
key_time = rational::fromString(attr.value().toString());
} else if (attr.name() == QStringLiteral("type")) {
key_type = static_cast<NodeKeyframe::Type>(attr.value().toInt());
} else if (attr.name() == QStringLiteral("inhandlex")) {
key_in_handle.setX(attr.value().toDouble());
} else if (attr.name() == QStringLiteral("inhandley")) {
key_in_handle.setY(attr.value().toDouble());
} else if (attr.name() == QStringLiteral("outhandlex")) {
key_out_handle.setX(attr.value().toDouble());
} else if (attr.name() == QStringLiteral("outhandley")) {
key_out_handle.setY(attr.value().toDouble());
}
}
key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true);
NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, this);
key->set_bezier_control_in(key_in_handle);
key->set_bezier_control_out(key_out_handle);
} else {
reader->skipCurrentElement();
}
}
track++;
} else {
reader->skipCurrentElement();
}
}
} else if (reader->name() == QStringLiteral("csinput")) {
SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText());
} else if (reader->name() == QStringLiteral("csdisplay")) {
SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText());
} else if (reader->name() == QStringLiteral("csview")) {
SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText());
} else if (reader->name() == QStringLiteral("cslook")) {
SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText());
} else {
reader->skipCurrentElement();
}
}
}
void Node::SaveImmediate(QXmlStreamWriter *writer, const QString& input, int element) const
{
if (IsInputKeyframable(input)) {
writer->writeTextElement(QStringLiteral("keyframing"), QString::number(IsInputKeyframing(input, element)));
}
NodeValue::Type data_type = GetInputDataType(input);
// Write standard value
writer->writeStartElement(QStringLiteral("standard"));
foreach (const QVariant& v, GetSplitStandardValue(input, element)) {
writer->writeTextElement(QStringLiteral("track"), NodeValue::ValueToString(data_type, v, true));
}
writer->writeEndElement(); // standard
// Write keyframes
writer->writeStartElement(QStringLiteral("keyframes"));
foreach (const NodeKeyframeTrack& track, GetKeyframeTracks(input, element)) {
writer->writeStartElement(QStringLiteral("track"));
foreach (NodeKeyframe* key, track) {
writer->writeStartElement(QStringLiteral("key"));
writer->writeAttribute(QStringLiteral("input"), key->input());
writer->writeAttribute(QStringLiteral("time"), key->time().toString());
writer->writeAttribute(QStringLiteral("type"), QString::number(key->type()));
writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x()));
writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y()));
writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x()));
writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y()));
writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true));
writer->writeEndElement(); // key
}
writer->writeEndElement(); // track
}
writer->writeEndElement(); // keyframes
if (data_type == NodeValue::kColor) {
// Save color management information
writer->writeTextElement(QStringLiteral("csinput"), GetInputProperty(input, QStringLiteral("col_input")).toString());
writer->writeTextElement(QStringLiteral("csdisplay"), GetInputProperty(input, QStringLiteral("col_display")).toString());
writer->writeTextElement(QStringLiteral("csview"), GetInputProperty(input, QStringLiteral("col_view")).toString());
writer->writeTextElement(QStringLiteral("cslook"), GetInputProperty(input, QStringLiteral("col_look")).toString());
}
}
TimeRange Node::GetRangeAffectedByKeyframe(NodeKeyframe *key) const
{
const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key);
int keyframe_index = key_track.indexOf(key);
TimeRange range = GetRangeAroundIndex(key->input(), keyframe_index, key->track(), key->element());
// If a previous key exists and it's a hold, we don't need to invalidate those frames
if (key_track.size() > 1
&& keyframe_index > 0
&& key_track.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) {
range.set_in(key->time());
}
return range;
}
TimeRange Node::GetRangeAroundIndex(const QString &input, int index, int track, int element) const
{
rational range_begin = RATIONAL_MIN;
rational range_end = RATIONAL_MAX;
const NodeKeyframeTrack& key_track = GetImmediate(input, element)->keyframe_tracks().at(track);
if (key_track.size() > 1) {
if (index > 0) {
// If this is not the first key, we'll need to limit it to the key just before
range_begin = key_track.at(index - 1)->time();
}
if (index < key_track.size() - 1) {
// If this is not the last key, we'll need to limit it to the key just after
range_end = key_track.at(index + 1)->time();
}
}
return TimeRange(range_begin, range_end);
}
void Node::ClearElement(const QString& input, int index)
{
GetImmediate(input, index)->delete_all_keyframes();
if (IsInputKeyframable(input)) {
SetInputIsKeyframing(input, false, index);
}
SetSplitStandardValue(input, GetSplitDefaultValue(input), index);
}
QRectF Node::CreateGizmoHandleRect(const QPointF &pt, int radius)
{
return QRectF(pt.x() - radius,
pt.y() - radius,
2*radius,
2*radius);
}
double Node::GetGizmoHandleRadius(const QTransform &transform)
{
double raw_value = QFontMetrics(qApp->font()).height() * 0.25;
raw_value /= transform.m11();
return raw_value;
}
void Node::DrawAndExpandGizmoHandles(QPainter *p, int handle_radius, QRectF *rects, int count)
{
for (int i=0; i<count; i++) {
QRectF& r = rects[i];
// Draw rect on screen
p->drawRect(r);
// Extend rect so it's easier to drag with handle
r.adjust(-handle_radius, -handle_radius, handle_radius, handle_radius);
}
}
void Node::InputValueChangedEvent(const QString &input, int element)
{
Q_UNUSED(input)
Q_UNUSED(element)
}
void Node::InputConnectedEvent(const QString &input, int element, const NodeOutput &output)
{
Q_UNUSED(input)
Q_UNUSED(element)
Q_UNUSED(output)
}
void Node::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output)
{
Q_UNUSED(input)
Q_UNUSED(element)
Q_UNUSED(output)
}
void Node::OutputConnectedEvent(const QString &output, const NodeInput &input)
{
Q_UNUSED(output)
Q_UNUSED(input)
}
void Node::OutputDisconnectedEvent(const QString &output, const NodeInput &input)
{
Q_UNUSED(output)
Q_UNUSED(input)
}
void Node::childEvent(QChildEvent *event)
{
super::childEvent(event);
NodeKeyframe* key = dynamic_cast<NodeKeyframe*>(event->child());
if (key) {
NodeInput i(this, key->input(), key->element());
if (event->type() == QEvent::ChildAdded) {
GetImmediate(key->input(), key->element())->insert_keyframe(key);
connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
connect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
connect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);
emit KeyframeAdded(key);
ParameterValueChanged(i, GetRangeAffectedByKeyframe(key));
} else if (event->type() == QEvent::ChildRemoved) {
TimeRange time_affected = GetRangeAffectedByKeyframe(key);
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);
GetImmediate(key->input(), key->element())->remove_keyframe(key);
emit KeyframeRemoved(key);
ParameterValueChanged(i, time_affected);
}
}
}
void Node::InvalidateFromKeyframeBezierInChange()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);
int keyframe_index = track.indexOf(key);
rational start = RATIONAL_MIN;
rational end = key->time();
if (keyframe_index > 0) {
start = track.at(keyframe_index - 1)->time();
}
ParameterValueChanged(key->key_track_ref().input(), TimeRange(start, end));
}
void Node::InvalidateFromKeyframeBezierOutChange()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);
int keyframe_index = track.indexOf(key);
rational start = key->time();
rational end = RATIONAL_MAX;
if (keyframe_index < track.size() - 1) {
end = track.at(keyframe_index + 1)->time();
}
ParameterValueChanged(key->key_track_ref().input(), TimeRange(start, end));
}
void Node::InvalidateFromKeyframeTimeChange()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
NodeInputImmediate* immediate = GetImmediate(key->input(), key->element());
TimeRange original_range = GetRangeAffectedByKeyframe(key);
TimeRangeList invalidate_range;
invalidate_range.insert(original_range);
if (!(original_range.in() < key->time() && original_range.out() > key->time())) {
// This keyframe needs resorting, store it and remove it from the list
immediate->remove_keyframe(key);
// Automatically insertion sort
immediate->insert_keyframe(key);
// Invalidate new area that the keyframe has been moved to
invalidate_range.insert(GetRangeAffectedByKeyframe(key));
}
// Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it
// was resorted in the if block above)
foreach (const TimeRange& r, invalidate_range) {
ParameterValueChanged(key->key_track_ref().input(), r);
}
}
void Node::InvalidateFromKeyframeValueChange()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key));
}
void Node::InvalidateFromKeyframeTypeChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);
if (track.size() == 1) {
// If there are no other frames, the interpolation won't do anything
return;
}
// Invalidate entire range
ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element()));
}
Project *Node::ArrayInsertCommand::GetRelevantProject() const
{
return node_->project();
}
Project *Node::ArrayRemoveCommand::GetRelevantProject() const
{
return node_->project();
}
Project *Node::ArrayResizeCommand::GetRelevantProject() const
{
return node_->project();
}
}