diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt
index ff82a6b2c..01bbfd5ec 100644
--- a/app/dialog/CMakeLists.txt
+++ b/app/dialog/CMakeLists.txt
@@ -23,6 +23,7 @@ add_subdirectory(diskcache)
add_subdirectory(export)
add_subdirectory(footagerelink)
add_subdirectory(keyframeproperties)
+add_subdirectory(nodegroup)
add_subdirectory(nodeproperties)
add_subdirectory(preferences)
add_subdirectory(progress)
diff --git a/app/dialog/nodegroup/CMakeLists.txt b/app/dialog/nodegroup/CMakeLists.txt
new file mode 100644
index 000000000..0f48edfc9
--- /dev/null
+++ b/app/dialog/nodegroup/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ dialog/nodegroup/nodegroupdialog.cpp
+ dialog/nodegroup/nodegroupdialog.h
+ PARENT_SCOPE
+)
diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp
new file mode 100644
index 000000000..2d07a4338
--- /dev/null
+++ b/app/dialog/nodegroup/nodegroupdialog.cpp
@@ -0,0 +1,99 @@
+/***
+
+ 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 "nodegroupdialog.h"
+
+#include
+#include
+#include
+#include
+
+#include "widget/nodeparamview/nodeparamview.h"
+#include "widget/nodeview/nodeview.h"
+
+namespace olive {
+
+#define super QDialog
+
+NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) :
+ super(parent),
+ group_(group),
+ parent_undo_(nullptr)
+{
+ QGridLayout *layout = new QGridLayout(this);
+
+ int row = 0;
+
+ layout->addWidget(new QLabel(tr("Name:")), row, 0);
+
+ name_edit_ = new QLineEdit();
+ layout->addWidget(name_edit_, row, 1);
+
+ row++;
+
+ QSplitter *splitter = new QSplitter(Qt::Horizontal);
+ layout->addWidget(splitter, row, 0, 1, 2);
+
+ NodeParamView *param_view = new NodeParamView(false);
+ param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected);
+ splitter->addWidget(param_view);
+
+ NodeView *node_view = new NodeView();
+ node_view->SetContexts({group});
+ QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection);
+ splitter->addWidget(node_view);
+
+ for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) {
+ param_view->SetInputChecked(it.value(), true);
+ }
+
+ connect(node_view, &NodeView::NodesSelected, param_view, &NodeParamView::SelectNodes);
+ connect(node_view, &NodeView::NodesDeselected, param_view, &NodeParamView::DeselectNodes);
+ node_view->SelectAll();
+
+ row++;
+
+ QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ btns->setCenterButtons(true);
+ connect(btns, &QDialogButtonBox::accepted, this, &NodeGroupDialog::accept);
+ connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject);
+ layout->addWidget(btns, row, 0, 1, 2);
+
+ setWindowTitle(tr("Group Editor"));
+}
+
+void NodeGroupDialog::accept()
+{
+ MultiUndoCommand *command = new MultiUndoCommand();
+
+ if (name_edit_->text() != group_->GetCustomName()) {
+ command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text()));
+ }
+
+ if (parent_undo_) {
+ parent_undo_->add_child(command);
+ } else {
+ Core::instance()->undo_stack()->push(command);
+ }
+
+ super::accept();
+}
+
+}
diff --git a/app/node/group.h b/app/dialog/nodegroup/nodegroupdialog.h
similarity index 61%
rename from app/node/group.h
rename to app/dialog/nodegroup/nodegroupdialog.h
index 2f15bbb71..741f0ac55 100644
--- a/app/node/group.h
+++ b/app/dialog/nodegroup/nodegroupdialog.h
@@ -18,26 +18,41 @@
***/
-#ifndef NODEGROUP_H
-#define NODEGROUP_H
+#ifndef NODEGROUPDIALOG_H
+#define NODEGROUPDIALOG_H
-#include "node.h"
+#include
+#include
+
+#include "node/group/group.h"
namespace olive {
-class NodeGroup : public Node
+class NodeGroupDialog : public QDialog
{
Q_OBJECT
public:
- NodeGroup();
+ explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr);
- void SetNodes(Node *nodes);
+ void SetParentUndoCommand(MultiUndoCommand *c)
+ {
+ parent_undo_ = c;
+ }
+
+public slots:
+ virtual void accept() override;
+
+signals:
private:
- QVector nodes_;
+ NodeGroup *group_;
+
+ QLineEdit *name_edit_;
+
+ MultiUndoCommand *parent_undo_;
};
}
-#endif // NODEGROUP_H
+#endif // NODEGROUPDIALOG_H
diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.cpp b/app/dialog/nodeproperties/nodepropertiesdialog.cpp
index 123b6c69b..fca1935ba 100644
--- a/app/dialog/nodeproperties/nodepropertiesdialog.cpp
+++ b/app/dialog/nodeproperties/nodepropertiesdialog.cpp
@@ -46,7 +46,7 @@ NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase,
label_edit_->setText(node->GetLabel());
label_layout->addWidget(label_edit_);
- NodeParamViewItem *item = new NodeParamViewItem(node);
+ NodeParamViewItem *item = new NodeParamViewItem(node, kNoCheckBoxes);
item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
item->SetTimebase(timebase);
item->setTitleBarWidget(new QWidget());
diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt
index 6cd34ca5b..508bf00bf 100644
--- a/app/node/CMakeLists.txt
+++ b/app/node/CMakeLists.txt
@@ -20,6 +20,7 @@ add_subdirectory(color)
add_subdirectory(distort)
add_subdirectory(filter)
add_subdirectory(generator)
+add_subdirectory(group)
add_subdirectory(input)
add_subdirectory(math)
add_subdirectory(output)
@@ -34,8 +35,6 @@ set(OLIVE_SOURCES
node/globals.h
node/graph.cpp
node/graph.h
- node/group.cpp
- node/group.h
node/hashtraverser.cpp
node/hashtraverser.h
node/inputdragger.cpp
diff --git a/app/node/group.cpp b/app/node/group.cpp
deleted file mode 100644
index 7b7ab0c87..000000000
--- a/app/node/group.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/***
-
- 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 "group.h"
-
-namespace olive {
-
-NodeGroup::NodeGroup()
-{
-
-}
-
-}
diff --git a/app/node/group/CMakeLists.txt b/app/node/group/CMakeLists.txt
new file mode 100644
index 000000000..3c0bce4d6
--- /dev/null
+++ b/app/node/group/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/group/group.cpp
+ node/group/group.h
+ PARENT_SCOPE
+)
diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp
new file mode 100644
index 000000000..fbe785bd9
--- /dev/null
+++ b/app/node/group/group.cpp
@@ -0,0 +1,177 @@
+/***
+
+ 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 "group.h"
+
+#include "node/graph.h"
+
+namespace olive {
+
+NodeGroup::NodeGroup() :
+ output_passthrough_(nullptr)
+{
+ graph_ = new NodeGraph();
+ graph_->setParent(this);
+}
+
+QString NodeGroup::Name() const
+{
+ if (custom_name_.isEmpty()) {
+ return tr("Group");
+ } else {
+ return custom_name_;
+ }
+}
+
+QString NodeGroup::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.group");
+}
+
+QVector NodeGroup::Category() const
+{
+ return {kCategoryGeneral};
+}
+
+QString NodeGroup::Description() const
+{
+ return tr("A group of nodes that is represented as a single node.");
+}
+
+void NodeGroup::Retranslate()
+{
+ foreach (Node *n, graph_->nodes()) {
+ n->Retranslate();
+ }
+}
+
+void NodeGroup::AddNode(Node *node)
+{
+ node->setParent(graph_);
+}
+
+void NodeGroup::RemoveNode(Node *node, QObject *new_parent)
+{
+ if (node->parent() == graph_) {
+ node->setParent(new_parent);
+ }
+}
+
+void NodeGroup::AddInputPassthrough(const NodeInput &input)
+{
+ Q_ASSERT(graph_->nodes().contains(input.node()));
+
+ for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
+ if (it.value() == input) {
+ // Already passing this input through
+ return;
+ }
+ }
+
+ // Add input
+ QString id = GetGroupInputIDFromInput(input);
+
+ AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags());
+
+ input_passthroughs_.insert(id, input);
+}
+
+void NodeGroup::RemoveInputPassthrough(const NodeInput &input)
+{
+ for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
+ if (it.value() == input) {
+ RemoveInput(it.key());
+ input_passthroughs_.erase(it);
+ break;
+ }
+ }
+}
+
+void NodeGroup::SetOutputPassthrough(Node *node)
+{
+ Q_ASSERT(graph_->nodes().contains(node));
+
+ output_passthrough_ = node;
+}
+
+QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input)
+{
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+
+ hash.addData(input.node()->GetUUID().toByteArray());
+
+ hash.addData(input.input().toUtf8());
+
+ hash.addData((const char*) &input.element(), sizeof(input.element()));
+
+ return QString::fromLatin1(hash.result().toHex());
+}
+
+bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const
+{
+ for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
+ if (it.value() == input) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void NodeAddToGroupCommand::redo()
+{
+ previous_parent_ = node_->parent();
+ group_->AddNode(node_);
+}
+
+void NodeAddToGroupCommand::undo()
+{
+ group_->RemoveNode(node_, previous_parent_);
+}
+
+void NodeGroupSetCustomNameCommand::redo()
+{
+ old_name_ = group_->GetCustomName();
+ group_->SetCustomName(new_name_);
+}
+
+void NodeGroupSetCustomNameCommand::undo()
+{
+ group_->SetCustomName(old_name_);
+}
+
+void NodeGroupAddInputPassthrough::redo()
+{
+ if (!group_->ContainsInputPassthrough(input_)) {
+ group_->AddInputPassthrough(input_);
+ actually_added_ = true;
+ } else {
+ actually_added_ = false;
+ }
+}
+
+void NodeGroupAddInputPassthrough::undo()
+{
+ if (actually_added_) {
+ group_->RemoveInputPassthrough(input_);
+ }
+}
+
+}
diff --git a/app/node/group/group.h b/app/node/group/group.h
new file mode 100644
index 000000000..a0aa9cee3
--- /dev/null
+++ b/app/node/group/group.h
@@ -0,0 +1,176 @@
+/***
+
+ 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 .
+
+***/
+
+#ifndef NODEGROUP_H
+#define NODEGROUP_H
+
+#include "node/node.h"
+
+namespace olive {
+
+class NodeGroup : public Node
+{
+ Q_OBJECT
+public:
+ NodeGroup();
+
+ NODE_DEFAULT_DESTRUCTOR(NodeGroup)
+ NODE_COPY_FUNCTION(NodeGroup)
+
+ virtual QString Name() const override;
+ virtual QString id() const override;
+ virtual QVector Category() const override;
+ virtual QString Description() const override;
+
+ virtual void Retranslate() override;
+
+ void AddNode(Node *node);
+
+ void RemoveNode(Node *node, QObject *new_parent = nullptr);
+
+ void AddInputPassthrough(const NodeInput &input);
+
+ void RemoveInputPassthrough(const NodeInput &input);
+
+ void SetOutputPassthrough(Node *node);
+
+ const QString &GetCustomName() const
+ {
+ return custom_name_;
+ }
+
+ void SetCustomName(const QString &name)
+ {
+ custom_name_ = name;
+
+ // NOTE: Not technically the right signal, but should achieve the right goal
+ emit LabelChanged(custom_name_);
+ }
+
+ void ClearCustomName()
+ {
+ custom_name_.clear();
+ }
+
+ static QString GetGroupInputIDFromInput(const NodeInput &input);
+
+ const QHash &GetInputPassthroughs() const
+ {
+ return input_passthroughs_;
+ }
+
+ bool ContainsInputPassthrough(const NodeInput &input) const;
+
+private:
+ NodeGraph *graph_;
+
+ QHash input_passthroughs_;
+
+ Node *output_passthrough_;
+
+ QString custom_name_;
+
+};
+
+class NodeAddToGroupCommand : public UndoCommand
+{
+public:
+ NodeAddToGroupCommand(Node *node, NodeGroup *group) :
+ node_(node),
+ group_(group)
+ {}
+
+ virtual Project * GetRelevantProject() const override
+ {
+ return node_->project();
+ }
+
+protected:
+ virtual void redo() override;
+
+ virtual void undo() override;
+
+private:
+ Node *node_;
+
+ NodeGroup *group_;
+
+ QObject *previous_parent_;
+
+};
+
+class NodeGroupSetCustomNameCommand : public UndoCommand
+{
+public:
+ NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) :
+ group_(group),
+ new_name_(name)
+ {}
+
+ virtual Project * GetRelevantProject() const override
+ {
+ return group_->project();
+ }
+
+protected:
+ virtual void redo() override;
+
+ virtual void undo() override;
+
+private:
+ NodeGroup *group_;
+
+ QString old_name_;
+
+ QString new_name_;
+
+};
+
+class NodeGroupAddInputPassthrough : public UndoCommand
+{
+public:
+ NodeGroupAddInputPassthrough(NodeGroup *group, const NodeInput &input) :
+ group_(group),
+ input_(input),
+ actually_added_(false)
+ {}
+
+ virtual Project * GetRelevantProject() const override
+ {
+ return group_->project();
+ }
+
+protected:
+ virtual void redo() override;
+
+ virtual void undo() override;
+
+private:
+ NodeGroup *group_;
+
+ NodeInput input_;
+
+ bool actually_added_;
+
+};
+
+}
+
+#endif // NODEGROUP_H
diff --git a/app/node/node.cpp b/app/node/node.cpp
index ddabc5a61..1d0316e8b 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -49,6 +49,7 @@ Node::Node() :
operation_stack_(0),
cache_result_(false)
{
+ uuid_ = QUuid::createUuid();
}
Node::~Node()
@@ -88,6 +89,8 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
} else if (reader->name() == QStringLiteral("label")) {
SetLabel(reader->readElementText());
+ } else if (reader->name() == QStringLiteral("uuid")) {
+ SetUUID(QUuid::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("color")) {
override_color_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("links")) {
@@ -169,6 +172,7 @@ void Node::Save(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this)));
+ writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString());
writer->writeTextElement(QStringLiteral("label"), GetLabel());
writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_));
@@ -219,7 +223,16 @@ void Node::Save(QXmlStreamWriter *writer) const
Project* Node::project() const
{
- return dynamic_cast(parent());
+ QObject *t = this->parent();
+
+ while (t) {
+ if (Project *p = dynamic_cast(t)) {
+ return p;
+ }
+ t = t->parent();
+ }
+
+ return nullptr;
}
QString Node::ShortName() const
@@ -1089,7 +1102,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const
return nullptr;
}
-Node::InputFlags Node::GetInputFlags(const QString &input) const
+InputFlags Node::GetInputFlags(const QString &input) const
{
const Input* i = GetInternalInputData(input);
@@ -1343,7 +1356,7 @@ void Node::HashAddNodeSignature(QCryptographicHash &hash) const
hash.addData(id().toUtf8());
}
-void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index)
+void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index)
{
if (id.isEmpty()) {
qWarning() << "Rejected adding input with an empty ID on node" << this->id();
diff --git a/app/node/node.h b/app/node/node.h
index ca3f7f43c..3a0fa5130 100644
--- a/app/node/node.h
+++ b/app/node/node.h
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include "codec/frame.h"
@@ -112,6 +113,9 @@ public:
Project* project() const;
+ const QUuid &GetUUID() const {return uuid_;}
+ void SetUUID(const QUuid &uuid) {uuid_ = uuid;}
+
/**
* @brief Clear current node variables and replace them with
*/
@@ -935,38 +939,9 @@ public:
};
+ InputFlags GetInputFlags(const QString& input) const;
+
protected:
- enum InputFlag {
- /// By default, inputs are keyframable, connectable, and NOT arrays
- kInputFlagNormal = 0x0,
- kInputFlagArray = 0x1,
- kInputFlagNotKeyframable = 0x2,
- kInputFlagNotConnectable = 0x4,
- kInputFlagHidden = 0x8
- };
-
- 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_;
-
- };
-
virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const;
void HashAddNodeSignature(QCryptographicHash &hash) const;
@@ -1216,8 +1191,6 @@ private:
return input_ids_.indexOf(input);
}
- InputFlags GetInputFlags(const QString& input) const;
-
Input* GetInternalInputData(const QString& input)
{
int i = GetInternalInputIndex(input);
@@ -1336,6 +1309,8 @@ private:
PositionMap context_positions_;
+ QUuid uuid_;
+
private slots:
/**
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h
index 56a89908c..5c516000c 100644
--- a/app/node/output/viewer/viewer.h
+++ b/app/node/output/viewer/viewer.h
@@ -254,8 +254,6 @@ private:
AudioPlaybackCache audio_playback_cache_;
- int operation_stack_;
-
VideoParams cached_video_params_;
AudioParams cached_audio_params_;
diff --git a/app/node/param.cpp b/app/node/param.cpp
index f0a685abe..2e579f613 100644
--- a/app/node/param.cpp
+++ b/app/node/param.cpp
@@ -69,6 +69,15 @@ bool NodeInput::IsArray() const
}
}
+InputFlags NodeInput::GetFlags() const
+{
+ if (IsValid()) {
+ return node_->GetInputFlags(input_);
+ } else {
+ return InputFlags(kInputFlagNormal);
+ }
+}
+
Node *NodeInput::GetConnectedOutput() const
{
if (IsValid()) {
@@ -87,6 +96,15 @@ NodeValue::Type NodeInput::GetDataType() const
}
}
+QVariant NodeInput::GetDefaultValue() const
+{
+ if (IsValid()) {
+ return node_->GetDefaultValue(input_);
+ } else {
+ return QVariant();
+ }
+}
+
QStringList NodeInput::GetComboBoxStrings() const
{
if (IsValid()) {
diff --git a/app/node/param.h b/app/node/param.h
index 3de4b6727..2e2276187 100644
--- a/app/node/param.h
+++ b/app/node/param.h
@@ -31,6 +31,37 @@ namespace olive {
class Node;
class NodeKeyframe;
+enum InputFlag {
+ /// By default, inputs are keyframable, connectable, and NOT arrays
+ kInputFlagNormal = 0x0,
+ kInputFlagArray = 0x1,
+ kInputFlagNotKeyframable = 0x2,
+ kInputFlagNotConnectable = 0x4,
+ kInputFlagHidden = 0x8
+};
+
+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_;
+
+};
+
struct NodeInputPair {
bool operator==(const NodeInputPair& rhs) const
{
@@ -98,7 +129,7 @@ public:
return input_;
}
- int element() const
+ const int &element() const
{
return element_;
}
@@ -123,10 +154,14 @@ public:
bool IsArray() const;
+ InputFlags GetFlags() const;
+
Node *GetConnectedOutput() const;
NodeValue::Type GetDataType() const;
+ QVariant GetDefaultValue() const;
+
QStringList GetComboBoxStrings() const;
QVariant GetProperty(const QString& key) const;
diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp
index 2c121745d..75b78c3c5 100644
--- a/app/render/previewautocacher.cpp
+++ b/app/render/previewautocacher.cpp
@@ -373,6 +373,9 @@ void PreviewAutoCacher::AddNode(Node *node)
// Add to project
copy->setParent(&copied_project_);
+ // Copy UUID
+ copy->SetUUID(node->GetUUID());
+
// Insert into map
InsertIntoCopyMap(node, copy);
diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h
index ad4665da3..f770c776f 100644
--- a/app/render/previewautocacher.h
+++ b/app/render/previewautocacher.h
@@ -24,8 +24,9 @@
#include
#include "config/config.h"
-#include "node/graph.h"
#include "node/color/colormanager/colormanager.h"
+#include "node/graph.h"
+#include "node/group/group.h"
#include "node/node.h"
#include "node/output/viewer/viewer.h"
#include "node/project/project.h"
@@ -172,6 +173,7 @@ private:
QVector graph_update_queue_;
QHash copy_map_;
+ QHash graph_map_;
ViewerOutput* copied_viewer_node_;
ColorManager* copied_color_manager_;
QVector created_nodes_;
diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp
index 9d3e77000..52b4407fc 100644
--- a/app/widget/nodeparamview/nodeparamview.cpp
+++ b/app/widget/nodeparamview/nodeparamview.cpp
@@ -32,10 +32,12 @@ namespace olive {
#define super TimeBasedWidget
-NodeParamView::NodeParamView(QWidget *parent) :
+NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
super(true, false, parent),
last_scroll_val_(0),
- focused_node_(nullptr)
+ focused_node_(nullptr),
+ create_checkboxes_(kNoCheckBoxes),
+ time_target_(nullptr)
{
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -46,16 +48,16 @@ NodeParamView::NodeParamView(QWidget *parent) :
layout->addWidget(splitter);
// Set up scroll area for params
- QScrollArea* scroll_area = new QScrollArea();
- scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
- scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- scroll_area->setWidgetResizable(true);
- splitter->addWidget(scroll_area);
+ param_scroll_area_ = new QScrollArea();
+ param_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ param_scroll_area_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ param_scroll_area_->setWidgetResizable(true);
+ splitter->addWidget(param_scroll_area_);
// Param widget
param_widget_container_ = new NodeParamViewParamContainer();
connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar);
- scroll_area->setWidget(param_widget_container_);
+ param_scroll_area_->setWidget(param_widget_container_);
param_widget_area_ = new NodeParamViewDockArea();
@@ -74,56 +76,63 @@ NodeParamView::NodeParamView(QWidget *parent) :
param_widget_container_layout->addStretch(INT_MAX);
- // Set up keyframe view
- QWidget* keyframe_area = new QWidget();
- QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area);
- keyframe_area_layout->setSpacing(0);
- keyframe_area_layout->setMargin(0);
-
- // Create ruler object
- keyframe_area_layout->addWidget(ruler());
-
- // Create keyframe view
- keyframe_view_ = new KeyframeView();
- keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- ConnectTimelineView(keyframe_view_);
- keyframe_area_layout->addWidget(keyframe_view_);
-
- // Connect ruler and keyframe view together
- connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime);
- connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime);
- connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime);
- connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged);
-
- // Connect keyframe view scaling to this
- connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale);
-
- splitter->addWidget(keyframe_area);
-
- // Set both widgets to 50/50
- splitter->setSizes({INT_MAX, INT_MAX});
-
// Disable collapsing param view (but collapsing keyframe view is permitted)
splitter->setCollapsible(0, false);
+ if (create_keyframe_view) {
+ // Set up keyframe view
+ QWidget* keyframe_area = new QWidget();
+ QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area);
+ keyframe_area_layout->setSpacing(0);
+ keyframe_area_layout->setMargin(0);
+
+ // Create ruler object
+ keyframe_area_layout->addWidget(ruler());
+
+ // Create keyframe view
+ keyframe_view_ = new KeyframeView();
+ keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ ConnectTimelineView(keyframe_view_);
+ keyframe_area_layout->addWidget(keyframe_view_);
+
+ // Connect ruler and keyframe view together
+ connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime);
+ connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime);
+ connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime);
+ connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged);
+
+ // Connect keyframe view scaling to this
+ connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale);
+
+ splitter->addWidget(keyframe_area);
+
+ // Set both widgets to 50/50
+ splitter->setSizes({INT_MAX, INT_MAX});
+ } else {
+ keyframe_view_ = nullptr;
+ }
+
// Create global vertical scrollbar on the right
vertical_scrollbar_ = new QScrollBar();
vertical_scrollbar_->setMaximum(0);
layout->addWidget(vertical_scrollbar_);
// Connect scrollbars together
- connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
- connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
- connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
- connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
- connect(vertical_scrollbar_, &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
- connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
+ connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
+ connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
- // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
- keyframe_view_->setHorizontalScrollBar(scrollbar());
- keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ if (keyframe_view_) {
+ connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
+ connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
+ connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
+ connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
- connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
+ // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
+ keyframe_view_->setHorizontalScrollBar(scrollbar());
+ keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+
+ connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
+ }
// Set a default scale - FIXME: Hardcoded
SetScale(120);
@@ -192,6 +201,14 @@ void NodeParamView::DeselectNodes(const QVector &nodes)
}
}
+void NodeParamView::SetInputChecked(const NodeInput &input, bool e)
+{
+ input_checked_.insert(input, e);
+ if (NodeParamViewItem *item = items_.value(input.node())) {
+ item->SetInputChecked(input, e);
+ }
+}
+
void NodeParamView::resizeEvent(QResizeEvent *event)
{
super::resizeEvent(event);
@@ -205,17 +222,21 @@ void NodeParamView::ScaleChangedEvent(const double &scale)
{
super::ScaleChangedEvent(scale);
- keyframe_view_->SetScale(scale);
+ if (keyframe_view_) {
+ keyframe_view_->SetScale(scale);
+ }
}
void NodeParamView::TimebaseChangedEvent(const rational &timebase)
{
super::TimebaseChangedEvent(timebase);
- keyframe_view_->SetTimebase(timebase);
+ if (keyframe_view_) {
+ keyframe_view_->SetTimebase(timebase);
+ }
foreach (NodeParamViewItem* item, items_) {
- item->SetTimebase(timebase);
+ item->SetTimebase(timebase);
}
UpdateItemTime(GetTime());
@@ -225,29 +246,37 @@ void NodeParamView::TimeChangedEvent(const rational &time)
{
super::TimeChangedEvent(time);
- keyframe_view_->SetTime(time);
+ if (keyframe_view_) {
+ keyframe_view_->SetTime(time);
+ }
UpdateItemTime(time);
}
void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n)
{
- // Set viewer as a time target
- keyframe_view_->SetTimeTarget(n);
+ if (keyframe_view_) {
+ // Set viewer as a time target
+ keyframe_view_->SetTimeTarget(n);
+ }
foreach (NodeParamViewItem* item, items_) {
item->SetTimeTarget(n);
}
+
+ time_target_ = n;
}
Node *NodeParamView::GetTimeTarget() const
{
- return keyframe_view_->GetTimeTarget();
+ return time_target_;
}
void NodeParamView::DeleteSelected()
{
- keyframe_view_->DeleteSelected();
+ if (keyframe_view_) {
+ keyframe_view_->DeleteSelected();
+ }
}
void NodeParamView::UpdateItemTime(const rational &time)
@@ -293,14 +322,16 @@ void NodeParamView::SignalNodeOrder()
void NodeParamView::AddNode(Node *n)
{
- NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_);
+ NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, param_widget_area_);
item->setAllowedAreas(Qt::LeftDockWidgetArea);
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
item->SetExpanded(node_expanded_state_.value(n, true));
- connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
- connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
+ if (keyframe_view_) {
+ connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
+ connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
+ }
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
@@ -310,6 +341,15 @@ void NodeParamView::AddNode(Node *n)
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
+ connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked);
+
+ if (create_checkboxes_) {
+ for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) {
+ if (it.key().node() == n) {
+ item->SetInputChecked(it.key(), it.value());
+ }
+ }
+ }
// Set time target
item->SetTimeTarget(GetTimeTarget());
@@ -327,15 +367,19 @@ void NodeParamView::AddNode(Node *n)
emit FocusedNodeChanged(focused_node_);
}
- keyframe_view_->AddKeyframesOfNode(n);
+ if (keyframe_view_) {
+ keyframe_view_->AddKeyframesOfNode(n);
+ }
}
void NodeParamView::RemoveNode(Node *n)
{
- keyframe_view_->RemoveKeyframesOfNode(n);
+ if (keyframe_view_) {
+ keyframe_view_->RemoveKeyframesOfNode(n);
- disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
- disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
+ disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
+ disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
+ }
delete items_.take(n);
@@ -358,8 +402,10 @@ void NodeParamView::UpdateGlobalScrollBar()
{
int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height();
- keyframe_view_->SetMaxScroll(height_offscreen);
- vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height());
+ if (keyframe_view_) {
+ keyframe_view_->SetMaxScroll(height_offscreen);
+ }
+ vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height());
}
void NodeParamView::PinNode(bool pin)
@@ -390,18 +436,20 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
item = dynamic_cast(parent);
if (item) {
- // Found it!
- if (item->GetNode() != focused_node_) {
- if (focused_node_) {
- // De-focus current node
- items_.value(focused_node_)->SetHighlighted(false);
+ if (item->parent() == param_widget_area_) {
+ // Found it!
+ if (item->GetNode() != focused_node_) {
+ if (focused_node_) {
+ // De-focus current node
+ items_.value(focused_node_)->SetHighlighted(false);
+ }
+
+ focused_node_ = item->GetNode();
+
+ item->SetHighlighted(true);
+
+ emit FocusedNodeChanged(focused_node_);
}
-
- focused_node_ = item->GetNode();
-
- item->SetHighlighted(true);
-
- emit FocusedNodeChanged(focused_node_);
}
break;
@@ -421,15 +469,17 @@ void NodeParamView::KeyframeViewDragged(int x, int y)
void NodeParamView::UpdateElementY()
{
- for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
- foreach (const QString& input, it.key()->inputs()) {
- int arr_sz = it.key()->InputArraySize(input);
+ if (keyframe_view_) {
+ for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
+ foreach (const QString& input, it.key()->inputs()) {
+ int arr_sz = it.key()->InputArraySize(input);
- for (int i=-1; iGetElementY(ic);
- keyframe_view_->SetElementY(ic, y);
+ int y = it.value()->GetElementY(ic);
+ keyframe_view_->SetElementY(ic, y);
+ }
}
}
}
diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h
index 1718f445e..677c4dce5 100644
--- a/app/widget/nodeparamview/nodeparamview.h
+++ b/app/widget/nodeparamview/nodeparamview.h
@@ -58,11 +58,25 @@ class NodeParamView : public TimeBasedWidget
{
Q_OBJECT
public:
- NodeParamView(QWidget* parent = nullptr);
+ NodeParamView(bool create_keyframe_view, QWidget* parent = nullptr);
+ NodeParamView(QWidget* parent = nullptr) :
+ NodeParamView(true, parent)
+ {
+ }
void SelectNodes(const QVector &nodes);
void DeselectNodes(const QVector& nodes);
+ void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
+ {
+ create_checkboxes_ = e;
+ }
+
+ bool IsInputChecked(const NodeInput &input) const
+ {
+ return input_checked_.value(input);
+ }
+
const QMap& GetItemMap() const
{
return items_;
@@ -82,6 +96,9 @@ public:
keyframe_view_->DeselectAll();
}
+public slots:
+ void SetInputChecked(const NodeInput &input, bool e);
+
signals:
void RequestSelectNode(const QVector& target);
@@ -117,10 +134,10 @@ private:
int last_scroll_val_;
+ QScrollArea* param_scroll_area_;
+
NodeParamViewParamContainer* param_widget_container_;
- // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
- // docking windows
NodeParamViewDockArea* param_widget_area_;
QVector pinned_nodes_;
@@ -131,6 +148,12 @@ private:
Node* focused_node_;
+ NodeParamViewCheckBoxBehavior create_checkboxes_;
+
+ Node *time_target_;
+
+ QHash input_checked_;
+
private slots:
void UpdateGlobalScrollBar();
diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h
index 90a00c1a0..c313236cd 100644
--- a/app/widget/nodeparamview/nodeparamviewdockarea.h
+++ b/app/widget/nodeparamview/nodeparamviewdockarea.h
@@ -25,6 +25,8 @@
namespace olive {
+// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
+// for docking QDockWidgets
class NodeParamViewDockArea : public QMainWindow
{
Q_OBJECT
diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp
index 3c2d5bbb0..3347551ef 100644
--- a/app/widget/nodeparamview/nodeparamviewitem.cpp
+++ b/app/widget/nodeparamview/nodeparamviewitem.cpp
@@ -38,12 +38,14 @@ const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1;
const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1;
const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1;
-// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
-const int NodeParamViewItemBody::kWidgetStartColumn = 2;
+const int NodeParamViewItemBody::kOptionalCheckBox = 0;
+const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1;
+const int NodeParamViewItemBody::kLabelColumn = 2;
+const int NodeParamViewItemBody::kWidgetStartColumn = 3;
#define super QDockWidget
-NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
+NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
super(parent),
node_(node),
highlighted_(false)
@@ -55,10 +57,11 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
this->setTitleBarWidget(title_bar_);
// Create and add contents widget
- body_ = new NodeParamViewItemBody(node_);
+ body_ = new NodeParamViewItemBody(node_, create_checkboxes);
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
+ connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged);
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded);
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled);
@@ -165,6 +168,11 @@ int NodeParamViewItem::GetElementY(const NodeInput &c) const
}
}
+void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e)
+{
+ body_->SetInputChecked(input, e);
+}
+
void NodeParamViewItem::ToggleExpanded()
{
SetExpanded(!IsExpanded());
@@ -222,9 +230,10 @@ void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
collapse_btn_->click();
}
-NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) :
+NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
QWidget(parent),
- node_(node)
+ node_(node),
+ create_checkboxes_(create_checkboxes)
{
QGridLayout* root_layout = new QGridLayout(this);
@@ -277,11 +286,22 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
ui_objects.layout = layout;
ui_objects.row = row;
+ // Create optional checkbox if requested
+ if (create_checkboxes_) {
+ ui_objects.optional_checkbox = new QCheckBox();
+ connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked);
+ layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
+
+ if (create_checkboxes_ == kCheckBoxesOnNonConnected && input_ref.IsConnected()) {
+ ui_objects.optional_checkbox->setVisible(false);
+ }
+ }
+
// Add descriptor label
ui_objects.main_label = new QLabel();
- // Label always goes into column 1 (array collapse button goes into 0 if applicable)
- layout->addWidget(ui_objects.main_label, row, 1);
+ // Create input label
+ layout->addWidget(ui_objects.main_label, row, kLabelColumn);
if (node->InputIsArray(input)) {
if (element == -1) {
@@ -292,8 +312,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
// Default to collapsed
array_collapse_btn->setChecked(false);
- // Collapse button always goes into column 0
- layout->addWidget(array_collapse_btn, row, 0);
+ // Add collapse button to layout
+ layout->addWidget(array_collapse_btn, row, kArrayCollapseBtnColumn);
// Connect signal to show/hide array params when toggled
connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed);
@@ -448,6 +468,11 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
if (ui_objects.key_control) {
ui_objects.key_control->setVisible(!input.IsConnected());
}
+
+ // Show/hide optional checkbox if requested
+ if (create_checkboxes_ == kCheckBoxesOnNonConnected) {
+ ui_objects.optional_checkbox->setVisible(!input.IsConnected());
+ }
}
}
@@ -575,6 +600,16 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase)
}
}
+void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e)
+{
+ if (input_ui_map_.contains(input)) {
+ QCheckBox *cb = input_ui_map_.value(input).optional_checkbox;
+ if (cb) {
+ cb->setChecked(e);
+ }
+ }
+}
+
void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input)
{
InputUI ui = input_ui_map_.value(input);
@@ -588,12 +623,25 @@ void NodeParamViewItemBody::ShowSpeedDurationDialogForNode()
sdd.exec();
}
+void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e)
+{
+ QCheckBox *cb = static_cast(sender());
+
+ for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
+ if (it.value().optional_checkbox == cb) {
+ emit InputCheckedChanged(it.key(), e);
+ break;
+ }
+ }
+}
+
NodeParamViewItemBody::InputUI::InputUI() :
main_label(nullptr),
widget_bridge(nullptr),
connected_label(nullptr),
key_control(nullptr),
extra_btn(nullptr),
+ optional_checkbox(nullptr),
array_insert_btn(nullptr),
array_remove_btn(nullptr)
{
diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h
index bbd31c9f4..55a0a3174 100644
--- a/app/widget/nodeparamview/nodeparamviewitem.h
+++ b/app/widget/nodeparamview/nodeparamviewitem.h
@@ -21,6 +21,7 @@
#ifndef NODEPARAMVIEWITEM_H
#define NODEPARAMVIEWITEM_H
+#include
#include
#include
#include
@@ -38,6 +39,12 @@
namespace olive {
+enum NodeParamViewCheckBoxBehavior {
+ kNoCheckBoxes,
+ kCheckBoxesOn,
+ kCheckBoxesOnNonConnected
+};
+
class NodeParamViewItemTitleBar : public QWidget
{
Q_OBJECT
@@ -75,7 +82,7 @@ private:
class NodeParamViewItemBody : public QWidget {
Q_OBJECT
public:
- NodeParamViewItemBody(Node* node, QWidget* parent = nullptr);
+ NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
void SetTimeTarget(Node* target);
@@ -86,7 +93,9 @@ public:
int GetElementY(NodeInput c) const;
// Set the timebase of any timebased widgets contained here
- void SetTimebase(const rational& timebase);
+ void SetTimebase(const rational& timebase);
+
+ void SetInputChecked(const NodeInput &input, bool e);
signals:
void RequestSetTime(const rational& time);
@@ -95,6 +104,8 @@ signals:
void ArrayExpandedChanged(bool e);
+ void InputCheckedChanged(const NodeInput &input, bool e);
+
private:
void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index);
@@ -114,6 +125,7 @@ private:
QGridLayout* layout;
int row;
QPushButton *extra_btn;
+ QCheckBox *optional_checkbox;
NodeParamViewArrayButton* array_insert_btn;
NodeParamViewArrayButton* array_remove_btn;
@@ -135,6 +147,8 @@ private:
rational timebase_;
+ NodeParamViewCheckBoxBehavior create_checkboxes_;
+
/**
* @brief The column to place the keyframe controls in
*
@@ -147,6 +161,9 @@ private:
static const int kArrayRemoveColumn;
static const int kExtraButtonColumn;
+ static const int kOptionalCheckBox;
+ static const int kArrayCollapseBtnColumn;
+ static const int kLabelColumn;
static const int kWidgetStartColumn;
private slots:
@@ -168,13 +185,15 @@ private slots:
void ShowSpeedDurationDialogForNode();
+ void OptionalCheckBoxClicked(bool e);
+
};
class NodeParamViewItem : public QDockWidget
{
Q_OBJECT
public:
- NodeParamViewItem(Node* node, QWidget* parent = nullptr);
+ NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
void SetTimeTarget(Node* target);
@@ -196,6 +215,8 @@ public:
int GetElementY(const NodeInput& c) const;
+ void SetInputChecked(const NodeInput &input, bool e);
+
public slots:
void SetExpanded(bool e);
@@ -214,6 +235,8 @@ signals:
void Moved();
+ void InputCheckedChanged(const NodeInput &input, bool e);
+
protected:
virtual void changeEvent(QEvent *e) override;
diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp
index e805d7eda..9b3619e0e 100644
--- a/app/widget/nodeview/nodeview.cpp
+++ b/app/widget/nodeview/nodeview.cpp
@@ -21,16 +21,18 @@
#include "nodeview.h"
#include
+#include
#include
#include
#include
#include "core.h"
+#include "dialog/nodegroup/nodegroupdialog.h"
#include "nodeviewundo.h"
#include "node/audio/volume/volume.h"
#include "node/distort/transform/transformdistortnode.h"
#include "node/factory.h"
-#include "node/group.h"
+#include "node/group/group.h"
#include "node/traverser.h"
#include "widget/menu/menushared.h"
#include "widget/timebased/timebasedview.h"
@@ -638,6 +640,7 @@ void NodeView::UpdateSelectionCache()
if (current_selection.isEmpty()) {
// All nodes that were selected have been deselected, so we'll just set them all to `deselected`
deselected = selected_nodes_;
+ selected_nodes_.clear();
} else {
foreach (Node* n, selected_nodes_) {
bool still_selected = false;
@@ -649,20 +652,20 @@ void NodeView::UpdateSelectionCache()
}
}
- if (still_selected) {
+ if (!still_selected) {
deselected.append(n);
selected_nodes_.removeOne(n);
}
}
}
- if (!selected.isEmpty()) {
- emit NodesSelected(selected);
- }
-
if (!deselected.isEmpty()) {
emit NodesDeselected(deselected);
}
+
+ if (!selected.isEmpty()) {
+ emit NodesSelected(selected);
+ }
}
void NodeView::ShowContextMenu(const QPoint &pos)
@@ -1082,13 +1085,88 @@ void NodeView::PositionNewEdge(const QPoint &pos)
void NodeView::GroupNodes()
{
- /*NodeGroup *group = new NodeGroup();
- selected_nodes_*/
+ // Get items
+ QVector items = scene_.GetSelectedItems();
+ if (items.isEmpty()) {
+ return;
+ }
+
+ // Get node context
+ Node *context = items.first()->GetContext();
+ QPointF avg_pos = items.first()->GetNodePosition();
+ for (int i=1; iGetContext() != context) {
+ QMessageBox::critical(this, tr("Failed to group nodes"), tr("Nodes can only be grouped if they're in the same context."));
+ return;
+ }
+
+ avg_pos += items.at(i)->GetNodePosition();
+ }
+ avg_pos /= items.size();
+
+ // Create group
+ NodeGroup *group = new NodeGroup();
+
+ // Add group to graph and context
+ MultiUndoCommand *command = new MultiUndoCommand();
+
+ command->add_child(new NodeAddCommand(context->parent(), group));
+ command->add_child(new NodeSetPositionCommand(group, context, avg_pos));
+
+ // Add nodes to group
+ QVector nodes_to_group = selected_nodes_;
+ DeselectAll();
+ foreach (Node *n, nodes_to_group) {
+ for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) {
+ Node *output = it->second;
+ const NodeInput &input = it->first;
+
+ if (!nodes_to_group.contains(output)) {
+ command->add_child(new NodeEdgeRemoveCommand(output, input));
+ command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element())));
+ }
+ }
+
+ for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) {
+ Node *output = it->first;
+ const NodeInput &input = it->second;
+
+ if (!nodes_to_group.contains(input.node())) {
+ command->add_child(new NodeEdgeRemoveCommand(output, input));
+ command->add_child(new NodeEdgeAddCommand(group, input));
+ }
+ }
+
+ command->add_child(new NodeRemovePositionFromContextCommand(n, context));
+ command->add_child(new NodeAddToGroupCommand(n, group));
+ command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition()));
+
+ for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) {
+ NodeInput input(n, *it, -1);
+
+ if (!input.IsConnected() || !nodes_to_group.contains(input.GetConnectedOutput())) {
+ command->add_child(new NodeGroupAddInputPassthrough(group, input));
+ }
+ }
+ }
+
+ // Do command
+ command->redo_now();
+
+ NodeGroupDialog ngd(group, this);
+ if (ngd.exec() == QDialog::Accepted) {
+ // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again)
+ Core::instance()->undo_stack()->push(command);
+ } else {
+ // Undo command and delete
+ command->undo_now();
+ delete command;
+ }
}
void NodeView::UngroupNodes()
{
- //static_cast(selected_nodes_.first());
+ //NodeGroup *group = static_cast(selected_nodes_.first());
}
void NodeView::PasteNodesInternal(const QVector &duplicate_nodes)
diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h
index fd5e2c0c9..5f7c802bb 100644
--- a/app/widget/nodeview/nodeview.h
+++ b/app/widget/nodeview/nodeview.h
@@ -45,7 +45,7 @@ class NodeView : public HandMovableView, public NodeCopyPasteService
{
Q_OBJECT
public:
- NodeView(QWidget* parent);
+ NodeView(QWidget* parent = nullptr);
virtual ~NodeView() override;
diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp
index b608ee58b..c2c516c60 100644
--- a/app/widget/nodeview/nodeviewcontext.cpp
+++ b/app/widget/nodeview/nodeviewcontext.cpp
@@ -189,7 +189,9 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command)
void NodeViewContext::Select(const QVector &nodes)
{
foreach (Node *n, nodes) {
- item_map_.value(n)->setSelected(true);
+ if (NodeViewItem *item = item_map_.value(n)) {
+ item->setSelected(true);
+ }
}
}
diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h
index cf49b332d..7cdc638a7 100644
--- a/app/widget/nodeview/nodeviewcontext.h
+++ b/app/widget/nodeview/nodeviewcontext.h
@@ -36,6 +36,11 @@ public:
QPointF MapScenePosToNodePosInContext(const QPointF &pos) const;
+ NodeViewItem *GetItemFromMap(Node *node) const
+ {
+ return item_map_.value(node);
+ }
+
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
public slots:
diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp
index 3ea29ff5a..1a9b6c516 100644
--- a/app/widget/nodeview/nodeviewitem.cpp
+++ b/app/widget/nodeview/nodeviewitem.cpp
@@ -544,7 +544,7 @@ QPointF NodeViewItem::GetInputPoint(const QString &input, int element) const
int index = node_inputs_.indexOf(input);
if (index < 0 || index >= int(input_connectors_.size())) {
- return QPointF();
+ return pos();
}
return input_connectors_[index]->scenePos();
diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp
index 47c23c3cb..db245f005 100644
--- a/app/window/mainwindow/mainwindow.cpp
+++ b/app/window/mainwindow/mainwindow.cpp
@@ -467,15 +467,6 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks)
}
}
-void MainWindow::ProjectPanelSelectionChanged(const QVector &nodes)
-{
- ProjectPanel *panel = static_cast(sender());
-
- if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
- node_panel_->Select(nodes, true);
- }
-}
-
void MainWindow::ShowWelcomeDialog()
{
if (Config::Current()[QStringLiteral("ShowWelcomeDialog")].toBool()) {
@@ -572,7 +563,6 @@ ProjectPanel *MainWindow::AppendProjectPanel()
connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::ProjectCloseRequested);
connect(panel, &ProjectPanel::ProjectNameChanged, this, &MainWindow::UpdateTitle);
- connect(panel, &ProjectPanel::SelectionChanged, this, &MainWindow::ProjectPanelSelectionChanged);
return panel;
}
diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h
index f8cf36d98..719652200 100644
--- a/app/window/mainwindow/mainwindow.h
+++ b/app/window/mainwindow/mainwindow.h
@@ -193,8 +193,6 @@ private slots:
void TimelinePanelSelectionChanged(const QVector &blocks);
- void ProjectPanelSelectionChanged(const QVector &nodes);
-
void ShowWelcomeDialog();
};