Files
oak-editor/app/node/node.h
T
Mike-Solar bec52b46b3 nodes: fix serialized ID typos with backward compatibility
- ChromaKey tolerance inputs are now spelled "upper/lower_tolerance_in";
  old projects with the misspelled "tolerence" IDs keep loading (values
  and connections) via a new Node::GetInputIDForLegacyID() hook applied
  in LoadInput and connection deserialization
- flip/ripple/swirl/tile/wave node IDs moved to the consistent
  org.olivevideoeditor.Olive.* domain; NodeFactory::CreateFromID maps
  the old org.oliveeditor.* IDs so old projects still resolve
- Regression tests: legacy ChromaKey IDs (values + connections) and
  legacy factory IDs
2026-07-17 08:38:25 +08:00

1571 lines
41 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef NODE_H
#define NODE_H
#include "ofxhImageEffectAPI.h"
#include <map>
#include <QMutex>
#include <QObject>
#include <QPainter>
#include <QPointF>
#include <QXmlStreamWriter>
#include "codec/frame.h"
#include "common/xmlutils.h"
#include "node/gizmo/draggable.h"
#include "node/globals.h"
#include "node/keyframe.h"
#include "node/inputimmediate.h"
#include "node/param.h"
#include "render/audioplaybackcache.h"
#include "render/audiowaveformcache.h"
#include "render/framehashcache.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
{
#define NODE_DEFAULT_FUNCTIONS(x) \
NODE_DEFAULT_DESTRUCTOR(x) \
NODE_COPY_FUNCTION(x)
#define NODE_DEFAULT_DESTRUCTOR(x) \
virtual ~x() override \
{ \
DisconnectAll(); \
}
#define NODE_COPY_FUNCTION(x) \
virtual Node *copy() const override \
{ \
return new x(); \
}
class Folder;
class Project;
struct SerializedData;
/**
* @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,
kCategoryOutput,
kCategoryGenerator,
kCategoryMath,
kCategoryKeying,
kCategoryFilter,
kCategoryColor,
kCategoryTime,
kCategoryTimeline,
kCategoryTransition,
kCategoryDistort,
kCategoryProject,
kCategoryOpenFX,
kCategoryCount
};
enum Flag {
kNone = 0,
kDontShowInParamView = 0x1,
kVideoEffect = 0x2,
kAudioEffect = 0x4,
kDontShowInCreateMenu = 0x8,
kIsItem = 0x10
};
struct ContextPair {
Node *node;
Node *context;
};
Node();
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
*/
Project *parent() const;
Project *project() const;
const uint64_t &GetFlags() const
{
return flags_;
}
/**
* @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<CategoryID> Category() const = 0;
/**
* @brief Return a sub-category string for secondary grouping
* within the primary category (e.g. "Filter" under "OpenFX").
*/
virtual QString SubCategory() const
{
return QString();
}
/**
* @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;
Folder *folder() const
{
return folder_;
}
bool IsItem() const
{
return flags_ & kIsItem;
}
/**
* @brief Function called to retranslate parameter names (should be overridden in derivatives)
*/
virtual void Retranslate();
enum DataType {
ICON,
DURATION,
CREATED_TIME,
MODIFIED_TIME,
FREQUENCY_RATE,
TOOLTIP
};
virtual QVariant data(const DataType &d) const;
const QVector<QString> &inputs() const
{
return input_ids_;
}
virtual QVector<QString> IgnoreInputsForRendering() const
{
return QVector<QString>();
}
class ActiveElements {
public:
enum Mode { kAllElements, kSpecified, kNoElements };
ActiveElements(Mode m = kAllElements)
{
mode_ = m;
}
Mode mode() const
{
return mode_;
}
std::list<int> elements() const
{
return elements_;
}
void add(int e)
{
elements_.push_back(e);
mode_ = kSpecified;
}
private:
Mode mode_;
std::list<int> elements_;
};
virtual ActiveElements GetActiveElementsAtTime(const QString &input,
const TimeRange &r) const
{
return ActiveElements::kAllElements;
}
bool HasInputWithID(const QString &id) const
{
return input_ids_.contains(id);
}
bool HasParamWithID(const QString &id) const
{
return HasInputWithID(id);
}
FrameHashCache *video_frame_cache() const
{
return video_cache_;
}
ThumbnailCache *thumbnail_cache() const
{
return thumbnail_cache_;
}
AudioPlaybackCache *audio_playback_cache() const
{
return audio_cache_;
}
AudioWaveformCache *waveform_cache() const
{
return waveform_cache_;
}
virtual TimeRange GetVideoCacheRange() const
{
return TimeRange();
}
virtual TimeRange GetAudioCacheRange() const
{
return TimeRange();
}
struct Position {
Position(const QPointF &p = QPointF(0, 0), bool e = false)
{
position = p;
expanded = e;
}
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
QPointF position;
bool expanded;
inline Position &operator+=(const Position &p)
{
position += p.position;
return *this;
}
inline Position &operator-=(const Position &p)
{
position -= p.position;
return *this;
}
friend inline const Position operator+(Position a, const Position &b)
{
a += b;
return a;
}
friend inline const Position operator-(Position a, const Position &b)
{
a -= b;
return a;
}
};
using PositionMap = QHash<Node *, Position>;
const PositionMap &GetContextPositions() const
{
return context_positions_;
}
bool IsNodeExpandedInContext(Node *node) const
{
return context_positions_.value(node).expanded;
}
bool ContextContainsNode(Node *node) const
{
return context_positions_.contains(node);
}
Position GetNodePositionDataInContext(Node *node)
{
return context_positions_.value(node);
}
QPointF GetNodePositionInContext(Node *node)
{
return GetNodePositionDataInContext(node).position;
}
bool SetNodePositionInContext(Node *node, const QPointF &pos);
bool SetNodePositionInContext(Node *node, const Position &pos);
void SetNodeExpandedInContext(Node *node, bool e)
{
context_positions_[node].expanded = e;
}
bool RemoveNodeFromContext(Node *node);
/**
* @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;
int GetOverrideColor() const
{
return override_color_;
}
/**
* @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(Node *output, const NodeInput &input);
static void DisconnectEdge(Node *output, const NodeInput &input);
void CopyCacheUuidsFrom(Node *n);
bool AreCachesEnabled() const
{
return caches_enabled_;
}
void SetCachesEnabled(bool e)
{
caches_enabled_ = e;
}
virtual QString GetInputName(const QString &id) const;
void SetInputName(const QString &id, const QString &name);
bool IsInputHidden(const QString &input) 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());
}
virtual bool IsInputConnectedForRender(const QString &input,
int element = -1) const
{
return IsInputConnected(input, element);
}
bool IsInputConnectedForRender(const NodeInput &input) const
{
return IsInputConnectedForRender(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());
}
Node *GetConnectedOutput(const QString &input, int element = -1) const;
Node *GetConnectedOutput(const NodeInput &input) const
{
return GetConnectedOutput(input.input(), input.element());
}
virtual Node *GetConnectedRenderOutput(const QString &input,
int element = -1) const
{
return GetConnectedOutput(input, element);
}
Node *GetConnectedRenderOutput(const NodeInput &input) const
{
return GetConnectedRenderOutput(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<QString, QVariant> 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;
void SetDefaultValue(const QString &input, const QVariant &val);
void SetSplitDefaultValue(const QString &input, const SplitValue &val);
void SetSplitDefaultValueOnTrack(const QString &input, const QVariant &val,
int track);
const QVector<NodeKeyframeTrack> &GetKeyframeTracks(const QString &input,
int element) const;
const QVector<NodeKeyframeTrack> &
GetKeyframeTracks(const NodeInput &input) const
{
return GetKeyframeTracks(input.input(), input.element());
}
QVector<NodeKeyframe *> GetKeyframesAtTime(const QString &input,
const rational &time,
int element = -1) const;
QVector<NodeKeyframe *> 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);
void InputArrayResize(const QString &id, int size);
void InputArrayRemove(const QString &id, int index);
void InputArrayAppend(const QString &id)
{
InputArrayResize(id, InputArraySize(id) + 1);
}
void InputArrayPrepend(const QString &id)
{
InputArrayInsert(id, 0);
}
void InputArrayRemoveLast(const QString &id)
{
InputArrayResize(id, InputArraySize(id) - 1);
}
int InputArraySize(const QString &id) const;
NodeInputImmediate *GetImmediate(const QString &input, int element) const;
NodeInput GetEffectInput()
{
return effect_input_.isEmpty() ? NodeInput() :
NodeInput(this, effect_input_);
}
const QString &GetEffectInputID() const
{
return effect_input_;
}
class ValueHint {
public:
explicit ValueHint(
const QVector<NodeValue::Type> &types = QVector<NodeValue::Type>(),
int index = -1, const QString &tag = QString())
: type_(types)
, index_(index)
, tag_(tag)
{
}
explicit ValueHint(const QVector<NodeValue::Type> &types,
const QString &tag)
: type_(types)
, index_(-1)
, tag_(tag)
{
}
explicit ValueHint(int index)
: index_(index)
{
}
explicit ValueHint(const QString &tag)
: index_(-1)
, tag_(tag)
{
}
const QVector<NodeValue::Type> &types() const
{
return type_;
}
const int &index() const
{
return index_;
}
const QString &tag() const
{
return tag_;
}
void set_type(const QVector<NodeValue::Type> &type)
{
type_ = type;
}
void set_index(const int &index)
{
index_ = index;
}
void set_tag(const QString &tag)
{
tag_ = tag;
}
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
private:
QVector<NodeValue::Type> type_;
int index_;
QString tag_;
};
const QMap<InputElementPair, ValueHint> &GetValueHints() const
{
return value_hints_;
}
virtual ValueHint GetValueHintForInput(const QString &input,
int element = -1) const
{
return value_hints_.value({ input, element });
}
void SetValueHintForInput(const QString &input, const ValueHint &hint,
int element = -1);
const NodeKeyframeTrack &GetTrackFromKeyframe(NodeKeyframe *key) const;
using InputConnections = std::map<NodeInput, Node *>;
/**
* @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<Node *, NodeInput>;
using OutputConnections = std::vector<OutputConnection>;
/**
* @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<Node *> 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<Node *> GetExclusiveDependencies() const;
/**
* @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one)
*/
QVector<Node *> GetImmediateDependencies() const;
struct ShaderRequest {
ShaderRequest(const QString &shader_id)
{
id = shader_id;
}
ShaderRequest(const QString &shader_id, const QString &shader_stub)
{
id = shader_id;
stub = shader_stub;
}
QString id;
QString stub;
};
/**
* @brief Generate hardware accelerated code for this Node
*/
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const;
/**
* @brief If Value() pushes a ShaderJob, this is the function that will process them.
*/
virtual void ProcessSamples(const NodeValueRow &values,
const SampleBuffer &input, SampleBuffer &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 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 Find inputs that `output` outputs to in order to arrive at this node
*
* Traverse this node's inputs recursively looking for `output`, and return a list of
* edges that `output` uses to get to `this` node.
*/
QVector<NodeInput> FindWaysNodeArrivesHere(const Node *output) 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);
enum TransformTimeDirection {
kTransformTowardsInput,
kTransformTowardsOutput
};
/**
* @brief Transforms time from this node through the connections it takes to get to the specified node
*/
TimeRange TransformTimeTo(TimeRange time, Node *target,
TransformTimeDirection dir, int path_index);
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template <class T> QVector<T *> FindInputNodes(int maximum = 0) const;
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template <class T>
static QVector<T *> FindInputNodesConnectedToInput(const NodeInput &input,
int maximum = 0);
using InvalidateCacheOptions = QHash<QString, QVariant>;
/**
* @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 = -1,
InvalidateCacheOptions options = InvalidateCacheOptions());
void InvalidateCache(
const TimeRange &range, const NodeInput &from,
const InvalidateCacheOptions &options = InvalidateCacheOptions())
{
InvalidateCache(range, from.input(), from.element(), options);
}
/**
* @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,
bool clamp) 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,
MultiUndoCommand *command = nullptr);
static void CopyInput(const Node *src, Node *dst, const QString &input,
bool include_connections, bool traverse_arrays,
MultiUndoCommand *command);
static void CopyValuesOfElement(const Node *src, Node *dst,
const QString &input, int src_element,
int dst_element,
MultiUndoCommand *command = nullptr);
static void CopyValuesOfElement(const Node *src, Node *dst,
const QString &input, int element,
MultiUndoCommand *command = nullptr)
{
return CopyValuesOfElement(src, dst, input, element, element, command);
}
/**
* @brief Clones a set of nodes and connects the new ones the way the old ones were
*/
static QVector<Node *> CopyDependencyGraph(const QVector<Node *> &nodes,
MultiUndoCommand *command);
static void CopyDependencyGraph(const QVector<Node *> &src,
const QVector<Node *> &dst,
MultiUndoCommand *command);
static Node *
CopyNodeAndDependencyGraphMinusItems(Node *node, MultiUndoCommand *command);
static Node *CopyNodeInGraph(Node *node, MultiUndoCommand *command);
/**
* @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 void Value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const;
bool HasGizmos() const
{
return !gizmos_.isEmpty();
}
const QVector<NodeGizmo *> &GetGizmos() const
{
return gizmos_;
}
virtual QTransform GizmoTransformation(const NodeValueRow &row,
const NodeGlobals &globals) const
{
return QTransform();
}
virtual void UpdateGizmoPositions(const NodeValueRow &row,
const NodeGlobals &globals)
{
}
const QString &GetLabel() const;
void SetLabel(const QString &s);
QString GetLabelAndName() const;
QString GetLabelOrName() const;
void InvalidateAll(const QString &input, int element = -1);
bool HasLinks() const
{
return !links_.isEmpty();
}
const QVector<Node *> &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);
bool Load(QXmlStreamReader *reader, SerializedData *data);
void Save(QXmlStreamWriter *writer) const;
virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data);
virtual void SaveCustom(QXmlStreamWriter *writer) const
{
}
virtual void PostLoadEvent(SerializedData *data);
bool LoadInput(QXmlStreamReader *reader, SerializedData *data);
void SaveInput(QXmlStreamWriter *writer, const QString &id) const;
/**
* @brief Maps an input ID read from an old project file to its current ID
*
* Nodes whose input IDs have been renamed override this so old projects
* keep loading. The default implementation returns the ID unchanged.
*/
virtual QString GetInputIDForLegacyID(const QString &id) const;
bool LoadImmediate(QXmlStreamReader *reader, const QString &input,
int element, SerializedData *data);
void SaveImmediate(QXmlStreamWriter *writer, const QString &input,
int element) const;
void SetFolder(Folder *folder)
{
folder_ = folder;
}
InputFlags GetInputFlags(const QString &input) const;
void SetInputFlag(const QString &input, InputFlag f, bool on = true);
virtual void LoadFinishedEvent()
{
}
virtual void ConnectedToPreviewEvent()
{
}
static void SetValueAtTime(const NodeInput &input, const rational &time,
const QVariant &value, int track,
MultiUndoCommand *command,
bool insert_on_all_tracks_if_no_key);
/**
* @brief Find path starting at `from` that outputs to arrive at `to`
*/
static std::list<NodeInput> FindPath(Node *from, Node *to, int path_index);
void ArrayResizeInternal(const QString &id, int size);
virtual void AddedToGraphEvent(Project *p)
{
}
virtual void RemovedFromGraphEvent(Project *p)
{
}
static QString GetConnectCommandString(Node *output,
const NodeInput &input);
static QString GetDisconnectCommandString(Node *output,
const NodeInput &input);
static const QString kEnabledInput;
OFX::Host::ImageEffect::Instance *getPluginInstance() const
{
return plugin_instance_;
}
OFX::Host::ImageEffect::ImageEffectPlugin *getPlugin() const
{
return plugin_instance_ ? plugin_instance_->getPlugin() : nullptr;
}
protected:
// If set, this node owns a plugin instance.
OFX::Host::ImageEffect::Instance *plugin_instance_ = nullptr;
void setPluginInstance(OFX::Host::ImageEffect::Instance *instance)
{
plugin_instance_ = instance;
}
void InsertInput(const QString &id, NodeValue::Type type,
const QVariant &default_value, InputFlags flags,
int index);
void PrependInput(const QString &id, NodeValue::Type type,
const QVariant &default_value,
InputFlags flags = InputFlags(kInputFlagNormal))
{
InsertInput(id, type, default_value, flags, 0);
}
void PrependInput(const QString &id, NodeValue::Type type,
InputFlags flags = InputFlags(kInputFlagNormal))
{
PrependInput(id, type, QVariant(), flags);
}
void AddInput(const QString &id, NodeValue::Type type,
const QVariant &default_value,
InputFlags flags = InputFlags(kInputFlagNormal))
{
InsertInput(id, type, default_value, flags, input_ids_.size());
}
void AddInput(const QString &id, NodeValue::Type type,
InputFlags flags = InputFlags(kInputFlagNormal))
{
AddInput(id, type, QVariant(), flags);
}
void RemoveInput(const QString &id);
void SetComboBoxStrings(const QString &id, const QStringList &strings)
{
SetInputProperty(id, QStringLiteral("combo_str"), strings);
}
void SendInvalidateCache(const TimeRange &range,
const InvalidateCacheOptions &options);
enum GizmoScaleHandles {
kGizmoScaleTopLeft,
kGizmoScaleTopCenter,
kGizmoScaleTopRight,
kGizmoScaleBottomLeft,
kGizmoScaleBottomCenter,
kGizmoScaleBottomRight,
kGizmoScaleCenterLeft,
kGizmoScaleCenterRight,
kGizmoScaleCount,
};
virtual void LinkChangeEvent()
{
}
virtual void InputValueChangedEvent(const QString &input, int element);
virtual void InputConnectedEvent(const QString &input, int element,
Node *output);
virtual void InputDisconnectedEvent(const QString &input, int element,
Node *output);
virtual void OutputConnectedEvent(const NodeInput &input);
virtual void OutputDisconnectedEvent(const NodeInput &input);
virtual void childEvent(QChildEvent *event) override;
void SetEffectInput(const QString &input)
{
effect_input_ = input;
}
void SetFlag(Flag f, bool on = true)
{
if (on) {
flags_ |= f;
} else {
flags_ &= ~f;
}
}
template <typename T>
T *AddDraggableGizmo(const QVector<NodeKeyframeTrackReference> &inputs =
QVector<NodeKeyframeTrackReference>(),
DraggableGizmo::DragValueBehavior behavior =
DraggableGizmo::kDeltaFromStart)
{
T *gizmo = new T(this);
gizmo->SetDragValueBehavior(behavior);
foreach (const NodeKeyframeTrackReference &input, inputs) {
gizmo->AddInput(input);
}
connect(gizmo, &DraggableGizmo::HandleStart, this,
&Node::GizmoDragStart);
connect(gizmo, &DraggableGizmo::HandleMovement, this,
&Node::GizmoDragMove);
return gizmo;
}
template <typename T>
T *AddDraggableGizmo(const QStringList &inputs,
DraggableGizmo::DragValueBehavior behavior =
DraggableGizmo::kDeltaFromStart)
{
QVector<NodeKeyframeTrackReference> refs(inputs.size());
for (int i = 0; i < refs.size(); i++) {
refs[i] = NodeInput(this, inputs[i]);
}
return AddDraggableGizmo<T>(refs, behavior);
}
protected slots:
virtual void GizmoDragStart(const olive::NodeValueRow &row, double x,
double y, const olive::core::rational &time)
{
}
virtual void GizmoDragMove(double x, double y,
const Qt::KeyboardModifiers &modifiers)
{
}
signals:
/**
* @brief Signal emitted when SetLabel() is called
*/
void LabelChanged(const QString &s);
void ColorChanged();
void ValueChanged(const NodeInput &input, const TimeRange &range);
void InputConnected(Node *output, const NodeInput &input);
void InputDisconnected(Node *output, const NodeInput &input);
void OutputConnected(Node *output, const NodeInput &input);
void OutputDisconnected(Node *output, const NodeInput &input);
void InputValueHintChanged(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(NodeKeyframe *key);
void MessageCountChanged();
void KeyframeTypeChanged(NodeKeyframe *key);
void KeyframeValueChanged(NodeKeyframe *key);
void KeyframeEnableChanged(const NodeInput &input, bool enabled);
void InputAdded(const QString &id);
void InputRemoved(const QString &id);
void InputNameChanged(const QString &id, const QString &name);
void InputDataTypeChanged(const QString &id, NodeValue::Type type);
void AddedToGraph(Project *graph);
void RemovedFromGraph(Project *graph);
void NodeAddedToContext(Node *node);
void NodePositionInContextChanged(Node *node, const QPointF &pos);
void NodeRemovedFromContext(Node *node);
void InputFlagsChanged(const QString &input, const InputFlags &flags);
private:
struct Input {
NodeValue::Type type;
InputFlags flags;
SplitValue default_value;
QHash<QString, QVariant> properties;
QString human_name;
int array_size;
};
NodeInputImmediate *CreateImmediate(const QString &input);
int GetInternalInputIndex(const QString &input) const
{
return input_ids_.indexOf(input);
}
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,
int element) const;
static Node *CopyNodeAndDependencyGraphMinusItemsInternal(
QMap<Node *, Node *> &created, Node *node, MultiUndoCommand *command);
/**
* @brief Immediates aren't deleted, so the actual array size may be larger than ArraySize()
*/
int GetInternalInputArraySize(const QString &input);
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template <class T>
static void FindInputNodesConnectedToInputInternal(const NodeInput &input,
QVector<T *> &list,
int maximum);
template <class T>
static void FindInputNodeInternal(const Node *n, QVector<T *> &list,
int maximum);
QVector<Node *> GetDependenciesInternal(bool traverse,
bool exclusive_only) const;
void ParameterValueChanged(const QString &input, int element,
const olive::core::TimeRange &range);
void ParameterValueChanged(const NodeInput &input,
const olive::core::TimeRange &range)
{
ParameterValueChanged(input.input(), input.element(), range);
}
/**
* @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);
/**
* @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<Node *> links_;
QVector<QString> input_ids_;
QVector<Input> input_data_;
QMap<QString, NodeInputImmediate *> standard_immediates_;
QMap<QString, QVector<NodeInputImmediate *>> array_immediates_;
InputConnections input_connections_;
OutputConnections output_connections_;
Folder *folder_;
QMap<InputElementPair, ValueHint> value_hints_;
PositionMap context_positions_;
uint64_t flags_;
QVector<NodeGizmo *> gizmos_;
QString effect_input_;
FrameHashCache *video_cache_;
ThumbnailCache *thumbnail_cache_;
AudioPlaybackCache *audio_cache_;
AudioWaveformCache *waveform_cache_;
bool caches_enabled_;
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 <class T>
void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input,
QVector<T *> &list,
int maximum)
{
Node *edge = input.GetConnectedOutput();
if (!edge) {
return;
}
T *cast_test = dynamic_cast<T *>(edge);
if (cast_test) {
list.append(cast_test);
if (maximum != 0 && list.size() == maximum) {
return;
}
}
FindInputNodeInternal<T>(edge, list, maximum);
}
template <class T>
QVector<T *> Node::FindInputNodesConnectedToInput(const NodeInput &input,
int maximum)
{
QVector<T *> list;
FindInputNodesConnectedToInputInternal<T>(input, list, maximum);
return list;
}
template <class T>
void Node::FindInputNodeInternal(const Node *n, QVector<T *> &list, int maximum)
{
for (auto it = n->input_connections_.cbegin();
it != n->input_connections_.cend(); it++) {
FindInputNodesConnectedToInputInternal(it->first, list, maximum);
}
}
template <class T> QVector<T *> Node::FindInputNodes(int maximum) const
{
QVector<T *> list;
FindInputNodeInternal<T>(this, list, maximum);
return list;
}
}
Q_DECLARE_METATYPE(olive::Node::ValueHint)
#endif // NODE_H