/*** 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 . ***/ #ifndef NODE_H #define NODE_H #include #include #include #include #include #include #include "codec/frame.h" #include "codec/samplebuffer.h" #include "common/rational.h" #include "common/timerange.h" #include "common/xmlutils.h" #include "node/keyframe.h" #include "node/inputimmediate.h" #include "node/param.h" #include "render/audioparams.h" #include "render/job/generatejob.h" #include "render/job/samplejob.h" #include "render/job/shaderjob.h" #include "render/shadercode.h" #include "splitvalue.h" namespace olive { class NodeGraph; class Folder; /** * @brief A single processing unit that can be connected with others to create intricate processing systems * * A cornerstone of "visual programming", a node is a single "function" that takes input and returns an output that can * be connected to other nodes. Inputs can be either user-set or retrieved from the output of another node. By joining * several nodes together, intricate, highly customizable, and infinitely extensible systems can be made for processing * data. It can also all be exposed to the user without forcing them to write code or compile anything. * * A major example in Olive is the entire rendering workflow. To render a frame, Olive will work through a node graph * that can be infinitely customized by the user to create images. * * This is a simple base class designed to contain all the functionality for this kind of processing connective unit. * It is an abstract class intended to be subclassed to create nodes with actual functionality. */ class Node : public QObject { Q_OBJECT public: enum CategoryID { kCategoryUnknown = -1, kCategoryInput, kCategoryOutput, kCategoryGenerator, kCategoryMath, kCategoryFilter, kCategoryColor, kCategoryGeneral, kCategoryTimeline, kCategoryChannels, kCategoryTransition, kCategoryDistort, kCategoryProject, kCategoryCount }; Node(bool create_default_output = true); virtual ~Node() override; /** * @brief Creates a clone of the Node * * By default, the clone will NOT have the values and connections of the original node. The caller is responsible for * copying that data with functions like CopyInputs() as copies may be done for different reasons. */ virtual Node* copy() const = 0; /** * @brief Convenience function - assumes parent is a NodeGraph */ NodeGraph* parent() const; Project* project() const; /** * @brief Clear current node variables and replace them with */ void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled); /** * @brief Save this node into a text/XML format */ void Save(QXmlStreamWriter* writer) const; /** * @brief Return the name of the node * * This is the node's name shown to the user. This must be overridden by subclasses, and preferably run through the * translator. */ virtual QString Name() const = 0; /** * @brief Returns a shortened name of this node if applicable * * Defaults to returning Name() but can be overridden. */ virtual QString ShortName() const; /** * @brief Return the unique identifier of the node * * This is used in save files and any other times a specific node must be picked out at runtime. This must be an ID * completely unique to this node, and preferably in bundle identifier format (e.g. "org.company.Name"). This string * should NOT be translated. */ virtual QString id() const = 0; /** * @brief Return the category this node is in (optional for subclassing, but recommended) * * In any organized node menus, show the node in this category. If this node should be in a subfolder of a subfolder, * use a "/" to separate categories (e.g. "Distort/Noise"). The string should not start with a "/" as this will be * interpreted as an empty string category. This value should be run through a translator as its largely user * oriented. */ virtual QVector Category() const = 0; /** * @brief Return a description of this node's purpose (optional for subclassing, but recommended) * * A short (1-2 sentence) description of what this node should do to help the user understand its purpose. This should * be run through a translator. */ virtual QString Description() const; const QString& ToolTip() const { return tooltip_; } Folder* folder() const { return folder_; } virtual bool IsItem() const { return false; } /** * @brief Function called to retranslate parameter names (should be overridden in derivatives) */ virtual void Retranslate(); virtual QIcon icon() const; virtual QString duration() const {return QString();} virtual QString rate() const {return QString();} const QVector& inputs() const { return input_ids_; } const QVector& outputs() const { return outputs_; } bool HasInputWithID(const QString& id) const { return input_ids_.contains(id); } bool HasOutputWithID(const QString& id) const { return outputs_.contains(id); } bool HasParamWithID(const QString& id) const { return HasInputWithID(id) || HasOutputWithID(id); } /** * @brief Retrieve the color of this node */ Color color() const; /** * @brief Same as color() but return a pretty gradient version */ QLinearGradient gradient_color(qreal top, qreal bottom) const; /** * @brief Uses config and returns either color() for flat shading or gradient for gradient */ QBrush brush(qreal top, qreal bottom) const; /** * @brief Sets the override color. Set to -1 for no override color. */ void SetOverrideColor(int index) { if (override_color_ != index) { override_color_ = index; emit ColorChanged(); } } static void ConnectEdge(const NodeOutput& output, const NodeInput& input); static void DisconnectEdge(const NodeOutput& output, const NodeInput& input); QString GetInputName(const QString& id) const; void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; bool IsInputConnectable(const QString& input) const; bool IsInputKeyframable(const QString& input) const; bool IsInputKeyframing(const QString& input, int element = -1) const; bool IsInputKeyframing(const NodeInput& input) const { return IsInputKeyframing(input.input(), input.element()); } void SetInputIsKeyframing(const QString& input, bool e, int element = -1); void SetInputIsKeyframing(const NodeInput& input, bool e) { SetInputIsKeyframing(input.input(), e, input.element()); } bool IsInputConnected(const QString& input, int element = -1) const; bool IsInputConnected(const NodeInput& input) const { return IsInputConnected(input.input(), input.element()); } bool IsInputStatic(const QString& input, int element = -1) const { return !IsInputConnected(input, element) && !IsInputKeyframing(input, element); } bool IsInputStatic(const NodeInput& input) const { return IsInputStatic(input.input(), input.element()); } NodeOutput GetConnectedOutput(const QString& input, int element = -1) const; NodeOutput GetConnectedOutput(const NodeInput& input) const { return GetConnectedOutput(input.input(), input.element()); } Node* GetConnectedNode(const QString& input, int element = -1) const { return GetConnectedOutput(input, element).node(); } Node* GetConnectedNode(const NodeInput& input) const { return GetConnectedNode(input.input(), input.element()); } bool IsUsingStandardValue(const QString& input, int track, int element = -1) const; NodeValue::Type GetInputDataType(const QString& id) const; void SetInputDataType(const QString& id, const NodeValue::Type& type); bool HasInputProperty(const QString& id, const QString& name) const; QHash GetInputProperties(const QString& id) const; QVariant GetInputProperty(const QString& id, const QString& name) const; void SetInputProperty(const QString& id, const QString& name, const QVariant& value); QVariant GetValueAtTime(const QString& input, const rational& time, int element = -1) const { NodeValue::Type type = GetInputDataType(input); return NodeValue::combine_track_values_into_normal_value(type, GetSplitValueAtTime(input, time, element)); } QVariant GetValueAtTime(const NodeInput& input, const rational& time) { return GetValueAtTime(input.input(), time, input.element()); } SplitValue GetSplitValueAtTime(const QString& input, const rational& time, int element = -1) const; SplitValue GetSplitValueAtTime(const NodeInput& input, const rational& time) { return GetSplitValueAtTime(input.input(), time, input.element()); } QVariant GetSplitValueAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const; QVariant GetSplitValueAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const { return GetSplitValueAtTimeOnTrack(input.input(), time, track, input.element()); } QVariant GetSplitValueAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const { return GetSplitValueAtTimeOnTrack(input.input(), time, input.track()); } QVariant GetDefaultValue(const QString& input) const; SplitValue GetSplitDefaultValue(const QString& input) const; QVariant GetSplitDefaultValueOnTrack(const QString& input, int track) const; const QVector& GetKeyframeTracks(const QString& input, int element) const; const QVector& GetKeyframeTracks(const NodeInput& input) const { return GetKeyframeTracks(input.input(), input.element()); } QVector GetKeyframesAtTime(const QString& input, const rational& time, int element = -1) const; QVector GetKeyframesAtTime(const NodeInput& input, const rational& time) const { return GetKeyframesAtTime(input.input(), time, input.element()); } NodeKeyframe* GetKeyframeAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const; NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const { return GetKeyframeAtTimeOnTrack(input.input(), time, track, input.element()); } NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const { return GetKeyframeAtTimeOnTrack(input.input(), time, input.track()); } NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const; NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeInput& input, const rational& time, int track) const { return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, track, input.element()); } NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const { return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, input.track()); } int GetNumberOfKeyframeTracks(const QString& id) const; int GetNumberOfKeyframeTracks(const NodeInput& id) const { return GetNumberOfKeyframeTracks(id.input()); } NodeKeyframe* GetEarliestKeyframe(const QString& id, int element = -1) const; NodeKeyframe* GetEarliestKeyframe(const NodeInput& id) const { return GetEarliestKeyframe(id.input(), id.element()); } NodeKeyframe* GetLatestKeyframe(const QString& id, int element = -1) const; NodeKeyframe* GetLatestKeyframe(const NodeInput& id) const { return GetLatestKeyframe(id.input(), id.element()); } NodeKeyframe* GetClosestKeyframeBeforeTime(const QString& id, const rational& time, int element = -1) const; NodeKeyframe* GetClosestKeyframeBeforeTime(const NodeInput& id, const rational& time) const { return GetClosestKeyframeBeforeTime(id.input(), time, id.element()); } NodeKeyframe* GetClosestKeyframeAfterTime(const QString& id, const rational& time, int element = -1) const; NodeKeyframe* GetClosestKeyframeAfterTime(const NodeInput& id, const rational& time) const { return GetClosestKeyframeAfterTime(id.input(), time, id.element()); } bool HasKeyframeAtTime(const QString& id, const rational& time, int element = -1) const; bool HasKeyframeAtTime(const NodeInput& id, const rational& time) const { return HasKeyframeAtTime(id.input(), time, id.element()); } QStringList GetComboBoxStrings(const QString& id) const; QVariant GetStandardValue(const QString& id, int element = -1) const; QVariant GetStandardValue(const NodeInput& id) const { return GetStandardValue(id.input(), id.element()); } SplitValue GetSplitStandardValue(const QString& id, int element = -1) const; SplitValue GetSplitStandardValue(const NodeInput& id) const { return GetSplitStandardValue(id.input(), id.element()); } QVariant GetSplitStandardValueOnTrack(const QString& input, int track, int element = -1) const; QVariant GetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id) const { return GetSplitStandardValueOnTrack(id.input().input(), id.track(), id.input().element()); } void SetStandardValue(const QString& id, const QVariant& value, int element = -1); void SetStandardValue(const NodeInput& id, const QVariant& value) { SetStandardValue(id.input(), value, id.element()); } void SetSplitStandardValue(const QString& id, const SplitValue& value, int element = -1); void SetSplitStandardValue(const NodeInput& id, const SplitValue& value) { SetSplitStandardValue(id.input(), value, id.element()); } void SetSplitStandardValueOnTrack(const QString& id, int track, const QVariant& value, int element = -1); void SetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id, const QVariant& value) { SetSplitStandardValueOnTrack(id.input().input(), id.track(), value, id.input().element()); } bool InputIsArray(const QString& id) const; void InputArrayInsert(const QString& id, int index, bool undoable = false); void InputArrayResize(const QString& id, int size, bool undoable = false); void InputArrayRemove(const QString& id, int index, bool undoable = false); void InputArrayAppend(const QString& id, bool undoable = false) { InputArrayResize(id, InputArraySize(id) + 1, undoable); } void InputArrayPrepend(const QString& id, bool undoable = false) { InputArrayInsert(id, 0, undoable); } void InputArrayRemoveLast(const QString& id, bool undoable = false) { InputArrayResize(id, InputArraySize(id) - 1, undoable); } int InputArraySize(const QString& id) const; const NodeKeyframeTrack& GetTrackFromKeyframe(NodeKeyframe* key) const; using InputConnections = std::map; /** * @brief Return map of input connections * * Inputs can only have one connection, so the key is the input connected and the value is the * output that it's connected to. */ const InputConnections& input_connections() const { return input_connections_; } using OutputConnection = std::pair; using OutputConnections = std::vector; /** * @brief Return list of output connections * * An output can connect an infinite amount of inputs, so in this map, the key is the output and * the value is a vector of inputs. */ const OutputConnections& output_connections() const { return output_connections_; } /** * @brief Return a list of all Nodes that this Node's inputs are connected to (does not include this Node) */ QVector GetDependencies() const; /** * @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them * outside of this hierarchy. * * Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node. */ QVector GetExclusiveDependencies() const; /** * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one) */ QVector GetImmediateDependencies() const; /** * @brief Generate hardware accelerated code for this Node */ virtual ShaderCode GetShaderCode(const QString& shader_id) const; /** * @brief If Value() pushes a ShaderJob, this is the function that will process them. */ virtual void ProcessSamples(NodeValueDatabase &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const; /** * @brief If Value() pushes a GenerateJob, override this function for the image to create * * @param frame * * The destination buffer. It will already be allocated and ready for writing to. */ virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const; /** * @brief Returns whether this Node outputs to `n` * * @param n * * The node instance to check. * * @param recursively * * Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs * (FALSE). */ bool OutputsTo(Node* n, bool recursively) const; /** * @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance. */ bool OutputsTo(const QString& id, bool recursively) const; /** * @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node. */ bool OutputsTo(const NodeInput &input, bool recursively) const; /** * @brief Returns whether this node ever receives an input from a particular node instance */ bool InputsFrom(Node* n, bool recursively) const; /** * @brief Returns whether this node ever receives an input from a node with a particular ID */ bool InputsFrom(const QString& id, bool recursively) const; /** * @brief Determines how many paths go from this node out to another node */ int GetRoutesTo(Node* n) const; /** * @brief Severs all input and output connections */ void DisconnectAll(); /** * @brief Get the human-readable name for any category */ static QString GetCategoryName(const CategoryID &c); /** * @brief Transforms time from this node through the connections it takes to get to the specified node */ QVector TransformTimeTo(const TimeRange& time, Node* target, bool input_dir); /** * @brief Find nodes of a certain type that this Node takes inputs from */ template QVector FindInputNodes() const; template /** * @brief Find a node of a certain type that this Node outputs to */ QVector FindOutputNode(); /** * @brief Convert a pointer to a value that can be sent between NodeParams */ static QVariant PtrToValue(void* ptr); template /** * @brief Convert a NodeParam value to a pointer of any kind */ static T* ValueToPtr(const QVariant& ptr); /** * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and * requires re-rendering * * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can * call this function with transformed time and relay the signal that way. */ virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time); void InvalidateCache(const TimeRange& range, const QString& from, int element = -1) { InvalidateCache(range, from, element, last_change_time_); } void InvalidateCache(const TimeRange& range, const NodeInput& from) { InvalidateCache(range, from.input(), from.element()); } /** * @brief Limits cache invalidation temporarily * * If you intend to do a number of operations in quick succession, you can optimize it by running * this function with EndOperation(). */ virtual void BeginOperation(); /** * @brief Stops limiting cache invalidation and flushes changes */ virtual void EndOperation(); /** * @brief Adjusts time that should be sent to nodes connected to certain inputs. * * If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be * overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function. */ virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const; /** * @brief The inverse of InputTimeAdjustment() */ virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const; /** * @brief Copies inputs from from Node to another including connections * * Nodes must be of the same types (i.e. have the same ID) */ static void CopyInputs(const Node *source, Node* destination, bool include_connections = true); static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays); static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element); static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element) { return CopyValuesOfElement(src, dst, input, element, element); } /** * @brief Clones a set of nodes and connects the new ones the way the old ones were */ static QVector CopyDependencyGraph(const QVector& nodes, MultiUndoCommand *command); static void CopyDependencyGraph(const QVector& src, const QVector& dst, MultiUndoCommand *command); static Node* CopyNodeAndDependencyGraphMinusItems(const Node* node, MultiUndoCommand* command); static Node* CopyNodeInGraph(const Node* node, MultiUndoCommand* command); /** * @brief Return whether this Node can be deleted or not */ bool CanBeDeleted() const; /** * @brief Set whether this Node can be deleted in the UI or not */ void SetCanBeDeleted(bool s); /** * @brief The main processing function * * The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you * create, this is where the code for that goes. * * Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may * output different values depending on the time, and even if not, it will likely be receiving different input * depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass * the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior * of the NodeParam objects will handle everything related to it automatically. */ virtual NodeValueTable Value(const QString &output, NodeValueDatabase& value) const; const QPointF& GetPosition() const; void SetPosition(const QPointF& pos, bool move_dependencies_relatively_too = false); virtual bool HasGizmos() const; virtual void DrawGizmos(NodeValueDatabase& db, QPainter* p); virtual bool GizmoPress(NodeValueDatabase& db, const QPointF& p); virtual void GizmoMove(const QPointF& p, const rational &time); virtual void GizmoRelease(); const QString& GetLabel() const; void SetLabel(const QString& s); virtual void Hash(const QString& output, QCryptographicHash& hash, const rational &time) const; void InvalidateAll(const QString& input, int element = -1); bool HasLinks() const { return !links_.isEmpty(); } const QVector& links() const { return links_; } static bool Link(Node* a, Node* b); static bool Unlink(Node* a, Node* b); static bool AreLinked(Node* a, Node* b); void SetFolder(Folder* folder) { folder_ = folder; } static const QString kDefaultOutput; protected: enum InputFlag { /// By default, inputs are keyframable, connectable, and NOT arrays kInputFlagNormal = 0x0, kInputFlagArray = 0x1, kInputFlagNotKeyframable = 0x2, kInputFlagNotConnectable = 0x4 }; class InputFlags { public: explicit InputFlags() { f_ = kInputFlagNormal; } explicit InputFlags(uint64_t flags) { f_ = flags; } bool operator&(const InputFlag& f) const { return f_ & f; } private: uint64_t f_; }; void AddInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal)); void AddInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal)) { AddInput(id, type, QVariant(), flags); } void RemoveInput(const QString& id); void AddOutput(const QString& id = kDefaultOutput); void RemoveOutput(const QString& id); void SetInputName(const QString& id, const QString& name); void SetComboBoxStrings(const QString& id, const QStringList& strings) { SetInputProperty(id, QStringLiteral("combo_str"), strings); } void SendInvalidateCache(const TimeRange &range, qint64 job_time); /** * @brief Don't send cache invalidation signals if `input` is connected or disconnected * * By default, when a node is connected or disconnected from input, the Node assumes that the * parameters has changed throughout the duration of the clip (essential from 0 to infinity). * In some scenarios, it may be preferable to handle this signal separately in order to */ void IgnoreInvalidationsFrom(const QString &input_id); void IgnoreHashingFrom(const QString& input_id); virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled); virtual void SaveCustom(QXmlStreamWriter* writer) const; enum GizmoScaleHandles { kGizmoScaleTopLeft, kGizmoScaleTopCenter, kGizmoScaleTopRight, kGizmoScaleBottomLeft, kGizmoScaleBottomCenter, kGizmoScaleBottomRight, kGizmoScaleCenterLeft, kGizmoScaleCenterRight, kGizmoScaleCount, }; static QRectF CreateGizmoHandleRect(const QPointF& pt, int radius); static double GetGizmoHandleRadius(const QTransform& transform); static void DrawAndExpandGizmoHandles(QPainter* p, int handle_radius, QRectF* rects, int count); virtual void LinkChangeEvent(){} virtual void InputValueChangedEvent(const QString& input, int element); virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput& output); virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput& output); virtual void OutputConnectedEvent(const QString& output, const NodeInput& input); virtual void OutputDisconnectedEvent(const QString& output, const NodeInput& input); virtual void childEvent(QChildEvent *event) override; void SetToolTip(const QString& s) { tooltip_ = s; } signals: /** * @brief Signal emitted whenever the position is set through SetPosition() */ void PositionChanged(const QPointF& pos); /** * @brief Signal emitted when SetLabel() is called */ void LabelChanged(const QString& s); void ColorChanged(); void ValueChanged(const NodeInput& input, const TimeRange& range); void InputConnected(const NodeOutput& output, const NodeInput& input); void InputDisconnected(const NodeOutput& output, const NodeInput& input); void OutputConnected(const NodeOutput& output, const NodeInput& input); void OutputDisconnected(const NodeOutput& output, const NodeInput& input); void InputPropertyChanged(const QString& input, const QString& key, const QVariant& value); void LinksChanged(); void InputArraySizeChanged(const QString& input, int old_size, int new_size); void KeyframeAdded(NodeKeyframe* key); void KeyframeRemoved(NodeKeyframe* key); void KeyframeTimeChanged(); void KeyframeEnableChanged(const NodeInput& input, bool enabled); void InputAdded(const QString& id); void InputRemoved(const QString& id); void OutputAdded(const QString& id); void OutputRemoved(const QString& id); void InputNameChanged(const QString& id, const QString& name); void InputDataTypeChanged(const QString& id, NodeValue::Type type); private: class ArrayInsertCommand : public UndoCommand { public: ArrayInsertCommand(Node* node, const QString& input, int index) : node_(node), input_(input), index_(index) { } virtual Project* GetRelevantProject() const override; virtual void redo() override { node_->InputArrayInsert(input_, index_, false); } virtual void undo() override { node_->InputArrayRemove(input_, index_, false); } private: Node* node_; QString input_; int index_; }; class ArrayRemoveCommand : public UndoCommand { public: ArrayRemoveCommand(Node* node, const QString& input, int index) : node_(node), input_(input), index_(index) { } virtual Project* GetRelevantProject() const override; protected: virtual void redo() override { // Save immediate data if (node_->IsInputKeyframable(input_)) { is_keyframing_ = node_->IsInputKeyframing(input_, index_); } standard_value_ = node_->GetSplitStandardValue(input_, index_); keyframes_ = node_->GetKeyframeTracks(input_, index_); node_->GetImmediate(input_, index_)->delete_all_keyframes(&memory_manager_); node_->InputArrayRemove(input_, index_, false); } virtual void undo() override { node_->InputArrayInsert(input_, index_, false); // Restore keyframes foreach (const NodeKeyframeTrack& track, keyframes_) { foreach (NodeKeyframe* key, track) { key->setParent(node_); } } node_->SetSplitStandardValue(input_, standard_value_, index_); if (node_->IsInputKeyframable(input_)) { node_->SetInputIsKeyframing(input_, is_keyframing_, index_); } } private: Node* node_; QString input_; int index_; SplitValue standard_value_; bool is_keyframing_; QVector keyframes_; QObject memory_manager_; }; class ArrayResizeCommand : public UndoCommand { public: ArrayResizeCommand(Node* node, const QString& input, int size) : node_(node), input_(input), size_(size) {} virtual void redo() override { old_size_ = node_->InputArraySize(input_); if (old_size_ > size_) { // Decreasing in size, disconnect any extraneous edges for (int i=size_; iinput_connections().at(input); removed_connections_[input] = output; DisconnectEdge(output, input); } catch (std::out_of_range&) {} } } node_->ArrayResizeInternal(input_, size_); } virtual void undo() override { for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) { ConnectEdge(it->second, it->first); } removed_connections_.clear(); node_->ArrayResizeInternal(input_, old_size_); } virtual Project* GetRelevantProject() const override; private: Node* node_; QString input_; int size_; int old_size_; InputConnections removed_connections_; }; struct Input { NodeValue::Type type; InputFlags flags; SplitValue default_value; QHash properties; QString human_name; int array_size; }; NodeInputImmediate* CreateImmediate(const QString& input); NodeInputImmediate* GetImmediate(const QString& input, int element) const; int GetInternalInputIndex(const QString& input) const { return input_ids_.indexOf(input); } InputFlags GetInputFlags(const QString& input) const; Input* GetInternalInputData(const QString& input) { int i = GetInternalInputIndex(input); if (i == -1) { return nullptr; } else { return &input_data_[i]; } } const Input* GetInternalInputData(const QString& input) const { int i = GetInternalInputIndex(input); if (i == -1) { return nullptr; } else { return &input_data_.at(i); } } void ReportInvalidInput(const char* attempted_action, const QString &id) const; void ArrayResizeInternal(const QString& id, int size); static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap& created, const Node *node, MultiUndoCommand *command); /** * @brief Immediates aren't deleted, so the actual array size may be larger than ArraySize() */ int GetInternalInputArraySize(const QString& input); template static void FindInputNodeInternal(const Node* n, QVector& list); template static void FindOutputNodeInternal(const Node* n, QVector& list); QVector GetDependenciesInternal(bool traverse, bool exclusive_only) const; void HashInputElement(QCryptographicHash& hash, const QString &input, int element, const rational& time) const; void ParameterValueChanged(const QString &input, int element, const olive::TimeRange &range); void ParameterValueChanged(const NodeInput& input, const olive::TimeRange &range) { ParameterValueChanged(input.input(), input.element(), range); } void LoadImmediate(QXmlStreamReader *reader, const QString& input, int element, XMLNodeData& xml_node_data, const QAtomicInt* cancelled); void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const; void UpdateLastChangedTime(); /** * @brief Intelligently determine how what time range is affected by a keyframe */ TimeRange GetRangeAffectedByKeyframe(NodeKeyframe *key) const; /** * @brief Gets a time range between the previous and next keyframes of index */ TimeRange GetRangeAroundIndex(const QString& input, int index, int track, int element) const; void ClearElement(const QString &input, int index); QVector ignore_connections_; QVector ignore_when_hashing_; /** * @brief Internal variable for whether this Node can be deleted or not */ bool can_be_deleted_; /** * @brief UI position for NodeViews */ QPointF position_; /** * @brief Custom user label for node */ QString label_; /** * @brief -1 if the color should be based on the category, >=0 if the user has set a custom color */ int override_color_; /** * @brief Nodes that are linked with this one */ QVector links_; QVector input_ids_; QVector input_data_; QVector outputs_; QMap standard_immediates_; QMap > array_immediates_; InputConnections input_connections_; OutputConnections output_connections_; qint64 last_change_time_; QString tooltip_; Folder* folder_; private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time */ void InvalidateFromKeyframeTimeChange(); /** * @brief Slot when a keyframe's value changes to signal that the cache needs updating */ void InvalidateFromKeyframeValueChange(); /** * @brief Slot when a keyframe's type changes to signal that the cache needs updating */ void InvalidateFromKeyframeTypeChanged(); /** * @brief Slot when a keyframe's bezier in value changes to signal that the cache needs updating */ void InvalidateFromKeyframeBezierInChange(); /** * @brief Slot when a keyframe's bezier out value changes to signal that the cache needs updating */ void InvalidateFromKeyframeBezierOutChange(); }; template void Node::FindInputNodeInternal(const Node* n, QVector &list) { for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) { Node* edge = it->second.node(); T* cast_test = dynamic_cast(edge); if (cast_test) { list.append(cast_test); } FindInputNodeInternal(edge, list); } } template QVector Node::FindInputNodes() const { QVector list; FindInputNodeInternal(this, list); return list; } template T* Node::ValueToPtr(const QVariant &ptr) { return reinterpret_cast(ptr.value()); } template void Node::FindOutputNodeInternal(const Node* n, QVector& list) { foreach (const OutputConnection& output, n->output_connections_) { Node* connected = output.second.node(); T* cast_test = dynamic_cast(connected); if (cast_test) { list.append(cast_test); } FindOutputNodeInternal(connected); } } template QVector Node::FindOutputNode() { QVector list; FindOutputNodeInternal(this, list); return list; } using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: NodeSetPositionCommand(Node* node, const QPointF& position, bool move_dependencies_relatively) : node_(node), new_pos_(position), move_deps_(move_dependencies_relatively) { } virtual Project * GetRelevantProject() const override { return node_->project(); } virtual void redo() override { old_pos_ = node_->GetPosition(); node_->SetPosition(new_pos_, move_deps_); } virtual void undo() override { node_->SetPosition(old_pos_, move_deps_); } private: Node* node_; QPointF new_pos_; QPointF old_pos_; bool move_deps_; }; class NodeSetPositionAndShiftSurroundingsCommand : public UndoCommand { public: NodeSetPositionAndShiftSurroundingsCommand(Node* node, const QPointF& pos, bool move_dependencies_relatively) : node_(node), position_(pos), move_dependencies_(move_dependencies_relatively) {} virtual ~NodeSetPositionAndShiftSurroundingsCommand() override { qDeleteAll(commands_); } virtual Project * GetRelevantProject() const override { return node_->project(); } virtual void redo() override; virtual void undo() override { for (int i=commands_.size()-1; i>=0; i--) { commands_.at(i)->undo(); } } private: Node* node_; QPointF position_; bool move_dependencies_; QVector commands_; }; class NodeSetPositionAsChildCommand : public UndoCommand { public: NodeSetPositionAsChildCommand(Node* node, Node* parent, int this_index, int child_count, bool shift_surroundings) : node_(node), parent_(parent), this_index_(this_index), child_count_(child_count), shift_surroundings_(shift_surroundings), sub_command_(nullptr) { } virtual ~NodeSetPositionAsChildCommand() override { delete sub_command_; } virtual Project * GetRelevantProject() const override { return node_->project(); } virtual void redo() override { if (!sub_command_) { // Calculate position of node QPointF pos = parent_->GetPosition(); // This is a dependency, so we'll place it one X before pos.setX(pos.x() - 1); // The Y will be calculated using the index and child count pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5); if (shift_surroundings_) { sub_command_ = new NodeSetPositionAndShiftSurroundingsCommand(node_, pos, true); } else { sub_command_ = new NodeSetPositionCommand(node_, pos, true); } } sub_command_->redo(); } virtual void undo() override { sub_command_->undo(); } private: Node* node_; Node* parent_; int this_index_; int child_count_; bool shift_surroundings_; UndoCommand* sub_command_; }; class NodeSetPositionToOffsetOfAnotherNodeCommand : public UndoCommand { public: NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, const QPointF& offset) : node_(node), other_node_(other_node), offset_(offset) {} virtual Project * GetRelevantProject() const override { return node_->project(); } virtual void redo() override { node_->SetPosition(other_node_->GetPosition() + offset_); } virtual void undo() override { node_->SetPosition(other_node_->GetPosition() - offset_); } private: Node* node_; Node* other_node_; QPointF offset_; }; } #endif // NODE_H