/*** Olive - Non-Linear Video Editor Copyright (C) 2021 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 . ***/ #include "node.h" #include #include #include #include #include "common/bezier.h" #include "common/lerp.h" #include "common/timecodefunctions.h" #include "common/xmlutils.h" #include "core.h" #include "config/config.h" #include "node/project/footage/footage.h" #include "project/project.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), last_change_time_(0), folder_(nullptr), operation_stack_(0), cache_result_(false) { 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(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")) { while (XMLReadNextStartElement(reader)) { if (!LoadCustom(reader, xml_node_data, version, cancelled)) { reader->skipCurrentElement(); } } } 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(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(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(it->second.node()))); writer->writeTextElement(QStringLiteral("output"), it->second.output()); writer->writeEndElement(); // connection } writer->writeEndElement(); // connections writer->writeStartElement(QStringLiteral("custom")); SaveCustom(writer); writer->writeEndElement(); // custom } Project* Node::project() const { return dynamic_cast(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({output, input})); // Update change times input.node()->UpdateLastChangedTime(); // 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({output, input}))); // Update change times input.node()->UpdateLastChangedTime(); // 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; iwriteStartElement(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* input_meta = GetInternalInputData(id); if (input_meta) { input_meta->type = type; int array_sz = InputArraySize(id); for (int i=-1; iset_data_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 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(); } } 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;itime() >= 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(); } NodeValue::Type type = GetInputDataType(input); // If we're here, the time must be somewhere in between the keyframes for (int i=0;itime() == time || !NodeValue::type_can_be_interpolated(type) || (before->type() == NodeKeyframe::kHold && after->time() > time)) { // 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 double before_val, after_val, interpolated; if (type == NodeValue::kRational) { before_val = before->value().value().toDouble(); after_val = after->value().value().toDouble(); } else { before_val = before->value().toDouble(); after_val = after->value().toDouble(); } 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_val, before_val + before->valid_bezier_control_out().y(), after_val + after->valid_bezier_control_in().y(), after_val, t); interpolated = 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_val + control_point.y(); } else { control_point = after->valid_bezier_control_in(); control_point_time = after->time().toDouble() + control_point.x(); control_point_value = after_val + 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_val, control_point_value, after_val, t); interpolated = 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()); interpolated = lerp(before_val, after_val, period_progress); } if (type == NodeValue::kRational) { return QVariant::fromValue(rational::fromDouble(interpolated)); } else { return interpolated; } } } } 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 { SplitValue val = GetSplitDefaultValue(input); if (track < val.size()) { return val.at(track); } else { return QVariant(); } } const QVector &Node::GetKeyframeTracks(const QString &input, int element) const { return GetImmediate(input, element)->keyframe_tracks(); } QVector 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 *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; iset_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; iarray_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& 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, qint64 job_time) { Q_UNUSED(from) Q_UNUSED(element) SendInvalidateCache(range, job_time); } void Node::BeginOperation() { // Increase operation stack operation_stack_++; } void Node::EndOperation() { // Decrease operation stack operation_stack_--; } 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::CopyDependencyGraph(const QVector &nodes, MultiUndoCommand *command) { int nb_nodes = nodes.size(); QVector copies(nb_nodes); for (int i=0; icopy(); // 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(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 &src, const QVector &dst, MultiUndoCommand *command) { for (int i=0; iinput_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); } } } } } Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& created, const Node *node, MultiUndoCommand *command) { // Make a new node of the same type Node* copy = node->copy(); // Add to map created.insert(node, copy); // Copy values to the clone CopyInputs(node, copy, false); // Add it to the same graph command->add_child(new NodeAddCommand(node->parent(), copy)); // Go through input connections and copy if non-item and connect if item for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) { NodeInput input = it->first; NodeOutput output = it->second; Node* connected = output.node(); Node* connected_copy; if (connected->IsItem()) { // This is an item and we avoid copying those and just connect to them directly connected_copy = connected; } else { // Non-item, we want to clone this too connected_copy = created.value(connected, nullptr); if (!connected_copy) { connected_copy = CopyNodeAndDependencyGraphMinusItemsInternal(created, connected, command); } } command->add_child(new NodeEdgeAddCommand(NodeOutput(connected_copy, output.output()), NodeInput(copy, input.input(), input.element()))); } return copy; } Node *Node::CopyNodeAndDependencyGraphMinusItems(const Node *node, MultiUndoCommand *command) { QMap created; return CopyNodeAndDependencyGraphMinusItemsInternal(created, node, command); } Node *Node::CopyNodeInGraph(const Node *node, MultiUndoCommand *command) { Node* copy; if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) { copy = Node::CopyNodeAndDependencyGraphMinusItems(node, command); } else { copy = node->copy(); command->add_child(new NodeAddCommand(static_cast(node->parent()), copy)); command->add_child(new NodeCopyInputsCommand(node, copy, true)); } return copy; } void Node::SendInvalidateCache(const TimeRange &range, qint64 job_time) { if (GetOperationStack() == 0) { 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(), job_time); } } } 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::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index) { 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_.insert(index, id); input_data_.insert(index, 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& subinputs = array_immediates_[id]; for (int i=subinputs.size(); iarray_size; imm->array_size = size; emit InputArraySizeChanged(id, old_sz, 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); } bool Node::LoadCustom(QXmlStreamReader *, XMLNodeData &, uint, const QAtomicInt*) { return false; } void Node::SaveCustom(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()); auto inputs = inputs_for_output(output); foreach (const QString& input, inputs) { // For each input, try to hash its value if (ignore_when_hashing_.contains(input)) { continue; } int arr_sz = InputArraySize(input); for (int i=-1; iid() == destination->id()); foreach (const QString& input, source->inputs()) { CopyInput(source, destination, input, include_connections, true); } destination->SetPosition(source->GetPosition()); destination->SetLabel(source->GetLabel()); destination->SetOverrideColor(source->GetOverrideColor()); } void Node::CopyInput(const 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; iinput_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(const 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->ArrayResizeInternal(input, src->InputArraySize(input)); } } bool Node::CanBeDeleted() const { return can_be_deleted_; } void Node::SetCanBeDeleted(bool s) { can_be_deleted_ = s; } void GetDependenciesRecursively(QVector& 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 (!exclusive_only || (connected_node->outputs().size() == 1 && !connected_node->IsItem())) { 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::GetDependenciesInternal(bool traverse, bool exclusive_only) const { QVector 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::GetDependencies() const { return GetDependenciesInternal(true, false); } QVector Node::GetExclusiveDependencies() const { return GetDependenciesInternal(true, true); } QVector 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 Node::TransformTimeTo(const TimeRange &time, Node *target, bool input_dir) { QVector 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(ptr); } const QPointF &Node::GetPosition() const { return position_; } void Node::SetPosition(const QPointF &pos, bool move_dependencies_relatively_too) { QPointF old_pos = position_; position_ = pos; emit PositionChanged(position_); if (move_dependencies_relatively_too) { QPointF difference = pos - old_pos; for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { Node* c = it->second.node(); c->SetPosition(c->GetPosition() + difference, true); } } } void Node::ParameterValueChanged(const QString& input, int element, const TimeRange& range) { UpdateLastChangedTime(); 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) { Q_UNUSED(xml_node_data) 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")) { QVariant value_on_track; if (data_type == NodeValue::kVideoParams) { VideoParams vp; vp.Load(reader); value_on_track = QVariant::fromValue(vp); } else if (data_type == NodeValue::kAudioParams) { AudioParams ap; ap.Load(reader); value_on_track = QVariant::fromValue(ap); } else { QString value_text = reader->readElementText(); 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 = NodeKeyframe::kLinear; 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(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->writeStartElement(QStringLiteral("track")); if (data_type == NodeValue::kVideoParams) { v.value().Save(writer); } else if (data_type == NodeValue::kAudioParams) { v.value().Save(writer); } else { writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); } writer->writeEndElement(); // track } 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()); } } void Node::UpdateLastChangedTime() { last_change_time_ = QDateTime::currentMSecsSinceEpoch(); } 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; idrawRect(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(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(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(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(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(sender()); ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key)); } void Node::InvalidateFromKeyframeTypeChanged() { NodeKeyframe* key = static_cast(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(); } void NodeSetPositionAndShiftSurroundingsCommand::redo() { if (commands_.isEmpty()) { // Move first node NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, position_, move_dependencies_); set_pos_command->redo(); commands_.append(set_pos_command); // Get bounding rect QRectF bounding_rect(position_.x() - 0.5, position_.y() - 0.5, 1, 1); // Start moving other nodes foreach (Node* surrounding, node_->parent()->nodes()) { if (bounding_rect.contains(surrounding->GetPosition()) && surrounding != node_) { QPointF new_pos = surrounding->GetPosition(); qreal move_rate = 0.50; if (surrounding->GetPosition().y() < position_.y()) { move_rate = -move_rate; } new_pos.setY(new_pos.y() + move_rate); auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, new_pos, true); sur_command->redo(); commands_.append(sur_command); } } } else { for (int i=0; iredo(); } } } }